Commit 299991019d

Andrew Kelley <andrew@ziglang.org>
2019-10-18 02:20:22
rework the progress module and integrate with stage1
1 parent a73c7bc
lib/std/fmt.zig
@@ -1055,14 +1055,21 @@ const BufPrintContext = struct {
 };
 
 fn bufPrintWrite(context: *BufPrintContext, bytes: []const u8) !void {
-    if (context.remaining.len < bytes.len) return error.BufferTooSmall;
+    if (context.remaining.len < bytes.len) {
+        mem.copy(u8, context.remaining, bytes[0..context.remaining.len]);
+        return error.BufferTooSmall;
+    }
     mem.copy(u8, context.remaining, bytes);
     context.remaining = context.remaining[bytes.len..];
 }
 
-pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) ![]u8 {
+pub const BufPrintError = error{
+    /// As much as possible was written to the buffer, but it was too small to fit all the printed bytes.
+    BufferTooSmall,
+};
+pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) BufPrintError![]u8 {
     var context = BufPrintContext{ .remaining = buf };
-    try format(&context, error{BufferTooSmall}, bufPrintWrite, fmt, args);
+    try format(&context, BufPrintError, bufPrintWrite, fmt, args);
     return buf[0 .. buf.len - context.remaining.len];
 }
 
lib/std/progress.zig
@@ -1,107 +1,242 @@
 const std = @import("std");
 const testing = std.testing;
