Commit aa6ef10cc6

Andrew Kelley <andrew@ziglang.org>
2020-12-19 05:51:18
std.Progress: make the API thread-safe
We generally get away with atomic primitives, however a lock is required around the refresh function since it traverses the Node graph, and we need to be sure no references to Nodes remain after end() is called.
1 parent b2f8631
lib/std/special/test_runner.zig
@@ -36,7 +36,7 @@ pub fn main() anyerror!void {
         }
         std.testing.log_level = .warn;
 
-        var test_node = root_node.start(test_fn.name, null);
+        var test_node = root_node.start(test_fn.name, 0);
         test_node.activate();
         progress.refresh();
         if (progress.terminal == null) {
lib/std/Progress.zig
@@ -3,263 +3,298 @@
 // This file is part of [zig](https://ziglang.org/), which is MIT licensed.
 // The MIT license requires this copyright notice to be included in all copies
 // and substantial portions of the software.
+
+//! This API non-allocating, non-fallible, and thread-safe.
+//! 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`
+
 const std = @import("std");
 const windows = std.os.windows;
 const testing = std.testing;
 const assert = std.debug.assert;
+const Progress = @This();
 
-/// 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,
-
-    /// Whether the terminal supports ANSI escape codes.
-    supports_ansi_escape_codes: bool = false,
-
-    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,
-
-    /// How many nanoseconds between writing updates to the terminal.
-    refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
-
-    /// How many nanoseconds to keep the output hidden
-    initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
-
-    done: bool = true,
-
-    /// 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,
-
-    /// 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,
-            };
-        }
+/// `null` if the current node (and its children) should
+/// not print on update()
+terminal: ?std.fs.File = undefined,
 
-        /// 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();
-        }
+/// Whether the terminal supports ANSI escape codes.
+supports_ansi_escape_codes: 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();
-            }
-        }
+root: Node = undefined,
 
-        /// 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;
-        }
-    };
+/// 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,
+
+/// How many nanoseconds between writing updates to the terminal.
+refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
+
+/// How many nanoseconds to keep the output hidden
+initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
+
+done: bool = true,
+
+/// Protects the `refresh` function, as well as `node.recently_updated_child`.
+/// Without this, callsites would call `Node.end` and then free `Node` memory
+/// while it was still being accessed by the `refresh` function.
+update_lock: std.Mutex = .{},
+
+/// 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,
 
-    /// Create a new progress node.
+/// 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,
+    name: []const u8,
+    /// Must be handled atomically to be thread-safe.
+    recently_updated_child: ?*Node = null,
+    /// Must be handled atomically to be thread-safe. 0 means null.
+    unprotected_estimated_total_items: usize,
+    /// Must be handled atomically to be thread-safe.
+    unprotected_completed_items: usize,
+
+    /// Create a new child progress node. Thread-safe.
     /// 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 {
-        const stderr = std.io.getStdErr();
-        self.terminal = null;
-        if (stderr.supportsAnsiEscapeCodes()) {
-            self.terminal = stderr;
-            self.supports_ansi_escape_codes = true;
-        } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
-            self.terminal = stderr;
-        }
-        self.root = Node{
-            .context = self,
-            .parent = null,
-            .completed_items = 0,
+    /// 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.
+    /// Passing 0 for `estimated_total_items` means unknown.
+    pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node {
+        return Node{
+            .context = self.context,
+            .parent = self,
             .name = name,
-            .estimated_total_items = estimated_total_items,
+            .unprotected_estimated_total_items = estimated_total_items,
+            .unprotected_completed_items = 0,
         };
-        self.columns_written = 0;
-        self.prev_refresh_timestamp = 0;
-        self.timer = try std.time.Timer.start();
-        self.done = false;
-        return &self.root;
     }
 
-    /// 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();
+    /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe.
+    pub fn completeOne(self: *Node) void {
+        self.activate();
+        _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .Monotonic);
+        self.context.maybeRefresh();
     }
 
-    /// 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) {
-            // restore the cursor position by moving the cursor
-            // `columns_written` cells to the left, then clear the rest of the
-            // line
-            if (self.supports_ansi_escape_codes) {
-                end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len;
-                end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
-            } else if (std.builtin.os.tag == .windows) winapi: {
-                var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
-                if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
-                    unreachable;
-
-                var cursor_pos = windows.COORD{
-                    .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
-                    .Y = info.dwCursorPosition.Y,
-                };
-
-                if (cursor_pos.X < 0)
-                    cursor_pos.X = 0;
-
-                const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
-
-                var written: windows.DWORD = undefined;
-                if (windows.kernel32.FillConsoleOutputAttribute(
-                    file.handle,
-                    info.wAttributes,
-                    fill_chars,
-                    cursor_pos,
-                    &written,
-                ) != windows.TRUE) {
-                    // Stop trying to write to this file.
-                    self.terminal = null;
-                    break :winapi;
-                }
-                if (windows.kernel32.FillConsoleOutputCharacterA(
-                    file.handle,
-                    ' ',
-                    fill_chars,
-                    cursor_pos,
-                    &written,
-                ) != windows.TRUE) unreachable;
-
-                if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
-                    unreachable;
-            } else unreachable;
-
-            self.columns_written = 0;
+    /// Finish a started `Node`. Thread-safe.
+    pub fn end(self: *Node) void {
+        self.context.maybeRefresh();
+        if (self.parent) |parent| {
+            {
+                const held = self.context.update_lock.acquire();
+                defer held.release();
+                _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .Monotonic, .Monotonic);
+            }
+            parent.completeOne();
+        } else {
+            self.context.done = true;
+            self.context.refresh();
         }