+const assert = std.debug.assert;
+
+/// This API is non-allocating and non-fallible. The tradeoff is that users of
+/// this API must provide the storage for each `Progress.Node`.
+/// Initialize the struct directly, overriding these fields as desired:
+/// * `refresh_rate_ms`
+/// * `initial_delay_ms`
+pub const Progress = struct {
+    /// `null` if the current node (and its children) should
+    /// not print on update()
+    terminal: ?std.fs.File = undefined,
+
+    root: Node = undefined,
+
+    /// Keeps track of how much time has passed since the beginning.
+    /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
+    timer: std.time.Timer = undefined,
+
+    /// When the previous refresh was written to the terminal.
+    /// Used to compare with `refresh_rate_ms`.
+    prev_refresh_timestamp: u64 = undefined,
+
+    /// This buffer represents the maximum number of bytes written to the terminal
+    /// with each refresh.
+    output_buffer: [100]u8 = undefined,
+
+    /// Keeps track of how many columns in the terminal have been output, so that
+    /// we can move the cursor back later.
+    columns_written: usize = undefined,
+
+    /// How many nanoseconds between writing updates to the terminal.
+    refresh_rate_ns: u64 = 50 * std.time.millisecond,
+
+    /// How many nanoseconds to keep the output hidden
+    initial_delay_ns: u64 = 500 * std.time.millisecond,
+
+    done: bool = true,
+
+    /// Represents one unit of progress. Each node can have children nodes, or
+    /// one can use integers with `update`.
+    pub const Node = struct {
+        context: *Progress,
+        parent: ?*Node,
+        completed_items: usize,
+        name: []const u8,
+        recently_updated_child: ?*Node = null,
+
+        /// This field may be updated freely.
+        estimated_total_items: ?usize,
+
+        /// Create a new child progress node.
+        /// Call `Node.end` when done.
+        /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
+        /// API to set `self.parent.recently_updated_child` with the return value.
+        /// Until that is fixed you probably want to call `activate` on the return value.
+        pub fn start(self: *Node, name: []const u8, estimated_total_items: ?usize) Node {
+            return Node{
+                .context = self.context,
+                .parent = self,
+                .completed_items = 0,
+                .name = name,
+                .estimated_total_items = estimated_total_items,
+            };
+        }
 
-pub const PrintConfig = struct {
-    /// If the current node (and its children) should
-    /// print to stderr on update()
-    flag: bool = false,
+        /// This is the same as calling `start` and then `end` on the returned `Node`.
+        pub fn completeOne(self: *Node) void {
+            if (self.parent) |parent| parent.recently_updated_child = self;
+            self.completed_items += 1;
+            self.context.maybeRefresh();
+        }
 
-    /// If all output should be suppressed instead
-    /// serves the same practical purpose as `flag` but supposed to be used
-    /// by separate parts of the user program.
-    suppress: bool = false,
-};
+        pub fn end(self: *Node) void {
+            self.context.maybeRefresh();
+            if (self.parent) |parent| {
+                if (parent.recently_updated_child) |parent_child| {
+                    if (parent_child == self) {
+                        parent.recently_updated_child = null;
+                    }
+                }
+                parent.completeOne();
+            } else {
+                self.context.done = true;
+                self.context.refresh();
+            }
+        }
 
-pub const ProgressNode = struct {
-    completed_items: usize = 0,
-    total_items: usize,
+        /// Tell the parent node that this node is actively being worked on.
+        pub fn activate(self: *Node) void {
+            if (self.parent) |parent| parent.recently_updated_child = self;
+        }
+    };
 
-    print_config: PrintConfig,
+    /// Create a new progress node.
+    /// Call `Node.end` when done.
+    /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
+    /// API to return Progress rather than accept it as a parameter.
+    pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node {
+        if (std.io.getStdErr()) |stderr| {
+            const is_term = stderr.isTty();
+            self.terminal = if (is_term) stderr else null;
+        } else |_| {
+            self.terminal = null;
+        }
+        self.root = Node{
+            .context = self,
+            .parent = null,
+            .completed_items = 0,
+            .name = name,
+            .estimated_total_items = estimated_total_items,
+        };
+        self.prev_refresh_timestamp = 0;
+        self.columns_written = 0;
+        self.timer = try std.time.Timer.start();
+        self.done = false;
+        return &self.root;
+    }
 
-    // TODO maybe instead of keeping a prefix field, we could
-    // select the proper prefix at the time of update(), and if we're not
-    // in a terminal, we use warn("/r{}", lots_of_whitespace).
-    prefix: []const u8,
+    /// Updates the terminal if enough time has passed since last update.
+    pub fn maybeRefresh(self: *Progress) void {
+        const now = self.timer.read();
+        if (now < self.initial_delay_ns) return;
+        if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
+        self.refresh();
+    }
 
-    /// Create a new progress node.
-    pub fn start(
-        parent_opt: ?ProgressNode,
-        total_items_opt: ?usize,
-    ) !ProgressNode {
-
-        // inherit the last set print "configuration" from the parent node
-        var print_config = PrintConfig{};
-        if (parent_opt) |parent| {
-            print_config = parent.print_config;
+    /// Updates the terminal and resets `self.next_refresh_timestamp`.
+    pub fn refresh(self: *Progress) void {
+        const file = self.terminal orelse return;
+
+        const prev_columns_written = self.columns_written;
+        var end: usize = 0;
+        if (self.columns_written > 0) {
+            end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", self.columns_written) catch unreachable).len;
+            self.columns_written = 0;
         }
 
-        var stderr = try std.io.getStdErr();
-        const is_term = std.os.isatty(stderr.handle);
+        if (!self.done) {
+            self.bufWriteNode(self.root, &end);
+            self.bufWrite(&end, "...");
+        }
 
-        // if we're in a terminal, use vt100 escape codes
-        // for the progress.
-        var prefix: []const u8 = undefined;
-        if (is_term) {
-            prefix = "\x21[2K\r";
-        } else {
-            prefix = "\n";
+        if (prev_columns_written > self.columns_written) {
+            const amt = prev_columns_written - self.columns_written;
+            std.mem.set(u8, self.output_buffer[end .. end + amt], ' ');
+            end += amt;
+            end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", amt) catch unreachable).len;
         }
 
-        return ProgressNode{
-            .total_items = total_items_opt orelse 0,
-            .print_config = print_config,
-            .prefix = prefix,
+        _ = file.write(self.output_buffer[0..end]) catch |e| {
+            // Stop trying to write to this file once it errors.
+            self.terminal = null;
         };
+        self.prev_refresh_timestamp = self.timer.read();
     }
 
-    /// Signal an update on the progress node.
-    /// The user of this function is supposed to modify
-    /// ProgressNode.PrintConfig.flag when update() is supposed to print.
-    pub fn update(
-        self: *ProgressNode,
-        current_action: ?[]const u8,
-        items_done_opt: ?usize,
-    ) void {
-        if (items_done_opt) |items_done| {
-            self.completed_items = items_done;
-
-            if (items_done > self.total_items) {
-                self.total_items = items_done;
+    fn bufWriteNode(self: *Progress, node: Node, end: *usize) void {
+        if (node.name.len != 0 or node.estimated_total_items != null) {
+            if (node.name.len != 0) {
+                self.bufWrite(end, "{}", node.name);
+                if (node.recently_updated_child != null or node.estimated_total_items != null or
+                    node.completed_items != 0)
+                {
+                    self.bufWrite(end, "...");
+                }
+            }
+            if (node.estimated_total_items) |total| {
+                self.bufWrite(end, "[{}/{}] ", node.completed_items, total);
+            } else if (node.completed_items != 0) {
+                self.bufWrite(end, "[{}] ", node.completed_items);
             }
         }
-
-        var cfg = self.print_config;
-        if (cfg.flag and !cfg.suppress and current_action != null) {
-            std.debug.warn(
-                "{}[{}/{}] {}",
-                self.prefix,
-                self.completed_items,
-                self.total_items,
-                current_action,
-            );
+        if (node.recently_updated_child) |child| {
+            self.bufWriteNode(child.*, end);
         }
     }
 
-    pub fn end(self: *ProgressNode) void {
-        if (!self.print_config.flag) return;
-
-        // TODO emoji?
-        std.debug.warn("\n[V] done!");
+    fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: ...) void {
+        if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
+            const amt = written.len;
+            end.* += amt;
+            self.columns_written += amt;
+        } else |err| switch (err) {
+            error.BufferTooSmall => {
+                self.columns_written += self.output_buffer.len - end.*;
+                end.* = self.output_buffer.len;
+            },
+        }
+        const bytes_needed_for_esc_codes_at_end = 11;
+        const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
+        if (end.* > max_end) {
+            const suffix = "...";
+            self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
+            std.mem.copy(u8, self.output_buffer[max_end..], suffix);
+            end.* = max_end + suffix.len;
+        }
     }
 };
 
 test "basic functionality" {
-    var node = try ProgressNode.start(null, 100);
-
-    var buf: [100]u8 = undefined;
+    var progress = Progress{};
+    const root_node = try progress.start("", 100);
+    defer root_node.end();
+
+    const sub_task_names = [_][]const u8{
+        "reticulating splines",
+        "adjusting shoes",
+        "climbing towers",
+        "pouring juice",
+    };
+    var next_sub_task: usize = 0;
 
     var i: usize = 0;
-    while (i < 100) : (i += 6) {
-        if (i > 50) node.print_config.flag = true;
-        const msg = try std.fmt.bufPrint(buf[0..], "action at i={}", i);
-        node.update(msg, i);
+    while (i < 100) : (i += 1) {
+        var node = root_node.start(sub_task_names[next_sub_task], 5);
+        node.activate();
+        next_sub_task = (next_sub_task + 1) % sub_task_names.len;
+
+        node.completeOne();
+        std.time.sleep(5 * std.time.millisecond);
+        node.completeOne();
+        node.completeOne();
+        std.time.sleep(5 * std.time.millisecond);
+        node.completeOne();
+        node.completeOne();
+        std.time.sleep(5 * std.time.millisecond);
+
+        node.end();
+
+        std.time.sleep(5 * std.time.millisecond);
+    }
+    {
+        var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null);
+        node.activate();
         std.time.sleep(10 * std.time.millisecond);
+        progress.maybeRefresh();
+        std.time.sleep(10 * std.time.millisecond);
+        node.end();
     }
-
-    node.end();
 }
lib/std/std.zig
@@ -6,20 +6,21 @@ pub const BufMap = @import("buf_map.zig").BufMap;
 pub const BufSet = @import("buf_set.zig").BufSet;
 pub const Buffer = @import("buffer.zig").Buffer;
 pub const BufferOutStream = @import("io.zig").BufferOutStream;
+pub const ChildProcess = @import("child_process.zig").ChildProcess;
 pub const DynLib = @import("dynamic_library.zig").DynLib;
 pub const HashMap = @import("hash_map.zig").HashMap;
 pub const Mutex = @import("mutex.zig").Mutex;
-pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;
 pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;
-pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
+pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;
 pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
+pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
 pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
-pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
-pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex;
+pub const Progress = @import("progress.zig").Progress;
 pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
+pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
 pub const SpinLock = @import("spinlock.zig").SpinLock;
+pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex;
 pub const StringHashMap = @import("hash_map.zig").StringHashMap;
-pub const ChildProcess = @import("child_process.zig").ChildProcess;
 pub const TailQueue = @import("linked_list.zig").TailQueue;
 pub const Thread = @import("thread.zig").Thread;
 
src/all_types.hpp
@@ -2010,6 +2010,8 @@ struct CodeGen {
 
     ZigFn *largest_frame_fn;
 
+    Stage2ProgressNode *progress_node;
+
     WantPIC want_pic;
     WantStackCheck want_stack_check;
     CacheHash cache_hash;
src/codegen.cpp
@@ -7610,7 +7610,7 @@ static void zig_llvm_emit_output(CodeGen *g) {
             if (g->bundle_compiler_rt && (g->out_type == OutTypeObj ||
                 (g->out_type == OutTypeLib && !g->is_dynamic)))
             {
-                zig_link_add_compiler_rt(g);
+                zig_link_add_compiler_rt(g, g->progress_node);
             }
             break;
 
@@ -9453,7 +9453,7 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose
 }
 
 // returns true if it was a cache miss
-static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
+static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file, Stage2ProgressNode *parent_prog_node) {
     Error err;
 
     Buf *artifact_dir;
@@ -9464,6 +9464,10 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
     Buf *c_source_file = buf_create_from_str(c_file->source_path);
     Buf *c_source_basename = buf_alloc();
     os_path_split(c_source_file, nullptr, c_source_basename);
+
+    Stage2ProgressNode *child_prog_node = stage2_progress_start(parent_prog_node, buf_ptr(c_source_basename),
+            buf_len(c_source_basename), 0);
+
     Buf *final_o_basename = buf_alloc();
     os_path_extname(c_source_basename, final_o_basename, nullptr);
     buf_append_str(final_o_basename, target_o_file_ext(g->zig_target));
@@ -9580,6 +9584,8 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
 
     g->link_objects.append(o_final_path);
     g->caches_to_release.append(cache_hash);
+
+    stage2_progress_end(child_prog_node);
 }
 
 // returns true if we had any cache misses
@@ -9596,11 +9602,16 @@ static void gen_c_objects(CodeGen *g) {
     }
 
     codegen_add_time_event(g, "Compile C Code");
+    const char *c_prog_name = "compiling C objects";
+    Stage2ProgressNode *c_prog_node = stage2_progress_start(g->progress_node, c_prog_name, strlen(c_prog_name),
+            g->c_source_files.length);
 
     for (size_t c_file_i = 0; c_file_i < g->c_source_files.length; c_file_i += 1) {
         CFile *c_file = g->c_source_files.at(c_file_i);
-        gen_c_object(g, self_exe_path, c_file);
+        gen_c_object(g, self_exe_path, c_file, c_prog_node);
     }
+
+    stage2_progress_end(c_prog_node);
 }
 
 void codegen_add_object(CodeGen *g, Buf *object_path) {
@@ -10320,6 +10331,10 @@ void codegen_build_and_link(CodeGen *g) {
             init(g);
 
             codegen_add_time_event(g, "Semantic Analysis");
+            const char *progress_name = "Semantic Analysis";
+            Stage2ProgressNode *child_progress_node = stage2_progress_start(g->progress_node,
+                    progress_name, strlen(progress_name), 0);
+            (void)child_progress_node;
 
             gen_root_source(g);
 
@@ -10343,13 +10358,31 @@ void codegen_build_and_link(CodeGen *g) {
 
         if (need_llvm_module(g)) {
             codegen_add_time_event(g, "Code Generation");
+            {
+                const char *progress_name = "Code Generation";
+                Stage2ProgressNode *child_progress_node = stage2_progress_start(g->progress_node,
+                        progress_name, strlen(progress_name), 0);
+                (void)child_progress_node;
+            }
 
             do_code_gen(g);
             codegen_add_time_event(g, "LLVM Emit Output");
+            {
+                const char *progress_name = "LLVM Emit Output";
+                Stage2ProgressNode *child_progress_node = stage2_progress_start(g->progress_node,
+                        progress_name, strlen(progress_name), 0);
+                (void)child_progress_node;
+            }
             zig_llvm_emit_output(g);
 
             if (!g->disable_gen_h && (g->out_type == OutTypeObj || g->out_type == OutTypeLib)) {
                 codegen_add_time_event(g, "Generate .h");
+                {
+                    const char *progress_name = "Generate .h";
+                    Stage2ProgressNode *child_progress_node = stage2_progress_start(g->progress_node,
+                            progress_name, strlen(progress_name), 0);
+                    (void)child_progress_node;
+                }
                 gen_h_file(g);
             }
         }
@@ -10446,10 +10479,15 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c
 }
 
 CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
-        ZigLibCInstallation *libc)
+        ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *child_progress_node)
 {
+    if (!child_progress_node) {
+        child_progress_node = stage2_progress_start(parent_gen->progress_node, name, strlen(name), 0);
+    }
+
     CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,
-        parent_gen->build_mode, parent_gen->zig_lib_dir, libc, get_stage1_cache_path(), false);
+        parent_gen->build_mode, parent_gen->zig_lib_dir, libc, get_stage1_cache_path(), false, child_progress_node);
+    child_gen->root_out_name = buf_create_from_str(name);
     child_gen->disable_gen_h = true;
     child_gen->want_stack_check = WantStackCheckDisabled;
     child_gen->verbose_tokenize = parent_gen->verbose_tokenize;
@@ -10478,9 +10516,10 @@ CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType o
 
 CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget *target,
     OutType out_type, BuildMode build_mode, Buf *override_lib_dir,
-    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build)
+    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node)
 {
     CodeGen *g = allocate<CodeGen>(1);
+    g->progress_node = progress_node;
 
     codegen_add_time_event(g, "Initialize");
 
src/codegen.hpp
@@ -18,10 +18,10 @@
 
 CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget *target,
     OutType out_type, BuildMode build_mode, Buf *zig_lib_dir,
-    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build);
+    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node);
 
 CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
-        ZigLibCInstallation *libc);
+        ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *progress_node);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len);
@@ -46,7 +46,7 @@ void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patc
 void codegen_add_time_event(CodeGen *g, const char *name);
 void codegen_print_timing_report(CodeGen *g, FILE *f);
 void codegen_link(CodeGen *g);
-void zig_link_add_compiler_rt(CodeGen *g);
+void zig_link_add_compiler_rt(CodeGen *g, Stage2ProgressNode *progress_node);
 void codegen_build_and_link(CodeGen *g);
 
 ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const char *root_src_path,
src/glibc.cpp
@@ -169,7 +169,7 @@ Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbo
 }
 
 Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, const ZigTarget *target,
-        Buf **out_dir, bool verbose)
+        Buf **out_dir, bool verbose, Stage2ProgressNode *progress_node)
 {
     Error err;
 
@@ -332,8 +332,7 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con
             return err;
         }
 
-        CodeGen *child_gen = create_child_codegen(g, zig_file_path, OutTypeLib, nullptr);
-        codegen_set_out_name(child_gen, buf_create_from_str(lib->name));
+        CodeGen *child_gen = create_child_codegen(g, zig_file_path, OutTypeLib, nullptr, lib->name, progress_node);
         codegen_set_lib_version(child_gen, lib->sover, 0, 0);
         child_gen->is_dynamic = true;
         child_gen->is_dummy_so = true;
src/glibc.hpp
@@ -41,7 +41,7 @@ struct ZigGLibCAbi {
 
 Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbose);
 Error glibc_build_dummies_and_maps(CodeGen *codegen, const ZigGLibCAbi *glibc_abi, const ZigTarget *target,
-        Buf **out_dir, bool verbose);
+        Buf **out_dir, bool verbose, Stage2ProgressNode *progress_node);
 
 // returns ErrorUnknownABI when glibc is not the native libc
 Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver);