+    }
 
-        if (!self.done) {
-            var need_ellipse = false;
-            var maybe_node: ?*Node = &self.root;
-            while (maybe_node) |node| {
-                if (need_ellipse) {
-                    self.bufWrite(&end, "... ", .{});
-                }
-                need_ellipse = false;
-                if (node.name.len != 0 or node.estimated_total_items != null) {
-                    if (node.name.len != 0) {
-                        self.bufWrite(&end, "{}", .{node.name});
-                        need_ellipse = true;
-                    }
-                    if (node.estimated_total_items) |total| {
-                        if (need_ellipse) self.bufWrite(&end, " ", .{});
-                        self.bufWrite(&end, "[{}/{}] ", .{ node.completed_items + 1, total });
-                        need_ellipse = false;
-                    } else if (node.completed_items != 0) {
-                        if (need_ellipse) self.bufWrite(&end, " ", .{});
-                        self.bufWrite(&end, "[{}] ", .{node.completed_items + 1});
-                        need_ellipse = false;
-                    }
-                }
-                maybe_node = node.recently_updated_child;
-            }
-            if (need_ellipse) {
-                self.bufWrite(&end, "... ", .{});
-            }
+    /// Tell the parent node that this node is actively being worked on. Thread-safe.
+    pub fn activate(self: *Node) void {
+        if (self.parent) |parent| {
+            @atomicStore(?*Node, &parent.recently_updated_child, self, .Monotonic);
         }
+    }
 
-        _ = 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();
+    /// Thread-safe. 0 means unknown.
+    pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
+        @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
     }
 
-    pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
-        const file = self.terminal orelse return;
-        self.refresh();
-        file.outStream().print(format, args) catch {
-            self.terminal = null;
-            return;
-        };
+    /// Thread-safe.
+    pub fn setCompletedItems(self: *Node, completed_items: usize) void {
+        @atomicStore(usize, &self.unprotected_completed_items, completed_items, .Monotonic);
+    }
+};
+
+/// 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.
+/// `estimated_total_items` value of 0 means unknown.
+pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*Node {
+    const stderr = std.io.getStdErr();
+    self.terminal = null;
+    if (stderr.supportsAnsiEscapeCodes()) {
+        self.terminal = stderr;
+        self.supports_ansi_escape_codes = true;
+    } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
+        self.terminal = stderr;
+    }
+    self.root = Node{
+        .context = self,
+        .parent = null,
+        .name = name,
+        .unprotected_estimated_total_items = estimated_total_items,
+        .unprotected_completed_items = 0,
+    };
+    self.columns_written = 0;
+    self.prev_refresh_timestamp = 0;
+    self.timer = try std.time.Timer.start();
+    self.done = false;
+    return &self.root;
+}
+
+/// Updates the terminal if enough time has passed since last update. Thread-safe.
+pub fn maybeRefresh(self: *Progress) void {
+    const now = self.timer.read();
+    if (now < self.initial_delay_ns) return;
+    const held = self.update_lock.tryAcquire() orelse return;
+    defer held.release();
+    if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
+    return self.refreshWithHeldLock();
+}
+
+/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
+pub fn refresh(self: *Progress) void {
+    const held = self.update_lock.tryAcquire() orelse return;
+    defer held.release();
+
+    return self.refreshWithHeldLock();
+}
+
+fn refreshWithHeldLock(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) {
+        // restore the cursor position by moving the cursor
+        // `columns_written` cells to the left, then clear the rest of the
+        // line
+        if (self.supports_ansi_escape_codes) {
+            end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
+            end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
+        } else if (std.builtin.os.tag == .windows) winapi: {
+            var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
+            if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
+                unreachable;
+
+            var cursor_pos = windows.COORD{
+                .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
+                .Y = info.dwCursorPosition.Y,
+            };
+
+            if (cursor_pos.X < 0)
+                cursor_pos.X = 0;
+
+            const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
+
+            var written: windows.DWORD = undefined;
+            if (windows.kernel32.FillConsoleOutputAttribute(
+                file.handle,
+                info.wAttributes,
+                fill_chars,
+                cursor_pos,
+                &written,
+            ) != windows.TRUE) {
+                // Stop trying to write to this file.
+                self.terminal = null;
+                break :winapi;
+            }
+            if (windows.kernel32.FillConsoleOutputCharacterA(
+                file.handle,
+                ' ',
+                fill_chars,
+                cursor_pos,
+                &written,
+            ) != windows.TRUE) unreachable;
+
+            if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
+                unreachable;
+        } else unreachable;
+
         self.columns_written = 0;
     }
 