src/link.cpp
@@ -594,11 +594,13 @@ struct LinkJob {
     ZigList<const char *> args;
     bool link_in_crt;
     HashMap<Buf *, bool, buf_hash, buf_eql_buf> rpath_table;
+    Stage2ProgressNode *build_dep_prog_node;
 };
 
-static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFile *c_file) {
-    CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr);
-    codegen_set_out_name(child_gen, buf_create_from_str(name));
+static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFile *c_file,
+        Stage2ProgressNode *progress_node)
+{
+    CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr, name, progress_node);
     ZigList<CFile *> c_source_files = {0};
     c_source_files.append(c_file);
     child_gen->c_source_files = c_source_files;
@@ -622,9 +624,8 @@ static const char *path_from_libunwind(CodeGen *g, const char *subpath) {
     return path_from_zig_lib(g, "libunwind", subpath);
 }
 
-static const char *build_libunwind(CodeGen *parent) {
-    CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-    codegen_set_out_name(child_gen, buf_create_from_str("unwind"));
+static const char *build_libunwind(CodeGen *parent, Stage2ProgressNode *progress_node) {
+    CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "unwind", progress_node);
     LinkLib *new_link_lib = codegen_add_link_lib(child_gen, buf_create_from_str("c"));
     new_link_lib->provided_explicitly = false;
     enum SrcKind {
@@ -1017,9 +1018,8 @@ static bool is_musl_arch_name(const char *name) {
     return false;
 }
 
-static const char *build_musl(CodeGen *parent) {
-    CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-    codegen_set_out_name(child_gen, buf_create_from_str("c"));
+static const char *build_musl(CodeGen *parent, Stage2ProgressNode *progress_node) {
+    CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "c", progress_node);
 
     // When there is a src/<arch>/foo.* then it should substitute for src/foo.*
     // Even a .s file can substitute for a .c file.
@@ -1175,7 +1175,7 @@ static void add_mingwex_os_dep(CodeGen *parent, CodeGen *child_gen, const char *
     child_gen->c_source_files.append(c_file);
 }
 
-static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
+static const char *get_libc_crt_file(CodeGen *parent, const char *file, Stage2ProgressNode *progress_node) {
     if (parent->libc == nullptr && parent->zig_target->os == OsWindows) {
         if (strcmp(file, "crt2.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
@@ -1188,7 +1188,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             //c_file->args.append("-DUNICODE");
             //c_file->args.append("-D_UNICODE");
             //c_file->args.append("-DWPRFLAG=1");
-            return build_libc_object(parent, "crt2", c_file);
+            return build_libc_object(parent, "crt2", c_file, progress_node);
         } else if (strcmp(file, "dllcrt2.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = buf_ptr(buf_sprintf(
@@ -1196,10 +1196,9 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             mingw_add_cc_args(parent, c_file);
             c_file->args.append("-U__CRTDLL__");
             c_file->args.append("-D__MSVCRT__");
-            return build_libc_object(parent, "dllcrt2", c_file);
+            return build_libc_object(parent, "dllcrt2", c_file, progress_node);
         } else if (strcmp(file, "mingw32.lib") == 0) {
-            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-            codegen_set_out_name(child_gen, buf_create_from_str("mingw32"));
+            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "mingw32", progress_node);
 
             static const char *deps[] = {
                 "mingw" OS_SEP "crt" OS_SEP "crt0_c.c",
@@ -1256,8 +1255,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             codegen_build_and_link(child_gen);
             return buf_ptr(&child_gen->output_file_path);
         } else if (strcmp(file, "msvcrt-os.lib") == 0) {
-            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-            codegen_set_out_name(child_gen, buf_create_from_str("msvcrt-os"));
+            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "msvcrt-os", progress_node);
 
             for (size_t i = 0; i < array_length(msvcrt_common_src); i += 1) {
                 add_msvcrt_os_dep(parent, child_gen, msvcrt_common_src[i]);
@@ -1274,8 +1272,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             codegen_build_and_link(child_gen);
             return buf_ptr(&child_gen->output_file_path);
         } else if (strcmp(file, "mingwex.lib") == 0) {
-            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-            codegen_set_out_name(child_gen, buf_create_from_str("mingwex"));
+            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "mingwex", progress_node);
 
             for (size_t i = 0; i < array_length(mingwex_generic_src); i += 1) {
                 add_mingwex_os_dep(parent, child_gen, mingwex_generic_src[i]);
@@ -1318,7 +1315,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->args.append("-DASSEMBLER");
             c_file->args.append("-g");
             c_file->args.append("-Wa,--noexecstack");
-            return build_libc_object(parent, "crti", c_file);
+            return build_libc_object(parent, "crti", c_file, progress_node);
         } else if (strcmp(file, "crtn.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = glibc_start_asm_path(parent, "crtn.S");
@@ -1329,7 +1326,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->args.append("-DASSEMBLER");
             c_file->args.append("-g");
             c_file->args.append("-Wa,--noexecstack");
-            return build_libc_object(parent, "crtn", c_file);
+            return build_libc_object(parent, "crtn", c_file, progress_node);
         } else if (strcmp(file, "start.os") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = glibc_start_asm_path(parent, "start.S");
@@ -1347,7 +1344,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->args.append("-DASSEMBLER");
             c_file->args.append("-g");
             c_file->args.append("-Wa,--noexecstack");
-            return build_libc_object(parent, "start", c_file);
+            return build_libc_object(parent, "start", c_file, progress_node);
         } else if (strcmp(file, "abi-note.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = path_from_libc(parent, "glibc" OS_SEP "csu" OS_SEP "abi-note.S");
@@ -1360,19 +1357,17 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->args.append("-DASSEMBLER");
             c_file->args.append("-g");
             c_file->args.append("-Wa,--noexecstack");
-            return build_libc_object(parent, "abi-note", c_file);
+            return build_libc_object(parent, "abi-note", c_file, progress_node);
         } else if (strcmp(file, "Scrt1.o") == 0) {
-            const char *start_os = get_libc_crt_file(parent, "start.os");
-            const char *abi_note_o = get_libc_crt_file(parent, "abi-note.o");
-            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeObj, nullptr);
-            codegen_set_out_name(child_gen, buf_create_from_str("Scrt1"));
+            const char *start_os = get_libc_crt_file(parent, "start.os", progress_node);
+            const char *abi_note_o = get_libc_crt_file(parent, "abi-note.o", progress_node);
+            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeObj, nullptr, "Scrt1", progress_node);
             codegen_add_object(child_gen, buf_create_from_str(start_os));
             codegen_add_object(child_gen, buf_create_from_str(abi_note_o));
             codegen_build_and_link(child_gen);
             return buf_ptr(&child_gen->output_file_path);
         } else if (strcmp(file, "libc_nonshared.a") == 0) {
-            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
-            codegen_set_out_name(child_gen, buf_create_from_str("c_nonshared"));
+            CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr, "c_nonshared", progress_node);
             {
                 CFile *c_file = allocate<CFile>(1);
                 c_file->source_path = path_from_libc(parent, "glibc" OS_SEP "csu" OS_SEP "elf-init.c");
@@ -1401,7 +1396,8 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
                 c_file->args.append("-DPIC");
                 c_file->args.append("-DLIBC_NONSHARED=1");
                 c_file->args.append("-DTOP_NAMESPACE=glibc");
-                codegen_add_object(child_gen, buf_create_from_str(build_libc_object(parent, "elf-init", c_file)));
+                codegen_add_object(child_gen, buf_create_from_str(
+                            build_libc_object(parent, "elf-init", c_file, progress_node)));
             }
             static const struct {
                 const char *name;
@@ -1445,7 +1441,8 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
                 c_file->args.append("-DPIC");
                 c_file->args.append("-DLIBC_NONSHARED=1");
                 c_file->args.append("-DTOP_NAMESPACE=glibc");
-                codegen_add_object(child_gen, buf_create_from_str(build_libc_object(parent, deps[i].name, c_file)));
+                codegen_add_object(child_gen, buf_create_from_str(
+                            build_libc_object(parent, deps[i].name, c_file, progress_node)));
             }
             codegen_build_and_link(child_gen);
             return buf_ptr(&child_gen->output_file_path);
@@ -1458,20 +1455,20 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->source_path = musl_start_asm_path(parent, "crti.s");
             musl_add_cc_args(parent, c_file, false);
             c_file->args.append("-Qunused-arguments");
-            return build_libc_object(parent, "crti", c_file);
+            return build_libc_object(parent, "crti", c_file, progress_node);
         } else if (strcmp(file, "crtn.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = musl_start_asm_path(parent, "crtn.s");
             c_file->args.append("-Qunused-arguments");
             musl_add_cc_args(parent, c_file, false);
-            return build_libc_object(parent, "crtn", c_file);
+            return build_libc_object(parent, "crtn", c_file, progress_node);
         } else if (strcmp(file, "crt1.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = path_from_libc(parent, "musl" OS_SEP "crt" OS_SEP "crt1.c");
             musl_add_cc_args(parent, c_file, false);
             c_file->args.append("-fno-stack-protector");
             c_file->args.append("-DCRT");
-            return build_libc_object(parent, "crt1", c_file);
+            return build_libc_object(parent, "crt1", c_file, progress_node);
         } else if (strcmp(file, "Scrt1.o") == 0) {
             CFile *c_file = allocate<CFile>(1);
             c_file->source_path = path_from_libc(parent, "musl" OS_SEP "crt" OS_SEP "Scrt1.c");
@@ -1479,7 +1476,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
             c_file->args.append("-fPIC");
             c_file->args.append("-fno-stack-protector");
             c_file->args.append("-DCRT");
-            return build_libc_object(parent, "Scrt1", c_file);
+            return build_libc_object(parent, "Scrt1", c_file, progress_node);
         } else {
             zig_unreachable();
         }
@@ -1491,10 +1488,11 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file) {
     }
 }
 
-static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path, OutType child_out_type) {
-    CodeGen *child_gen = create_child_codegen(parent_gen, full_path, child_out_type,
-            parent_gen->libc);
-    codegen_set_out_name(child_gen, buf_create_from_str(aname));
+static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path, OutType child_out_type,
+        Stage2ProgressNode *progress_node)
+{
+    CodeGen *child_gen = create_child_codegen(parent_gen, full_path, child_out_type, parent_gen->libc, aname,
+            progress_node);
 
     // This is so that compiler_rt and libc.zig libraries know whether they
     // will eventually be linked with libc. They make different decisions
@@ -1511,18 +1509,18 @@ static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path,
     return &child_gen->output_file_path;
 }
 
-static Buf *build_compiler_rt(CodeGen *parent_gen, OutType child_out_type) {
+static Buf *build_compiler_rt(CodeGen *parent_gen, OutType child_out_type, Stage2ProgressNode *progress_node) {
     Buf *full_path = buf_alloc();
     os_path_join(parent_gen->zig_std_special_dir, buf_create_from_str("compiler_rt.zig"), full_path);
 
-    return build_a_raw(parent_gen, "compiler_rt", full_path, child_out_type);
+    return build_a_raw(parent_gen, "compiler_rt", full_path, child_out_type, progress_node);
 }
 
-static Buf *build_c(CodeGen *parent_gen, OutType child_out_type) {
+static Buf *build_c(CodeGen *parent_gen, OutType child_out_type, Stage2ProgressNode *progress_node) {
     Buf *full_path = buf_alloc();
     os_path_join(parent_gen->zig_std_special_dir, buf_create_from_str("c.zig"), full_path);
 
-    return build_a_raw(parent_gen, "c", full_path, child_out_type);
+    return build_a_raw(parent_gen, "c", full_path, child_out_type, progress_node);
 }
 
 static const char *get_darwin_arch_string(const ZigTarget *t) {
@@ -1616,7 +1614,7 @@ static void add_glibc_libs(LinkJob *lj) {
 
     Buf *artifact_dir;
     if ((err = glibc_build_dummies_and_maps(lj->codegen, glibc_abi, lj->codegen->zig_target,
-                    &artifact_dir, true)))
+                    &artifact_dir, true, lj->build_dep_prog_node)))
     {
         fprintf(stderr, "%s\n", err_str(err));
         exit(1);
@@ -1692,9 +1690,9 @@ static void construct_linker_job_elf(LinkJob *lj) {
         } else {
             crt1o = "Scrt1.o";
         }
-        lj->args.append(get_libc_crt_file(g, crt1o));
+        lj->args.append(get_libc_crt_file(g, crt1o, lj->build_dep_prog_node));
         if (target_libc_needs_crti_crtn(g->zig_target)) {
-            lj->args.append(get_libc_crt_file(g, "crti.o"));
+            lj->args.append(get_libc_crt_file(g, "crti.o", lj->build_dep_prog_node));
         }
     }
 
@@ -1759,11 +1757,11 @@ static void construct_linker_job_elf(LinkJob *lj) {
 
     if (!g->is_dummy_so && (g->out_type == OutTypeExe || is_dyn_lib)) {
         if (g->libc_link_lib == nullptr) {
-            Buf *libc_a_path = build_c(g, OutTypeLib);
+            Buf *libc_a_path = build_c(g, OutTypeLib, lj->build_dep_prog_node);
             lj->args.append(buf_ptr(libc_a_path));
         }
 
-        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib);
+        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib, lj->build_dep_prog_node);
         lj->args.append(buf_ptr(compiler_rt_o_path));
     }
 
@@ -1823,15 +1821,15 @@ static void construct_linker_job_elf(LinkJob *lj) {
             }
         } else if (target_is_glibc(g->zig_target)) {
             if (target_supports_libunwind(g->zig_target)) {
-                lj->args.append(build_libunwind(g));
+                lj->args.append(build_libunwind(g, lj->build_dep_prog_node));
             }
             add_glibc_libs(lj);
-            lj->args.append(get_libc_crt_file(g, "libc_nonshared.a"));
+            lj->args.append(get_libc_crt_file(g, "libc_nonshared.a", lj->build_dep_prog_node));
         } else if (target_is_musl(g->zig_target)) {
             if (target_supports_libunwind(g->zig_target)) {
-                lj->args.append(build_libunwind(g));
+                lj->args.append(build_libunwind(g, lj->build_dep_prog_node));
             }
-            lj->args.append(build_musl(g));
+            lj->args.append(build_musl(g, lj->build_dep_prog_node));
         } else {
             zig_unreachable();
         }
@@ -1840,9 +1838,9 @@ static void construct_linker_job_elf(LinkJob *lj) {
     // crt end
     if (lj->link_in_crt) {
         if (target_is_android(g->zig_target)) {
-            lj->args.append(get_libc_crt_file(g, "crtend_android.o"));
+            lj->args.append(get_libc_crt_file(g, "crtend_android.o", lj->build_dep_prog_node));
         } else if (target_libc_needs_crti_crtn(g->zig_target)) {
-            lj->args.append(get_libc_crt_file(g, "crtn.o"));
+            lj->args.append(get_libc_crt_file(g, "crtn.o", lj->build_dep_prog_node));
         }
     }
 
@@ -1887,10 +1885,10 @@ static void construct_linker_job_wasm(LinkJob *lj) {
     }
 
     if (g->out_type != OutTypeObj) {
-        Buf *libc_o_path = build_c(g, OutTypeObj);
+        Buf *libc_o_path = build_c(g, OutTypeObj, lj->build_dep_prog_node);
         lj->args.append(buf_ptr(libc_o_path));
 
-        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeObj);
+        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeObj, lj->build_dep_prog_node);
         lj->args.append(buf_ptr(compiler_rt_o_path));
     }
 }
@@ -2170,14 +2168,14 @@ static void add_mingw_link_args(LinkJob *lj, bool is_library) {
     }
 
     if (is_dll) {
-        lj->args.append(get_libc_crt_file(g, "dllcrt2.o"));
+        lj->args.append(get_libc_crt_file(g, "dllcrt2.o", lj->build_dep_prog_node));
     } else {
-        lj->args.append(get_libc_crt_file(g, "crt2.o"));
+        lj->args.append(get_libc_crt_file(g, "crt2.o", lj->build_dep_prog_node));
     }
 
-    lj->args.append(get_libc_crt_file(g, "mingw32.lib"));
-    lj->args.append(get_libc_crt_file(g, "mingwex.lib"));
-    lj->args.append(get_libc_crt_file(g, "msvcrt-os.lib"));
+    lj->args.append(get_libc_crt_file(g, "mingw32.lib", lj->build_dep_prog_node));
+    lj->args.append(get_libc_crt_file(g, "mingwex.lib", lj->build_dep_prog_node));
+    lj->args.append(get_libc_crt_file(g, "msvcrt-os.lib", lj->build_dep_prog_node));
 
     for (size_t def_i = 0; def_i < array_length(mingw_def_list); def_i += 1) {
         const char *name = mingw_def_list[def_i].name;
@@ -2319,12 +2317,12 @@ static void construct_linker_job_coff(LinkJob *lj) {
 
     if (g->out_type == OutTypeExe || (g->out_type == OutTypeLib && g->is_dynamic)) {
         if (g->libc_link_lib == nullptr && !g->is_dummy_so) {
-            Buf *libc_a_path = build_c(g, OutTypeLib);
+            Buf *libc_a_path = build_c(g, OutTypeLib, lj->build_dep_prog_node);
             lj->args.append(buf_ptr(libc_a_path));
         }
 
         // msvc compiler_rt is missing some stuff, so we still build it and rely on weak linkage
-        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib);
+        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib, lj->build_dep_prog_node);
         lj->args.append(buf_ptr(compiler_rt_o_path));
     }
 
@@ -2563,7 +2561,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
 
     // compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce
     if (g->out_type == OutTypeExe || is_dyn_lib) {
-        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib);
+        Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib, lj->build_dep_prog_node);
         lj->args.append(buf_ptr(compiler_rt_o_path));
     }
 
@@ -2621,16 +2619,22 @@ static void construct_linker_job(LinkJob *lj) {
     }
 }
 
-void zig_link_add_compiler_rt(CodeGen *g) {
-    Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeObj);
+void zig_link_add_compiler_rt(CodeGen *g, Stage2ProgressNode *progress_node) {
+    Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeObj, progress_node);
     g->link_objects.append(compiler_rt_o_path);
 }
 
 void codegen_link(CodeGen *g) {
     codegen_add_time_event(g, "Build Dependencies");
-
     LinkJob lj = {0};
 
+    {
+        const char *progress_name = "Build Dependencies";
+        lj.build_dep_prog_node = stage2_progress_start(g->progress_node,
+                progress_name, strlen(progress_name), 0);
+    }
+
+
     // even though we're calling LLD as a library it thinks the first
     // argument is its own exe name
     lj.args.append("lld");
@@ -2656,6 +2660,12 @@ void codegen_link(CodeGen *g) {
         }
         ZigLLVM_OSType os_type = get_llvm_os_type(g->zig_target->os);
         codegen_add_time_event(g, "LLVM Link");
+        {
+            const char *progress_name = "linking";
+            Stage2ProgressNode *child_progress_node = stage2_progress_start(g->progress_node,
+                    progress_name, strlen(progress_name), 0);
+            (void)child_progress_node;
+        }
         if (g->verbose_link) {
             fprintf(stderr, "ar rcs %s", buf_ptr(&g->output_file_path));
             for (size_t i = 0; i < file_names.length; i += 1) {
src/main.cpp
@@ -506,6 +506,8 @@ int main(int argc, char **argv) {
     ZigList<const char *> llvm_argv = {0};
     llvm_argv.append("zig (LLVM option parsing)");
 
+    Stage2ProgressNode *root_progress_node = stage2_progress_start_root(stage2_progress_create(), "", 0, 0);
+
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
         Buf zig_exe_path_buf = BUF_INIT;
         if ((err = os_self_exe_path(&zig_exe_path_buf))) {
@@ -589,7 +591,7 @@ int main(int argc, char **argv) {
         }
 
         CodeGen *g = codegen_create(main_pkg_path, build_runner_path, &target, OutTypeExe,
-                BuildModeDebug, override_lib_dir, nullptr, &full_cache_dir, false);
+                BuildModeDebug, override_lib_dir, nullptr, &full_cache_dir, false, root_progress_node);
         g->valgrind_support = valgrind_support;
         g->enable_time_report = timing_info;
         codegen_set_out_name(g, buf_create_from_str("build"));
@@ -1034,17 +1036,19 @@ int main(int argc, char **argv) {
             ZigLibCInstallation libc;
             if ((err = zig_libc_parse(&libc, buf_create_from_str(in_file), &target, true)))
                 return EXIT_FAILURE;
+            stage2_progress_end(root_progress_node);
             return EXIT_SUCCESS;
         }
         ZigLibCInstallation libc;
         if ((err = zig_libc_find_native(&libc, true)))
             return EXIT_FAILURE;
         zig_libc_render(&libc, stdout);
+        stage2_progress_end(root_progress_node);
         return EXIT_SUCCESS;
     }
     case CmdBuiltin: {
         CodeGen *g = codegen_create(main_pkg_path, nullptr, &target,
-                out_type, build_mode, override_lib_dir, nullptr, nullptr, false);
+                out_type, build_mode, override_lib_dir, nullptr, nullptr, false, root_progress_node);
         codegen_set_strip(g, strip);
         for (size_t i = 0; i < link_libs.length; i += 1) {
             LinkLib *link_lib = codegen_add_link_lib(g, buf_create_from_str(link_libs.at(i)));
@@ -1060,6 +1064,7 @@ int main(int argc, char **argv) {
             fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
             return EXIT_FAILURE;
         }
+        stage2_progress_end(root_progress_node);
         return EXIT_SUCCESS;
     }
     case CmdRun:
@@ -1148,7 +1153,7 @@ int main(int argc, char **argv) {
                 cache_dir_buf = buf_create_from_str(cache_dir);
             }
             CodeGen *g = codegen_create(main_pkg_path, zig_root_source_file, &target, out_type, build_mode,
-                    override_lib_dir, libc, cache_dir_buf, cmd == CmdTest);
+                    override_lib_dir, libc, cache_dir_buf, cmd == CmdTest, root_progress_node);
             if (llvm_argv.length >= 2) codegen_set_llvm_argv(g, llvm_argv.items + 1, llvm_argv.length - 2);
             g->valgrind_support = valgrind_support;
             g->want_pic = want_pic;
@@ -1276,6 +1281,7 @@ int main(int argc, char **argv) {
                         if (printf("%s\n", buf_ptr(&g->output_file_path)) < 0)
                             return EXIT_FAILURE;
                     }
+                    stage2_progress_end(root_progress_node);
                     return EXIT_SUCCESS;
                 } else {
                     zig_unreachable();
@@ -1284,6 +1290,7 @@ int main(int argc, char **argv) {
                 codegen_translate_c(g, in_file_buf, stdout, cmd == CmdTranslateCUserland);
                 if (timing_info)
                     codegen_print_timing_report(g, stderr);
+                stage2_progress_end(root_progress_node);
                 return EXIT_SUCCESS;
             } else if (cmd == CmdTest) {
                 codegen_set_emit_file_type(g, emit_file_type);
@@ -1338,6 +1345,7 @@ int main(int argc, char **argv) {
                     fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n");
                     fprintf(stderr, "%s\n", buf_ptr(test_exe_path));
                 }
+                stage2_progress_end(root_progress_node);
                 return (term.how == TerminationIdClean) ? term.code : -1;
             } else {
                 zig_unreachable();
src/userland.cpp
@@ -59,3 +59,31 @@ stage2_DepNextResult stage2_DepTokenizer_next(stage2_DepTokenizer *self) {
     const char *msg = "stage0 called stage2_DepTokenizer_next";
     stage2_panic(msg, strlen(msg));
 }
+
+
+struct Stage2Progress {
+    int trash;
+};
+
+struct Stage2ProgressNode {
+    int trash;
+};
+
+Stage2Progress *stage2_progress_create(void) {
+    return nullptr;
+}
+
+void stage2_progress_destroy(Stage2Progress *progress) {}
+
+Stage2ProgressNode *stage2_progress_start_root(Stage2Progress *progress,
+        const char *name_ptr, size_t name_len, size_t estimated_total_items)
+{
+    return nullptr;
+}
+Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node,
+        const char *name_ptr, size_t name_len, size_t estimated_total_items)
+{
+    return nullptr;
+}
+void stage2_progress_end(Stage2ProgressNode *node) {}
+void stage2_progress_complete_one(Stage2ProgressNode *node) {}
src/userland.h
@@ -156,4 +156,23 @@ ZIG_EXTERN_C void stage2_DepTokenizer_deinit(stage2_DepTokenizer *self);
 // ABI warning
 ZIG_EXTERN_C stage2_DepNextResult stage2_DepTokenizer_next(stage2_DepTokenizer *self);
 
+// ABI warning
+struct Stage2Progress;
+// ABI warning
+struct Stage2ProgressNode;
+// ABI warning
+ZIG_EXTERN_C Stage2Progress *stage2_progress_create(void);
+// ABI warning
+ZIG_EXTERN_C void stage2_progress_destroy(Stage2Progress *progress);
+// ABI warning
+ZIG_EXTERN_C Stage2ProgressNode *stage2_progress_start_root(Stage2Progress *progress,
+        const char *name_ptr, size_t name_len, size_t estimated_total_items);
+// ABI warning
+ZIG_EXTERN_C Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node,
+        const char *name_ptr, size_t name_len, size_t estimated_total_items);
+// ABI warning
+ZIG_EXTERN_C void stage2_progress_end(Stage2ProgressNode *node);
+// ABI warning
+ZIG_EXTERN_C void stage2_progress_complete_one(Stage2ProgressNode *node);
+
 #endif
src-self-hosted/stage1.zig
@@ -456,3 +456,52 @@ export fn stage2_attach_segfault_handler() void {
         std.debug.attachSegfaultHandler();
     }
 }