-    fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) 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.NoSpaceLeft => {
-                self.columns_written += self.output_buffer.len - end.*;
-                end.* = self.output_buffer.len;
-            },
+    if (!self.done) {
+        var need_ellipse = false;
+        var maybe_node: ?*Node = &self.root;
+        while (maybe_node) |node| {
+            if (need_ellipse) {
+                self.bufWrite(&end, "... ", .{});
+            }
+            need_ellipse = false;
+            const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic);
+            const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic);
+            if (node.name.len != 0 or eti > 0) {
+                if (node.name.len != 0) {
+                    self.bufWrite(&end, "{s}", .{node.name});
+                    need_ellipse = true;
+                }
+                if (eti > 0) {
+                    if (need_ellipse) self.bufWrite(&end, " ", .{});
+                    self.bufWrite(&end, "[{d}/{d}] ", .{ completed_items + 1, eti });
+                    need_ellipse = false;
+                } else if (completed_items != 0) {
+                    if (need_ellipse) self.bufWrite(&end, " ", .{});
+                    self.bufWrite(&end, "[{d}] ", .{completed_items + 1});
+                    need_ellipse = false;
+                }
+            }
+            maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Monotonic);
         }
-        const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 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;
+        if (need_ellipse) {
+            self.bufWrite(&end, "... ", .{});
         }
     }
-};
+
+    _ = 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();
+}
+
+pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
+    const file = self.terminal orelse return;
+    self.refresh();
+    file.outStream().print(format, args) catch {
+        self.terminal = null;
+        return;
+    };
+    self.columns_written = 0;
+}
+
+fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) 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.NoSpaceLeft => {
+            self.columns_written += self.output_buffer.len - end.*;
+            end.* = self.output_buffer.len;
+        },
+    }
+    const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 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 disable = true;
@@ -300,7 +335,7 @@ test "basic functionality" {
         std.time.sleep(5 * std.time.ns_per_ms);
     }
     {
-        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);
+        var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0);
         node.activate();
         std.time.sleep(10 * std.time.ns_per_ms);
         progress.refresh();
lib/std/std.zig
@@ -29,7 +29,7 @@ pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayE
 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 Progress = @import("progress.zig").Progress;
+pub const Progress = @import("Progress.zig");
 pub const ResetEvent = @import("reset_event.zig").ResetEvent;
 pub const SemanticVersion = @import("SemanticVersion.zig");
 pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
src/Compilation.zig
@@ -1378,7 +1378,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
 
 pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
     var progress: std.Progress = .{};
-    var main_progress_node = try progress.start("", null);
+    var main_progress_node = try progress.start("", 0);
     defer main_progress_node.end();
     if (self.color == .off) progress.terminal = null;
 
@@ -1811,7 +1811,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
     const c_source_basename = std.fs.path.basename(c_object.src.src_path);
 
     c_comp_progress_node.activate();
-    var child_progress_node = c_comp_progress_node.start(c_source_basename, null);
+    var child_progress_node = c_comp_progress_node.start(c_source_basename, 0);
     child_progress_node.activate();
     defer child_progress_node.end();
 
src/stage1.zig
@@ -293,7 +293,7 @@ export fn stage2_progress_start_root(
 ) *std.Progress.Node {
     return progress.start(
         name_ptr[0..name_len],
-        if (estimated_total_items == 0) null else estimated_total_items,
+        estimated_total_items,
     ) catch @panic("timer unsupported");
 }
 
@@ -312,7 +312,7 @@ export fn stage2_progress_start(
     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,
+        estimated_total_items,
     );
     child_node.activate();
     return child_node;
@@ -333,8 +333,8 @@ export fn stage2_progress_complete_one(node: *std.Progress.Node) void {
 
 // ABI warning
 export fn stage2_progress_update_node(node: *std.Progress.Node, done_count: usize, total_count: usize) void {
-    node.completed_items = done_count;
-    node.estimated_total_items = total_count;
+    node.setCompletedItems(done_count);
+    node.setEstimatedTotalItems(total_count);
     node.activate();
     node.context.maybeRefresh();
 }
CMakeLists.txt
@@ -410,7 +410,7 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
-    "${CMAKE_SOURCE_DIR}/lib/std/progress.zig"
+    "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"