+
+// ABI warning
+export fn stage2_progress_create() *std.Progress {
+    const ptr = std.heap.c_allocator.create(std.Progress) catch @panic("out of memory");
+    ptr.* = std.Progress{};
+    return ptr;
+}
+
+// ABI warning
+export fn stage2_progress_destroy(progress: *std.Progress) void {
+    std.heap.c_allocator.destroy(progress);
+}
+
+// ABI warning
+export fn stage2_progress_start_root(progress: *std.Progress, name_ptr: [*]const u8, name_len: usize, estimated_total_items: usize) *std.Progress.Node {
+    return progress.start(
+        name_ptr[0..name_len],
+        if (estimated_total_items == 0) null else estimated_total_items,
+    ) catch @panic("timer unsupported");
+}
+
+// ABI warning
+export fn stage2_progress_start(
+    node: *std.Progress.Node,
+    name_ptr: [*]const u8,
+    name_len: usize,
+    estimated_total_items: usize,
+) *std.Progress.Node {
+    const child_node = std.heap.c_allocator.create(std.Progress.Node) catch @panic("out of memory");
+    child_node.* = node.start(
+        name_ptr[0..name_len],
+        if (estimated_total_items == 0) null else estimated_total_items,
+    );
+    child_node.activate();
+    return child_node;
+}
+
+// ABI warning
+export fn stage2_progress_end(node: *std.Progress.Node) void {
+    node.end();
+    if (&node.context.root != node) {
+        std.heap.c_allocator.destroy(node);
+    }
+}
+
+// ABI warning
+export fn stage2_progress_complete_one(node: *std.Progress.Node) void {
+    node.completeOne();
+}