From 1273782b5a6fbc71253b73566bc37a4c0244488c Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Thu, 28 Aug 2025 19:45:54 +0200 Subject: [PATCH 01/18] Implemented strace cache system. --- nob.h | 430 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 429 insertions(+), 1 deletion(-) diff --git a/nob.h b/nob.h index 737f61f..281e4e5 100644 --- a/nob.h +++ b/nob.h @@ -390,12 +390,15 @@ typedef struct { size_t capacity; } Nob_Cmd; +typedef struct Nob_Strace_Cache Nob_Strace_Cache; // Options for nob_cmd_run_opt() function. typedef struct { // Run the command asynchronously appending its Nob_Proc to the provided Nob_Procs array Nob_Procs *async; // Maximum processes allowed in the .async list. Zero implies nob_nprocs(). size_t max_procs; + // Caching system that uses strace to determin what files are input and outputs to the command (Only on linux.) + Nob_Strace_Cache *strace_cache; // Redirect stdin to file const char *stdin_path; // Redirect stdout to file @@ -667,6 +670,7 @@ typedef struct { NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv); NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); +NOBDEF Nob_String_View nob_sv_chop_by_delim_sv(Nob_String_View *sv, Nob_String_View delim); NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n); NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv); NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv); @@ -674,6 +678,7 @@ NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv); NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b); NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr); NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix); +NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value); NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr); NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View @@ -690,6 +695,57 @@ NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); // String_View name = ...; // printf("Name: "SV_Fmt"\n", SV_Arg(name)); +typedef struct Nob_String_Builders { + Nob_String_Builder *items; + size_t count; + size_t capacity; + + size_t alloc_count; +} Nob_String_Builders; + +NOBDEF void nob_string_builders_push(Nob_String_Builders *sbs, Nob_String_View sv); + +typedef struct Nob_Strace_Cache_Indexies { + int *items; + size_t count; + size_t capacity; +} Nob_Strace_Cache_Indexies; + +typedef struct Nob_Strace_Cache_Node { + char *argv; + Nob_Strace_Cache_Indexies children; + Nob_String_Builders input_files; + Nob_String_Builders output_files; +} Nob_Strace_Cache_Node; + +typedef struct Nob_Strace_Cache_Nodes { + Nob_Strace_Cache_Node *items; + size_t count; + size_t capacity; +} Nob_Strace_Cache_Nodes; + +struct Nob_Strace_Cache { + Nob_Strace_Cache_Nodes nodes; + Nob_Strace_Cache_Indexies roots; + Nob_Cmd temp_cmd; // Don't worry about it... + Nob_String_Builder temp_sb; // Don't worry about it... + + const char *file_path; // File that will be used to read and write collected information for caching + bool is_loaded; +}; + +static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd); +static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup); +static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output); +static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); +static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files); +static bool nob_strace_cache_read(Nob_Strace_Cache *cache); +static bool nob_strace_cache_write_rec(Nob_Strace_Cache cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots); +static bool nob_strace_cache_write(Nob_Strace_Cache cache); + +bool nob_cmd_cache_run(Nob_Cmd *cmd, Nob_Strace_Cache *cache); +void nob_strace_cache_free(Nob_Strace_Cache cache); + // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, // it is recommended to `#define NOB_NO_MINIRENT` if it causes problems for you. @@ -1034,6 +1090,48 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1; +#if defined(_WIN32) + // TODO: strace cache Windows + opt.strace_cache = NULL; +#endif + + if (opt.strace_cache) { + Nob_Strace_Cache *cache = opt.strace_cache; + if (opt.async) NOB_TODO("Strace cache and async is not implemented."); + if (opt.stdin_path) NOB_TODO("Strace cache and stdin_path is not implemented."); + if (opt.stdout_path) NOB_TODO("Strace cache and stdout_path is not implemented."); + if (opt.stderr_path) NOB_TODO("Strace cache and stderr_path is not implemented."); + + if (false == cache->is_loaded) { + if (false == nob_strace_cache_read(cache)) nob_return_defer(false); + } + cache->temp_cmd.count = 0; + if (true == nob_cmd_strace_is_cached(*cache, *cmd)) { + cache->temp_sb.count = 0; + nob_cmd_render(*cmd, &cache->temp_sb); + nob_log(NOB_INFO, "CACHED: %.*s", (int)cache->temp_sb.count, cache->temp_sb.items); + nob_return_defer(true); + } + cache->temp_cmd.count = 0; + // TODO: Add -o [Output trace into a file] flag. It's not that easy becase it will create out-file. foreach pid that is created by the command. + // So that would imply that we need to read multiple files in a directory... + // Folow forks Only trace openat syscalls Don't trace failed syscalls + nob_cmd_append(&cache->temp_cmd, "strace", "-ff" , "-e", "trace=openat" , "-z" ); + for (size_t i = 0; i < cmd->count; ++i) { + nob_cmd_append(&cache->temp_cmd, cmd->items[i]); + } + + const char *strace_path = "strace_err"; + if (false == nob_cmd_run(&cache->temp_cmd, .stderr_path = strace_path)) { + nob_log(NOB_ERROR, "Failed to run strace: %s", strerror(errno)); + nob_return_defer(false); + } + + Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, *cmd, &cache->roots, true); + nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb); + nob_return_defer(true); + } + if (opt.async && max_procs > 0) { while (opt.async->count >= max_procs) { for (size_t i = 0; i < opt.async->count; ++i) { @@ -1175,7 +1273,7 @@ static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, if (fderr) { if (dup2(*fderr, STDERR_FILENO) < 0) { - nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); + nob_log(NOB_ERROR, "Dick Could not setup stderr %d for child process: %s", *fderr, strerror(errno)); exit(1); } } @@ -1932,6 +2030,22 @@ NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) return n; } +void nob_string_builders_push(Nob_String_Builders *sbs, Nob_String_View sv) +{ + // TODO: Handle situations where sv is already in sbs... + if (sbs->count == sbs->alloc_count++) { + Nob_String_Builder new_sb = { 0 }; + nob_sb_appendf(&new_sb, "%.*s", (int)sv.count, sv.data); + nob_sb_append_null(&new_sb); + nob_da_append(sbs, new_sb); + } else { + Nob_String_Builder *old_sb = &sbs->items[sbs->count++]; + old_sb->count = 0; + nob_sb_appendf(old_sb, "%.*s", (int)sv.count, sv.data); + nob_sb_append_null(old_sb); + } +} + NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) { size_t i = 0; @@ -1952,6 +2066,40 @@ NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) return result; } +NOBDEF Nob_String_View nob_sv_chop_by_delim_sv(Nob_String_View *sv, Nob_String_View delim) +{ + if (delim.count == 0) return nob_sv_from_parts(sv->data, 0); + + size_t i = 0; + + if (sv->count >= delim.count) { + for (; i < sv->count + 1 - delim.count; ++i) { + bool is_match = true; + for (size_t j = 0; j < delim.count; ++j) { + if (sv->data[i + j] != delim.data[j]) { + is_match = false; + break; + } + } + + if (is_match) break; + } + } + Nob_String_View result = nob_sv_from_parts(sv->data, i); + + i += delim.count - 1; + + if (i < sv->count) { + sv->count -= i + 1; + sv->data += i + 1; + } else { + sv->count -= i; + sv->data += i; + } + + return result; +} + NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n) { if (n > sv->count) { @@ -2035,6 +2183,286 @@ NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_pref return false; } +NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value) +{ + for (size_t i = 0; i < sv.count + 1 - value.count; ++i) { + bool is_match = true; + for (size_t j = 0; j < value.count; ++j) { + if (sv.data[i + j] != value.data[j]) { + is_match = false; + break; + } + } + + if (is_match) return true; + } + return false; +} + +NOBDEF void nob_strace_cache_finish(Nob_Strace_Cache cache) +{ + nob_strace_cache_write(cache); + nob_da_free(cache.roots); + nob_cmd_free(cache.temp_cmd); + nob_sb_free(cache.temp_sb); + for (size_t i = 0; i < cache.nodes.count; ++i) { + Nob_Strace_Cache_Node node = cache.nodes.items[i]; + nob_da_free(node.children); + for (size_t j = 0; j < node.input_files.count; ++j) nob_sb_free(node.input_files.items[j]); + for (size_t j = 0; j < node.output_files.count; ++j) nob_sb_free(node.output_files.items[j]); + free(node.argv); + nob_da_free(node.input_files); + nob_da_free(node.output_files); + } + nob_da_free(cache.nodes); +} + +static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd) +{ + int cmd_index = 0; + int node_index = -1; + + for (size_t i = 0; i < cache.roots.count; ++i) { + int cur_node_index = cache.roots.items[i]; + Nob_Strace_Cache_Node node = cache.nodes.items[cur_node_index]; + if (strcmp(cmd.items[0], node.argv) == 0) { + node_index = cur_node_index; + } + } + ++cmd_index; + + if (node_index < 0) return false; + Nob_Strace_Cache_Node node = cache.nodes.items[node_index]; + while (cmd_index < (int)cmd.count) { + Nob_Strace_Cache_Node child_node; + int child_index; + for (size_t i = 0; i < node.children.count; ++i) { + child_index = node.children.items[i]; + child_node = cache.nodes.items[child_index]; + if (strcmp(cmd.items[cmd_index], child_node.argv) == 0) goto found; + } + return false; + +found: + node = child_node; + node_index = child_index; + ++cmd_index; + } + + cache.temp_cmd.count = 0; + for (size_t i = 0; i < node.input_files.count; ++i) { + nob_cmd_append(&cache.temp_cmd, node.input_files.items[i].items); + } + + for (size_t i = 0; i < node.output_files.count; ++i) { + if (nob_needs_rebuild(node.output_files.items[i].items, cache.temp_cmd.items, cache.temp_cmd.count)) return false; + } + + return true; +} + +static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup) +{ + assert(cmd.count != 0); + + int node_index = -1; + + for (size_t i = 0; i < indexies->count; ++i) { + int cur_node_index = indexies->items[i]; + Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; + if (strcmp(cmd.items[0], node.argv) == 0) { + node_index = cur_node_index; + } + } + + Nob_Strace_Cache_Node *next = NULL; + if (node_index == -1) { + Nob_Strace_Cache_Node new_node = { + .argv = dup ? strdup(cmd.items[0]) : (char*)cmd.items[0], + }; + nob_da_append(&cache->nodes, new_node); + nob_da_append(indexies, cache->nodes.count - 1); + next = &nob_da_last(&cache->nodes); + } else { + next = &cache->nodes.items[node_index]; + } + + if (cmd.count == 1) return next; + + ++cmd.items; + --cmd.count; + + return nob_cmd_strace_cache_node(cache, cmd, &next->children, dup); +} + +static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output) +{ + if (!nob_read_entire_file(strace_output_path, strace_output)) return false; + + Nob_String_View iter = nob_sb_to_sv(*strace_output); + Nob_String_View openat_delim = nob_sv_from_cstr("openat("); + Nob_String_View _ = { 0 }; + (void)_; + + while (iter.count > 0) { + _ = nob_sv_chop_by_delim_sv(&iter, openat_delim); + _ = nob_sv_chop_by_delim(&iter, '"'); + Nob_String_View path = nob_sv_chop_by_delim(&iter, '"'); + if (path.count == 0) break; + + if (nob_sv_starts_with(path, nob_sv_from_cstr("/usr/"))) continue; // SKIP system files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/etc/"))) continue; // SKIP etc files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/tmp/"))) continue; // SKIP tmp files... + // TODO: Think about adding a flag to enable system/etc/tmp files + + Nob_String_View rw_args = nob_sv_chop_by_delim(&iter, ')'); + if (rw_args.count == 0) break; + + bool is_read = nob_sv_contains(rw_args, nob_sv_from_cstr("O_RDONLY")); + bool is_write = nob_sv_contains(rw_args, nob_sv_from_cstr("O_RDWR")) || + nob_sv_contains(rw_args, nob_sv_from_cstr("O_WRONLY")); + + if (is_write) { + nob_string_builders_push(output_files, path); + } else if (is_read) { + nob_string_builders_push(input_files, path); + } else { + nob_log(NOB_WARNING, "Unknown rw_args(%zu): `%.*s`", rw_args.count, (int)rw_args.count, rw_args.data); + return false; + } + } + + return true; +} + +static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd) +{ + // TODO: Handle spaces.. + sv = nob_sv_trim_left(sv); + while (sv.count > 0) + { + Nob_String_View arg = nob_sv_chop_by_delim(&sv, ' '); + char *arg_dup = strdup(nob_temp_sprintf("%.*s", (int)arg.count, arg.data)); + nob_cmd_append(cmd, arg_dup); + sv = nob_sv_trim_left(sv); + } + return true; +} + +static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files) +{ + // TODO: Handle spaces.. + sv = nob_sv_trim_left(sv); + while (sv.count > 0) + { + Nob_String_View file_view = nob_sv_chop_by_delim(&sv, ' '); + Nob_String_View arg = nob_sv_from_cstr(nob_temp_sprintf("%.*s", (int)file_view.count, file_view.data)); + nob_string_builders_push(files, arg); + sv = nob_sv_trim_left(sv); + } + return true; +} + +static bool nob_strace_cache_read(Nob_Strace_Cache *cache) +{ + NOB_ASSERT(cache->is_loaded == false); + cache->is_loaded = true; + + if (NULL == cache->file_path) return true; + + int exists = nob_file_exists(cache->file_path); + if (exists == -1) return false; + + // File doesn't exist but that is ok because, maybe, it's the first time building. + // And we don't need to do anything else. + if (exists == 0) { + nob_log(NOB_INFO, "Cache Cold"); + return true; + } + + if (false == nob_read_entire_file(cache->file_path, &cache->temp_sb)) return false; + + Nob_String_View iter = nob_sb_to_sv(cache->temp_sb); + iter = nob_sv_trim_left(iter); + while (iter.count > 0) { + Nob_String_View outputs = nob_sv_chop_by_delim(&iter, ':'); + if (outputs.count == 0) { + nob_log(NOB_ERROR, "Failed to read outputs, expected:\n: \n\t\n"); + return false; + } + if (iter.count == 0) { + nob_log(NOB_ERROR, "Expected inputs after outputs:\n: \n\t\n"); + return false; + } + + Nob_String_View inputs = nob_sv_chop_by_delim(&iter, '\n'); + if (inputs.count == 0) { + nob_log(NOB_ERROR, "Failed to read inputs, expected:\n: \n\t\n"); + return false; + } + if (iter.count == 0) { + nob_log(NOB_ERROR, "Expected command after inputs:\n: \n\t\n"); + return false; + } + + Nob_String_View command = iter; + Nob_String_View command2 = nob_sv_chop_by_delim(&iter, '\n'); + // Handle the case where file ends without new line + if (command2.count > 0) command = command2; + + if (false == nob_strace_cache_parse_cmd(command, &cache->temp_cmd)) return false; + Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, cache->temp_cmd, &cache->roots, false); + if (false == nob_strace_cache_parse_file_list(inputs, &node->input_files)) { + nob_log(NOB_ERROR, "Failed to parse input file list: `%.*s`", (int)inputs.count, inputs.data); + return false; + } + if (false == nob_strace_cache_parse_file_list(outputs, &node->output_files)) { + nob_log(NOB_ERROR, "Failed to parse output file list: `%.*s`", (int)outputs.count, outputs.data); + return false; + } + iter = nob_sv_trim_left(iter); + } + return true; +} + +static bool nob_strace_cache_write_rec(Nob_Strace_Cache cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots) +{ + size_t old_count = cache.temp_sb.count; + for (size_t i = 0; i < roots.count; ++i) { + Nob_Strace_Cache_Node node = cache.nodes.items[roots.items[i]]; + nob_sb_append_cstr(&cache.temp_sb, node.argv); + if (node.input_files.count > 0 || node.output_files.count > 0) { + for (size_t j = 0; j < node.output_files.count; ++j) { + if (j + 1 < node.output_files.count) nob_sb_appendf(out, "%.*s ", (int)node.output_files.items[j].count, node.output_files.items[j].items); + else nob_sb_appendf(out, "%.*s: ", (int)node.output_files.items[j].count, node.output_files.items[j].items); + } + for (size_t j = 0; j < node.input_files.count; ++j) { + if (j + 1 < node.input_files.count) nob_sb_appendf(out, "%.*s ", (int)node.input_files.items[j].count, node.input_files.items[j].items); + else nob_sb_appendf(out, "%.*s\n\t", (int)node.input_files.items[j].count, node.input_files.items[j].items); + } + nob_sb_appendf(out, "%.*s\n\n", (int)cache.temp_sb.count, cache.temp_sb.items); + } else { + // TODO: if an arg contains space or other whitespaces, escape them.. + nob_sb_append_cstr(&cache.temp_sb, " "); + nob_strace_cache_write_rec(cache, out, node.children); + } + + cache.temp_sb.count = old_count; + } + return true; +} + +static bool nob_strace_cache_write(Nob_Strace_Cache cache) +{ + Nob_String_Builder out = { 0 }; + cache.temp_sb.count = 0; + if (false == nob_strace_cache_write_rec(cache, &out, cache.roots)) return false; + if (false == nob_write_entire_file(cache.file_path, out.items, out.count)) return false; + nob_sb_free(out); + return true; +} + + // RETURNS: // 0 - file does not exists // 1 - file exists From bf63518c1a94c4125e2d4a3b23ed985049147647 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 14:44:21 +0200 Subject: [PATCH 02/18] Added how_to strace_cache. --- how_to/strace_cache/.gitignore | 3 +++ how_to/strace_cache/nob.c | 31 +++++++++++++++++++++++++++++++ how_to/strace_cache/nob.h | 1 + how_to/strace_cache/src/foo.c | 7 +++++++ 4 files changed, 42 insertions(+) create mode 100644 how_to/strace_cache/.gitignore create mode 100644 how_to/strace_cache/nob.c create mode 120000 how_to/strace_cache/nob.h create mode 100644 how_to/strace_cache/src/foo.c diff --git a/how_to/strace_cache/.gitignore b/how_to/strace_cache/.gitignore new file mode 100644 index 0000000..59c52e1 --- /dev/null +++ b/how_to/strace_cache/.gitignore @@ -0,0 +1,3 @@ +nob +nob.old +build/ \ No newline at end of file diff --git a/how_to/strace_cache/nob.c b/how_to/strace_cache/nob.c new file mode 100644 index 0000000..4064df2 --- /dev/null +++ b/how_to/strace_cache/nob.c @@ -0,0 +1,31 @@ +#define NOB_IMPLEMENTATION +#include "nob.h" + +int main(int argc, char **argv) +{ + NOB_GO_REBUILD_URSELF(argc, argv); + if (!nob_mkdir_if_not_exists("build")) return 1; + + Nob_Cmd cmd = {0}; + // Cache data will be read and witten to a .file_path field ("build/nob.cache" in this case.) + // In case .file_path filed is not set, cache data will only be in memory. + Nob_Strace_Cache cache = { .file_path = "build/nob.cache" }; + + for (int i = 0; i < 10; ++i) { + nob_cc(&cmd); + nob_cc_flags(&cmd); + nob_cc_output(&cmd, "build/foo"); + nob_cc_inputs(&cmd, "src/foo.c"); + if (!nob_cmd_run(&cmd, .strace_cache = &cache)) return 1; + + // Don't cache this command. + nob_cmd_append(&cmd, "./build/foo"); + if (!nob_cmd_run(&cmd)) return 1; + } + + // Save cache data to disc. + // And free all memory malloced by strace_cache. + nob_strace_cache_finish(cache); + + return 0; +} diff --git a/how_to/strace_cache/nob.h b/how_to/strace_cache/nob.h new file mode 120000 index 0000000..53e38de --- /dev/null +++ b/how_to/strace_cache/nob.h @@ -0,0 +1 @@ +../../nob.h \ No newline at end of file diff --git a/how_to/strace_cache/src/foo.c b/how_to/strace_cache/src/foo.c new file mode 100644 index 0000000..445937f --- /dev/null +++ b/how_to/strace_cache/src/foo.c @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + printf("Foo, bar\n"); + return 0; +} From 27ca751df1bf3684836446989a28cf5c0095604a Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 15:04:52 +0200 Subject: [PATCH 03/18] Fixed some bugs. Added more todos. --- nob.h | 167 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 43 deletions(-) diff --git a/nob.h b/nob.h index 281e4e5..2afa36d 100644 --- a/nob.h +++ b/nob.h @@ -703,7 +703,7 @@ typedef struct Nob_String_Builders { size_t alloc_count; } Nob_String_Builders; -NOBDEF void nob_string_builders_push(Nob_String_Builders *sbs, Nob_String_View sv); +NOBDEF void nob_string_builders_push_unique(Nob_String_Builders *sbs, Nob_String_View sv); typedef struct Nob_Strace_Cache_Indexies { int *items; @@ -732,19 +732,55 @@ struct Nob_Strace_Cache { const char *file_path; // File that will be used to read and write collected information for caching bool is_loaded; + + bool was_last_cached; }; +// Usage: +/* +#define NOB_IMPLEMENTATION +#include "nob.h" + +int main(void) +{ + Nob_Cmd cmd = { 0 }; + Nob_Strace_Cache cache = { .file_path = "nob.cache" }; -static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd); + nob_cmd_append(&cmd, "cc", "-o", "main", "main.c"); + nob_cmd_run(&cmd, .strace_cache = &cache); + + nob_strace_cache_finish(cache); + nob_cmd_free(cmd); +} +*/ + +void nob_strace_cache_finish(Nob_Strace_Cache cache); + +static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd); static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup); static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output); static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files); static bool nob_strace_cache_read(Nob_Strace_Cache *cache); -static bool nob_strace_cache_write_rec(Nob_Strace_Cache cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots); -static bool nob_strace_cache_write(Nob_Strace_Cache cache); - -bool nob_cmd_cache_run(Nob_Cmd *cmd, Nob_Strace_Cache *cache); -void nob_strace_cache_free(Nob_Strace_Cache cache); +static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots); +static bool nob_strace_cache_write(Nob_Strace_Cache *cache); +// Strace Cache TODOS: +// * Use in combination with stdin/out - easy +// * Use in combination with stderr -hard +// * Use in combination with async - Idk how... +// * Think about other syscalls that a command can make that can invalidate cache: +// * getenv +// * sockets +// * Do we wanna even support those commands? +// * Maybe store hashes of each files in +// * Make the output format such that the first line of command is cd command to the pwd of command. [how_to/nob.c not cachable because of this] +// * Check if strace exists on Macs, and BSDs +// * How to do this on Windows? +// * Check how stable strace output is. +// * realpath expands symlinks and that means that if the symlink changes, cache will not be invalidated. Do something about this.. +// * Use insted of strace utility. Check how to use at https://github.com/strace/strace +// strace is just a program that is not on all distributions. is part of glibc and should be more cross platform. +// * Run ``strace -v`` to check if strace binary is on the system, if not, disable strace caching. +// * Do something about programs that don't read or write anything.. // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, @@ -1106,12 +1142,14 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) if (false == nob_strace_cache_read(cache)) nob_return_defer(false); } cache->temp_cmd.count = 0; - if (true == nob_cmd_strace_is_cached(*cache, *cmd)) { + if (true == nob_cmd_strace_is_cached(cache, *cmd)) { cache->temp_sb.count = 0; nob_cmd_render(*cmd, &cache->temp_sb); nob_log(NOB_INFO, "CACHED: %.*s", (int)cache->temp_sb.count, cache->temp_sb.items); + cache->was_last_cached = true; nob_return_defer(true); } + cache->was_last_cached = false; cache->temp_cmd.count = 0; // TODO: Add -o [Output trace into a file] flag. It's not that easy becase it will create out-file. foreach pid that is created by the command. // So that would imply that we need to read multiple files in a directory... @@ -1121,7 +1159,7 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) nob_cmd_append(&cache->temp_cmd, cmd->items[i]); } - const char *strace_path = "strace_err"; + const char *strace_path = nob_temp_sprintf("%s.temp", cache->file_path); if (false == nob_cmd_run(&cache->temp_cmd, .stderr_path = strace_path)) { nob_log(NOB_ERROR, "Failed to run strace: %s", strerror(errno)); nob_return_defer(false); @@ -2030,19 +2068,25 @@ NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) return n; } -void nob_string_builders_push(Nob_String_Builders *sbs, Nob_String_View sv) +void nob_string_builders_push_unique(Nob_String_Builders *sbs, Nob_String_View sv) { - // TODO: Handle situations where sv is already in sbs... - if (sbs->count == sbs->alloc_count++) { + for (size_t i = 0; i < sbs->count; ++i) { + if (nob_sv_eq(nob_sb_to_sv(sbs->items[i]), sv)) return; + } + + if (sbs->count == sbs->alloc_count) { + sbs->alloc_count += 1; Nob_String_Builder new_sb = { 0 }; nob_sb_appendf(&new_sb, "%.*s", (int)sv.count, sv.data); nob_sb_append_null(&new_sb); + new_sb.count -= 1; nob_da_append(sbs, new_sb); } else { Nob_String_Builder *old_sb = &sbs->items[sbs->count++]; old_sb->count = 0; nob_sb_appendf(old_sb, "%.*s", (int)sv.count, sv.data); nob_sb_append_null(old_sb); + old_sb->count -= 1; } } @@ -2185,6 +2229,8 @@ NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_pref NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value) { + if (sv.count < value.count) return false; + for (size_t i = 0; i < sv.count + 1 - value.count; ++i) { bool is_match = true; for (size_t j = 0; j < value.count; ++j) { @@ -2201,15 +2247,20 @@ NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value) NOBDEF void nob_strace_cache_finish(Nob_Strace_Cache cache) { - nob_strace_cache_write(cache); + nob_strace_cache_write(&cache); + const char* strace_output_path = nob_temp_sprintf("%s.temp", cache.file_path); + if (nob_file_exists(strace_output_path) > 0) { + nob_delete_file(strace_output_path); + } + nob_da_free(cache.roots); nob_cmd_free(cache.temp_cmd); nob_sb_free(cache.temp_sb); for (size_t i = 0; i < cache.nodes.count; ++i) { Nob_Strace_Cache_Node node = cache.nodes.items[i]; nob_da_free(node.children); - for (size_t j = 0; j < node.input_files.count; ++j) nob_sb_free(node.input_files.items[j]); - for (size_t j = 0; j < node.output_files.count; ++j) nob_sb_free(node.output_files.items[j]); + for (size_t j = 0; j < node.input_files.alloc_count; ++j) nob_sb_free(node.input_files.items[j]); + for (size_t j = 0; j < node.output_files.alloc_count; ++j) nob_sb_free(node.output_files.items[j]); free(node.argv); nob_da_free(node.input_files); nob_da_free(node.output_files); @@ -2217,14 +2268,14 @@ NOBDEF void nob_strace_cache_finish(Nob_Strace_Cache cache) nob_da_free(cache.nodes); } -static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd) +static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd) { int cmd_index = 0; int node_index = -1; - for (size_t i = 0; i < cache.roots.count; ++i) { - int cur_node_index = cache.roots.items[i]; - Nob_Strace_Cache_Node node = cache.nodes.items[cur_node_index]; + for (size_t i = 0; i < cache->roots.count; ++i) { + int cur_node_index = cache->roots.items[i]; + Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; if (strcmp(cmd.items[0], node.argv) == 0) { node_index = cur_node_index; } @@ -2232,13 +2283,13 @@ static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd) ++cmd_index; if (node_index < 0) return false; - Nob_Strace_Cache_Node node = cache.nodes.items[node_index]; + Nob_Strace_Cache_Node node = cache->nodes.items[node_index]; while (cmd_index < (int)cmd.count) { Nob_Strace_Cache_Node child_node; int child_index; for (size_t i = 0; i < node.children.count; ++i) { child_index = node.children.items[i]; - child_node = cache.nodes.items[child_index]; + child_node = cache->nodes.items[child_index]; if (strcmp(cmd.items[cmd_index], child_node.argv) == 0) goto found; } return false; @@ -2249,13 +2300,13 @@ static bool nob_cmd_strace_is_cached(Nob_Strace_Cache cache, Nob_Cmd cmd) ++cmd_index; } - cache.temp_cmd.count = 0; + cache->temp_cmd.count = 0; for (size_t i = 0; i < node.input_files.count; ++i) { - nob_cmd_append(&cache.temp_cmd, node.input_files.items[i].items); + nob_cmd_append(&cache->temp_cmd, node.input_files.items[i].items); } for (size_t i = 0; i < node.output_files.count; ++i) { - if (nob_needs_rebuild(node.output_files.items[i].items, cache.temp_cmd.items, cache.temp_cmd.count)) return false; + if (nob_needs_rebuild(node.output_files.items[i].items, cache->temp_cmd.items, cache->temp_cmd.count)) return false; } return true; @@ -2272,6 +2323,7 @@ static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; if (strcmp(cmd.items[0], node.argv) == 0) { node_index = cur_node_index; + break; } } @@ -2280,10 +2332,12 @@ static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Strace_Cache_Node new_node = { .argv = dup ? strdup(cmd.items[0]) : (char*)cmd.items[0], }; + nob_da_append(indexies, cache->nodes.count); nob_da_append(&cache->nodes, new_node); - nob_da_append(indexies, cache->nodes.count - 1); next = &nob_da_last(&cache->nodes); } else { + // TODO: Nasty hack. If it needs to be free but the place is already taken... + if (false == dup) free((char*)cmd.items[0]); next = &cache->nodes.items[node_index]; } @@ -2295,8 +2349,26 @@ static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, return nob_cmd_strace_cache_node(cache, cmd, &next->children, dup); } +static Nob_String_View nob_strace_cache_relative_to_absolute(Nob_String_View sv) +{ + // TODO: Windows support +#if _WIN32 + nob_log(NOB_WARNING, "relative_to_absolute not implemented on windows. Returning relative path..."); + return sv; +#else + static char temp_buffer[PATH_MAX]; + const char* temp_input = nob_temp_sv_to_cstr(sv); + if (NULL == realpath(temp_input, temp_buffer)) { + nob_log(NOB_WARNING, "Failed to get an absolute path for %s. Using relative", temp_input); + return sv; + } + return nob_sv_from_cstr(temp_buffer); +#endif +} + static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output) { + strace_output->count = 0; if (!nob_read_entire_file(strace_output_path, strace_output)) return false; Nob_String_View iter = nob_sb_to_sv(*strace_output); @@ -2304,16 +2376,23 @@ static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_St Nob_String_View _ = { 0 }; (void)_; + input_files->count = 0; + output_files->count = 0; while (iter.count > 0) { _ = nob_sv_chop_by_delim_sv(&iter, openat_delim); _ = nob_sv_chop_by_delim(&iter, '"'); Nob_String_View path = nob_sv_chop_by_delim(&iter, '"'); if (path.count == 0) break; - if (nob_sv_starts_with(path, nob_sv_from_cstr("/usr/"))) continue; // SKIP system files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/etc/"))) continue; // SKIP etc files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/tmp/"))) continue; // SKIP tmp files... - // TODO: Think about adding a flag to enable system/etc/tmp files + if (nob_sv_starts_with(path, nob_sv_from_cstr("/usr/"))) continue; // SKIP system files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/etc/"))) continue; // SKIP etc files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/tmp/"))) continue; // SKIP tmp files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/sys/"))) continue; // SKIP sys files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/proc/"))) continue; // SKIP proc files... + if (nob_sv_contains(path, nob_sv_from_cstr("/.cache/"))) continue; // SKIP cache files... + if (nob_sv_contains(path, nob_sv_from_cstr("/run/"))) continue; // SKIP cache files... + if (nob_sv_starts_with(path, nob_sv_from_cstr("/dev/"))) continue; // SKIP dev files... + // TODO: Think about adding a flag to enable system/etc/tmp files Nob_String_View rw_args = nob_sv_chop_by_delim(&iter, ')'); if (rw_args.count == 0) break; @@ -2323,9 +2402,9 @@ static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_St nob_sv_contains(rw_args, nob_sv_from_cstr("O_WRONLY")); if (is_write) { - nob_string_builders_push(output_files, path); + nob_string_builders_push_unique(output_files, nob_strace_cache_relative_to_absolute(path)); } else if (is_read) { - nob_string_builders_push(input_files, path); + nob_string_builders_push_unique(input_files, nob_strace_cache_relative_to_absolute(path)); } else { nob_log(NOB_WARNING, "Unknown rw_args(%zu): `%.*s`", rw_args.count, (int)rw_args.count, rw_args.data); return false; @@ -2357,7 +2436,7 @@ static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Buil { Nob_String_View file_view = nob_sv_chop_by_delim(&sv, ' '); Nob_String_View arg = nob_sv_from_cstr(nob_temp_sprintf("%.*s", (int)file_view.count, file_view.data)); - nob_string_builders_push(files, arg); + nob_string_builders_push_unique(files, nob_strace_cache_relative_to_absolute(arg)); sv = nob_sv_trim_left(sv); } return true; @@ -2420,17 +2499,18 @@ static bool nob_strace_cache_read(Nob_Strace_Cache *cache) nob_log(NOB_ERROR, "Failed to parse output file list: `%.*s`", (int)outputs.count, outputs.data); return false; } + cache->temp_cmd.count = 0; iter = nob_sv_trim_left(iter); } return true; } -static bool nob_strace_cache_write_rec(Nob_Strace_Cache cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots) +static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots) { - size_t old_count = cache.temp_sb.count; + size_t old_count = cache->temp_sb.count; for (size_t i = 0; i < roots.count; ++i) { - Nob_Strace_Cache_Node node = cache.nodes.items[roots.items[i]]; - nob_sb_append_cstr(&cache.temp_sb, node.argv); + Nob_Strace_Cache_Node node = cache->nodes.items[roots.items[i]]; + nob_sb_append_cstr(&cache->temp_sb, node.argv); if (node.input_files.count > 0 || node.output_files.count > 0) { for (size_t j = 0; j < node.output_files.count; ++j) { if (j + 1 < node.output_files.count) nob_sb_appendf(out, "%.*s ", (int)node.output_files.items[j].count, node.output_files.items[j].items); @@ -2440,24 +2520,25 @@ static bool nob_strace_cache_write_rec(Nob_Strace_Cache cache, Nob_String_Builde if (j + 1 < node.input_files.count) nob_sb_appendf(out, "%.*s ", (int)node.input_files.items[j].count, node.input_files.items[j].items); else nob_sb_appendf(out, "%.*s\n\t", (int)node.input_files.items[j].count, node.input_files.items[j].items); } - nob_sb_appendf(out, "%.*s\n\n", (int)cache.temp_sb.count, cache.temp_sb.items); + if (node.input_files.count == 0) nob_sb_appendf(out, "\n\t"); + nob_sb_appendf(out, "%.*s\n\n", (int)cache->temp_sb.count, cache->temp_sb.items); } else { // TODO: if an arg contains space or other whitespaces, escape them.. - nob_sb_append_cstr(&cache.temp_sb, " "); + nob_sb_append_cstr(&cache->temp_sb, " "); nob_strace_cache_write_rec(cache, out, node.children); } - cache.temp_sb.count = old_count; + cache->temp_sb.count = old_count; } return true; } -static bool nob_strace_cache_write(Nob_Strace_Cache cache) +static bool nob_strace_cache_write(Nob_Strace_Cache *cache) { Nob_String_Builder out = { 0 }; - cache.temp_sb.count = 0; - if (false == nob_strace_cache_write_rec(cache, &out, cache.roots)) return false; - if (false == nob_write_entire_file(cache.file_path, out.items, out.count)) return false; + cache->temp_sb.count = 0; + if (false == nob_strace_cache_write_rec(cache, &out, cache->roots)) return false; + if (false == nob_write_entire_file(cache->file_path, out.items, out.count)) return false; nob_sb_free(out); return true; } From fe7591fcaed8686a27bc1d7ea77587c3bff1dc42 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 15:05:27 +0200 Subject: [PATCH 04/18] Added strace_cache to how_to/nob.c. --- how_to/nob.c | 1 + 1 file changed, 1 insertion(+) diff --git a/how_to/nob.c b/how_to/nob.c index 4981ce3..cc31288 100644 --- a/how_to/nob.c +++ b/how_to/nob.c @@ -10,6 +10,7 @@ const char *examples[] = { "001_basic_usage", "005_parallel_build", "010_nob_two_stage", + "strace_cache", }; int main(int argc, char **argv) From c1c5b7cde00bb74a0fd7b6c8506855ca2ed5afd1 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 16:25:10 +0200 Subject: [PATCH 05/18] Disable strace cache on systems that don't have strace program. --- nob.h | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/nob.h b/nob.h index 2afa36d..419c2df 100644 --- a/nob.h +++ b/nob.h @@ -732,6 +732,7 @@ struct Nob_Strace_Cache { const char *file_path; // File that will be used to read and write collected information for caching bool is_loaded; + bool is_disabled; bool was_last_cached; }; @@ -773,14 +774,15 @@ static bool nob_strace_cache_write(Nob_Strace_Cache *cache); // * Do we wanna even support those commands? // * Maybe store hashes of each files in // * Make the output format such that the first line of command is cd command to the pwd of command. [how_to/nob.c not cachable because of this] -// * Check if strace exists on Macs, and BSDs +// * How to do this on BSDs? +// * How to do this on Mac? // * How to do this on Windows? // * Check how stable strace output is. // * realpath expands symlinks and that means that if the symlink changes, cache will not be invalidated. Do something about this.. // * Use insted of strace utility. Check how to use at https://github.com/strace/strace // strace is just a program that is not on all distributions. is part of glibc and should be more cross platform. -// * Run ``strace -v`` to check if strace binary is on the system, if not, disable strace caching. // * Do something about programs that don't read or write anything.. +// * Flag to disable absolute paths. It is more correct to have absolute path, but it looks ugly. // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, @@ -1131,16 +1133,20 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) opt.strace_cache = NULL; #endif - if (opt.strace_cache) { + if (opt.strace_cache && false == opt.strace_cache->is_disabled) { Nob_Strace_Cache *cache = opt.strace_cache; + if (false == cache->is_loaded) { + if (false == nob_strace_cache_read(cache)) { + cache->is_disabled = true; + goto disabled_strace_cache; + } + } + if (opt.async) NOB_TODO("Strace cache and async is not implemented."); if (opt.stdin_path) NOB_TODO("Strace cache and stdin_path is not implemented."); if (opt.stdout_path) NOB_TODO("Strace cache and stdout_path is not implemented."); if (opt.stderr_path) NOB_TODO("Strace cache and stderr_path is not implemented."); - if (false == cache->is_loaded) { - if (false == nob_strace_cache_read(cache)) nob_return_defer(false); - } cache->temp_cmd.count = 0; if (true == nob_cmd_strace_is_cached(cache, *cmd)) { cache->temp_sb.count = 0; @@ -1170,6 +1176,7 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) nob_return_defer(true); } +disabled_strace_cache: if (opt.async && max_procs > 0) { while (opt.async->count >= max_procs) { for (size_t i = 0; i < opt.async->count; ++i) { @@ -2447,6 +2454,16 @@ static bool nob_strace_cache_read(Nob_Strace_Cache *cache) NOB_ASSERT(cache->is_loaded == false); cache->is_loaded = true; + // Check if strace exists on the system + Nob_Cmd cmd = { 0 }; + nob_cmd_append(&cmd, "strace", "-V"); + bool test_strace_result = nob_cmd_run(&cmd); + nob_cmd_free(cmd); + if (false == test_strace_result) { + nob_log(NOB_WARNING, "Failed to run `strace -V` Continuing without caching."); + return false; + } + if (NULL == cache->file_path) return true; int exists = nob_file_exists(cache->file_path); From b1595b2201e6739c556b832863b2a5072c2fb5bb Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 17:58:34 +0200 Subject: [PATCH 06/18] Print stderror when command fails (with strace spam.) Add executing program to the list of input files. --- nob.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nob.h b/nob.h index 419c2df..4048fb1 100644 --- a/nob.h +++ b/nob.h @@ -1167,12 +1167,17 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) const char *strace_path = nob_temp_sprintf("%s.temp", cache->file_path); if (false == nob_cmd_run(&cache->temp_cmd, .stderr_path = strace_path)) { + if (true == nob_read_entire_file(strace_path, &cache->temp_sb)) { + nob_log(NOB_ERROR, "%.*s", cache->temp_sb.count, cache->temp_sb.items); + } nob_log(NOB_ERROR, "Failed to run strace: %s", strerror(errno)); nob_return_defer(false); } Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, *cmd, &cache->roots, true); nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb); + // Add a file that was executed because it's not picked up by strace.. + nob_string_builders_push_unique(&node->input_files, nob_sv_from_cstr(cmd->items[0])); nob_return_defer(true); } From c4ecb810ff35c8950b08d8906368739e75a135f8 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 18:11:22 +0200 Subject: [PATCH 07/18] Only add exe path if it exist. --- nob.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nob.h b/nob.h index 4048fb1..5248971 100644 --- a/nob.h +++ b/nob.h @@ -758,6 +758,7 @@ void nob_strace_cache_finish(Nob_Strace_Cache cache); static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd); static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup); +static Nob_String_View nob_strace_cache_relative_to_absolute(Nob_String_View sv); static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output); static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files); @@ -1177,7 +1178,11 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, *cmd, &cache->roots, true); nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb); // Add a file that was executed because it's not picked up by strace.. - nob_string_builders_push_unique(&node->input_files, nob_sv_from_cstr(cmd->items[0])); + if (nob_file_exists(cmd->items[0]) > 0) { + // But only if it exists, if it doesn't it means that the binary was picked up from the path and that means it's no relevant. + // (maybe...) + nob_string_builders_push_unique(&node->input_files, nob_strace_cache_relative_to_absolute(nob_sv_from_cstr(cmd->items[0]))); + } nob_return_defer(true); } From 05e4af021e3142e98b294b85dceefe3847300f8f Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 30 Aug 2025 18:41:50 +0200 Subject: [PATCH 08/18] bool to disable conversion to absolute paths. --- nob.h | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/nob.h b/nob.h index 5248971..1b1cdae 100644 --- a/nob.h +++ b/nob.h @@ -733,6 +733,7 @@ struct Nob_Strace_Cache { const char *file_path; // File that will be used to read and write collected information for caching bool is_loaded; bool is_disabled; + bool no_absolute; bool was_last_cached; }; @@ -758,10 +759,10 @@ void nob_strace_cache_finish(Nob_Strace_Cache cache); static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd); static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup); -static Nob_String_View nob_strace_cache_relative_to_absolute(Nob_String_View sv); -static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output); +static Nob_String_View nob_strace_cache_relative_to_absolute_maybe(Nob_String_View sv, bool for_real); +static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output, bool absolute_paths); static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); -static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files); +static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files, bool absolute_paths); static bool nob_strace_cache_read(Nob_Strace_Cache *cache); static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots); static bool nob_strace_cache_write(Nob_Strace_Cache *cache); @@ -783,7 +784,6 @@ static bool nob_strace_cache_write(Nob_Strace_Cache *cache); // * Use insted of strace utility. Check how to use at https://github.com/strace/strace // strace is just a program that is not on all distributions. is part of glibc and should be more cross platform. // * Do something about programs that don't read or write anything.. -// * Flag to disable absolute paths. It is more correct to have absolute path, but it looks ugly. // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, @@ -1176,12 +1176,12 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) } Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, *cmd, &cache->roots, true); - nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb); + nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb, !cache->no_absolute); // Add a file that was executed because it's not picked up by strace.. if (nob_file_exists(cmd->items[0]) > 0) { // But only if it exists, if it doesn't it means that the binary was picked up from the path and that means it's no relevant. // (maybe...) - nob_string_builders_push_unique(&node->input_files, nob_strace_cache_relative_to_absolute(nob_sv_from_cstr(cmd->items[0]))); + nob_string_builders_push_unique(&node->input_files, nob_strace_cache_relative_to_absolute_maybe(nob_sv_from_cstr(cmd->items[0]), !cache->no_absolute)); } nob_return_defer(true); } @@ -2366,8 +2366,10 @@ static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, return nob_cmd_strace_cache_node(cache, cmd, &next->children, dup); } -static Nob_String_View nob_strace_cache_relative_to_absolute(Nob_String_View sv) +static Nob_String_View nob_strace_cache_relative_to_absolute_maybe(Nob_String_View sv, bool for_real) { + if (!for_real) return sv; + // TODO: Windows support #if _WIN32 nob_log(NOB_WARNING, "relative_to_absolute not implemented on windows. Returning relative path..."); @@ -2376,14 +2378,14 @@ static Nob_String_View nob_strace_cache_relative_to_absolute(Nob_String_View sv) static char temp_buffer[PATH_MAX]; const char* temp_input = nob_temp_sv_to_cstr(sv); if (NULL == realpath(temp_input, temp_buffer)) { - nob_log(NOB_WARNING, "Failed to get an absolute path for %s. Using relative", temp_input); + nob_log(NOB_WARNING, "Failed to get an absolute path for %s: %s. Using relative", temp_input, strerror(errno)); return sv; } return nob_sv_from_cstr(temp_buffer); #endif } -static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output) +static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output, bool absolute_paths) { strace_output->count = 0; if (!nob_read_entire_file(strace_output_path, strace_output)) return false; @@ -2419,9 +2421,9 @@ static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_St nob_sv_contains(rw_args, nob_sv_from_cstr("O_WRONLY")); if (is_write) { - nob_string_builders_push_unique(output_files, nob_strace_cache_relative_to_absolute(path)); + nob_string_builders_push_unique(output_files, nob_strace_cache_relative_to_absolute_maybe(path, absolute_paths)); } else if (is_read) { - nob_string_builders_push_unique(input_files, nob_strace_cache_relative_to_absolute(path)); + nob_string_builders_push_unique(input_files, nob_strace_cache_relative_to_absolute_maybe(path, absolute_paths)); } else { nob_log(NOB_WARNING, "Unknown rw_args(%zu): `%.*s`", rw_args.count, (int)rw_args.count, rw_args.data); return false; @@ -2445,7 +2447,7 @@ static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd) return true; } -static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files) +static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files, bool absolute_paths) { // TODO: Handle spaces.. sv = nob_sv_trim_left(sv); @@ -2453,7 +2455,7 @@ static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Buil { Nob_String_View file_view = nob_sv_chop_by_delim(&sv, ' '); Nob_String_View arg = nob_sv_from_cstr(nob_temp_sprintf("%.*s", (int)file_view.count, file_view.data)); - nob_string_builders_push_unique(files, nob_strace_cache_relative_to_absolute(arg)); + nob_string_builders_push_unique(files, nob_strace_cache_relative_to_absolute_maybe(arg, absolute_paths)); sv = nob_sv_trim_left(sv); } return true; @@ -2518,11 +2520,11 @@ static bool nob_strace_cache_read(Nob_Strace_Cache *cache) if (false == nob_strace_cache_parse_cmd(command, &cache->temp_cmd)) return false; Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, cache->temp_cmd, &cache->roots, false); - if (false == nob_strace_cache_parse_file_list(inputs, &node->input_files)) { + if (false == nob_strace_cache_parse_file_list(inputs, &node->input_files, !cache->no_absolute)) { nob_log(NOB_ERROR, "Failed to parse input file list: `%.*s`", (int)inputs.count, inputs.data); return false; } - if (false == nob_strace_cache_parse_file_list(outputs, &node->output_files)) { + if (false == nob_strace_cache_parse_file_list(outputs, &node->output_files, !cache->no_absolute)) { nob_log(NOB_ERROR, "Failed to parse output file list: `%.*s`", (int)outputs.count, outputs.data); return false; } From 75abd666d398027ac08acb74151fdc1b01ab1d61 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sun, 31 Aug 2025 12:03:52 +0200 Subject: [PATCH 09/18] Bug fix where is_disabled wasn't set in the right place. --- nob.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/nob.h b/nob.h index 1b1cdae..3f8e322 100644 --- a/nob.h +++ b/nob.h @@ -1137,10 +1137,7 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) if (opt.strace_cache && false == opt.strace_cache->is_disabled) { Nob_Strace_Cache *cache = opt.strace_cache; if (false == cache->is_loaded) { - if (false == nob_strace_cache_read(cache)) { - cache->is_disabled = true; - goto disabled_strace_cache; - } + if (false == nob_strace_cache_read(cache)) goto disabled_strace_cache; } if (opt.async) NOB_TODO("Strace cache and async is not implemented."); @@ -2276,7 +2273,7 @@ NOBDEF void nob_strace_cache_finish(Nob_Strace_Cache cache) for (size_t i = 0; i < cache.nodes.count; ++i) { Nob_Strace_Cache_Node node = cache.nodes.items[i]; nob_da_free(node.children); - for (size_t j = 0; j < node.input_files.alloc_count; ++j) nob_sb_free(node.input_files.items[j]); + for (size_t j = 0; j < node.input_files.alloc_count; ++j) nob_sb_free(node.input_files.items[j]); for (size_t j = 0; j < node.output_files.alloc_count; ++j) nob_sb_free(node.output_files.items[j]); free(node.argv); nob_da_free(node.input_files); @@ -2473,6 +2470,7 @@ static bool nob_strace_cache_read(Nob_Strace_Cache *cache) nob_cmd_free(cmd); if (false == test_strace_result) { nob_log(NOB_WARNING, "Failed to run `strace -V` Continuing without caching."); + cache->is_disabled = true; return false; } From 6a99ff59a4889f195a48c1043e8816fc84876354 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sun, 31 Aug 2025 12:40:53 +0200 Subject: [PATCH 10/18] Upsich. --- nob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nob.h b/nob.h index 3f8e322..abc4483 100644 --- a/nob.h +++ b/nob.h @@ -1325,7 +1325,7 @@ static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, if (fderr) { if (dup2(*fderr, STDERR_FILENO) < 0) { - nob_log(NOB_ERROR, "Dick Could not setup stderr %d for child process: %s", *fderr, strerror(errno)); + nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); exit(1); } } From 37b12da747ddf1674952244855144a0bf14781d6 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sun, 31 Aug 2025 12:56:54 +0200 Subject: [PATCH 11/18] Typo. --- nob.h | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/nob.h b/nob.h index abc4483..c573a4f 100644 --- a/nob.h +++ b/nob.h @@ -705,15 +705,15 @@ typedef struct Nob_String_Builders { NOBDEF void nob_string_builders_push_unique(Nob_String_Builders *sbs, Nob_String_View sv); -typedef struct Nob_Strace_Cache_Indexies { +typedef struct Nob_Strace_Cache_Indexes { int *items; size_t count; size_t capacity; -} Nob_Strace_Cache_Indexies; +} Nob_Strace_Cache_Indexes; typedef struct Nob_Strace_Cache_Node { char *argv; - Nob_Strace_Cache_Indexies children; + Nob_Strace_Cache_Indexes children; Nob_String_Builders input_files; Nob_String_Builders output_files; } Nob_Strace_Cache_Node; @@ -726,7 +726,7 @@ typedef struct Nob_Strace_Cache_Nodes { struct Nob_Strace_Cache { Nob_Strace_Cache_Nodes nodes; - Nob_Strace_Cache_Indexies roots; + Nob_Strace_Cache_Indexes roots; Nob_Cmd temp_cmd; // Don't worry about it... Nob_String_Builder temp_sb; // Don't worry about it... @@ -758,13 +758,13 @@ int main(void) void nob_strace_cache_finish(Nob_Strace_Cache cache); static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd); -static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup); +static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexes *indexes, bool dup); static Nob_String_View nob_strace_cache_relative_to_absolute_maybe(Nob_String_View sv, bool for_real); static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output, bool absolute_paths); static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files, bool absolute_paths); static bool nob_strace_cache_read(Nob_Strace_Cache *cache); -static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots); +static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexes roots); static bool nob_strace_cache_write(Nob_Strace_Cache *cache); // Strace Cache TODOS: // * Use in combination with stdin/out - easy @@ -784,6 +784,7 @@ static bool nob_strace_cache_write(Nob_Strace_Cache *cache); // * Use insted of strace utility. Check how to use at https://github.com/strace/strace // strace is just a program that is not on all distributions. is part of glibc and should be more cross platform. // * Do something about programs that don't read or write anything.. +// * Handle spaces in command arguments and file paths.. // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, @@ -2326,14 +2327,14 @@ static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd) return true; } -static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexies *indexies, bool dup) +static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexes *indexes, bool dup) { - assert(cmd.count != 0); + NOB_ASSERT(cmd.count != 0); int node_index = -1; - for (size_t i = 0; i < indexies->count; ++i) { - int cur_node_index = indexies->items[i]; + for (size_t i = 0; i < indexes->count; ++i) { + int cur_node_index = indexes->items[i]; Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; if (strcmp(cmd.items[0], node.argv) == 0) { node_index = cur_node_index; @@ -2346,7 +2347,7 @@ static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Strace_Cache_Node new_node = { .argv = dup ? strdup(cmd.items[0]) : (char*)cmd.items[0], }; - nob_da_append(indexies, cache->nodes.count); + nob_da_append(indexes, cache->nodes.count); nob_da_append(&cache->nodes, new_node); next = &nob_da_last(&cache->nodes); } else { @@ -2532,7 +2533,7 @@ static bool nob_strace_cache_read(Nob_Strace_Cache *cache) return true; } -static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexies roots) +static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexes roots) { size_t old_count = cache->temp_sb.count; for (size_t i = 0; i < roots.count; ++i) { From b8e90c0ec8cb9fd93feb03e67929d026b48da7cb Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sun, 31 Aug 2025 13:02:33 +0200 Subject: [PATCH 12/18] Typo. --- how_to/strace_cache/nob.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/how_to/strace_cache/nob.c b/how_to/strace_cache/nob.c index 4064df2..1c8cbef 100644 --- a/how_to/strace_cache/nob.c +++ b/how_to/strace_cache/nob.c @@ -3,12 +3,12 @@ int main(int argc, char **argv) { - NOB_GO_REBUILD_URSELF(argc, argv); + NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob.h"); if (!nob_mkdir_if_not_exists("build")) return 1; Nob_Cmd cmd = {0}; - // Cache data will be read and witten to a .file_path field ("build/nob.cache" in this case.) - // In case .file_path filed is not set, cache data will only be in memory. + // Cache data will be read and written to a .file_path field ("build/nob.cache" in this case.) + // In case .file_path field is not set, cache data will only be in memory. Nob_Strace_Cache cache = { .file_path = "build/nob.cache" }; for (int i = 0; i < 10; ++i) { @@ -27,5 +27,7 @@ int main(int argc, char **argv) // And free all memory malloced by strace_cache. nob_strace_cache_finish(cache); + nob_cmd_free(cmd); + return 0; } From 38417b70f888e73dc9f4bd88eead1f66f61ebfd1 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 6 Sep 2025 07:13:14 +0200 Subject: [PATCH 13/18] Strace -> Ptrace. --- how_to/strace_cache/nob.c | 72 +++- nob.h | 876 ++++++++++++++++++++------------------ 2 files changed, 523 insertions(+), 425 deletions(-) diff --git a/how_to/strace_cache/nob.c b/how_to/strace_cache/nob.c index 1c8cbef..ba0216c 100644 --- a/how_to/strace_cache/nob.c +++ b/how_to/strace_cache/nob.c @@ -1,6 +1,10 @@ #define NOB_IMPLEMENTATION #include "nob.h" +// Just for debugging and to see what is cached in human readable format +static void nob__ptrace_cache_print_makefile(Nob_Ptrace_Cache cache); +static void nob__ptrace_cache_print_debug(Nob_Ptrace_Cache cache); + int main(int argc, char **argv) { NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob.h"); @@ -9,25 +13,85 @@ int main(int argc, char **argv) Nob_Cmd cmd = {0}; // Cache data will be read and written to a .file_path field ("build/nob.cache" in this case.) // In case .file_path field is not set, cache data will only be in memory. - Nob_Strace_Cache cache = { .file_path = "build/nob.cache" }; + Nob_Ptrace_Cache cache = { .file_path = "build/nob.cache" }; for (int i = 0; i < 10; ++i) { nob_cc(&cmd); nob_cc_flags(&cmd); nob_cc_output(&cmd, "build/foo"); nob_cc_inputs(&cmd, "src/foo.c"); - if (!nob_cmd_run(&cmd, .strace_cache = &cache)) return 1; + if (!nob_cmd_run(&cmd, .ptrace_cache = &cache)) return 1; // Don't cache this command. nob_cmd_append(&cmd, "./build/foo"); if (!nob_cmd_run(&cmd)) return 1; } + // Convert Nob_Ptrace_Cache to Makefile and print it + nob__ptrace_cache_print_makefile(cache); + nob__ptrace_cache_print_debug(cache); + // Save cache data to disc. - // And free all memory malloced by strace_cache. - nob_strace_cache_finish(cache); + // And free all memory malloced by ptrace_cache. + nob_ptrace_cache_finish(cache); nob_cmd_free(cmd); return 0; } + +#if NOB_HAS_PTRACE_CACHE +static void nob__ptrace_cache_print_makefile_rec(Nob_Ptrace_Cache cache, Nob_String_Builder* cur_command, int parent) +{ + size_t old_count = cur_command->count; + for (size_t i = 0; i < cache.nodes.count; ++i) { + Nob__Ptrace_Cache_Node node = cache.nodes.items[i]; + if (node.parent != parent) continue; + if (node.kind == Nob__Ptrace_Cache_Node_Arg) nob_sb_append_cstr(cur_command, " "); + if (node.kind == Nob__Ptrace_Cache_Node_Stdin) nob_sb_append_cstr(cur_command, " < "); + if (node.kind == Nob__Ptrace_Cache_Node_Stdout) nob_sb_append_cstr(cur_command, " > "); + if (node.kind == Nob__Ptrace_Cache_Node_Stderr) nob_sb_append_cstr(cur_command, " 2> "); + nob_sb_append_cstr(cur_command, &cache.arena.items[node.arg_index]); + if (node.input_paths.count != 0 || node.output_paths.count != 0) { + for (size_t j = 0; j < node.output_paths.count;) { + printf("%s", &cache.arena.items[node.output_paths.items[j]]); + if (++j < node.output_paths.count) printf(" "); + } + printf(": "); + for (size_t j = 0; j < node.input_paths.count; ++j) printf("%s ", &cache.arena.items[node.input_paths.items[j]]); + printf("\n\t"); + printf("%.*s\n\n", (int)cur_command->count, cur_command->items); + } + nob__ptrace_cache_print_makefile_rec(cache, cur_command, (int)i); + cur_command->count = old_count; + } +} + +static void nob__ptrace_cache_print_makefile(Nob_Ptrace_Cache cache) +{ + Nob_String_Builder cur_command = { 0 }; + nob__ptrace_cache_print_makefile_rec(cache, &cur_command, -1); + nob_sb_free(cur_command); +} + +static void nob__ptrace_cache_print_debug(Nob_Ptrace_Cache cache) +{ + printf("-----\n"); + for (size_t i = 0; i < cache.nodes.count; ++i) { + Nob__Ptrace_Cache_Node node = cache.nodes.items[i]; + printf("[%zu]%s(parent=%d, kind=%d, in_c=%zu, out_c=%zu)\n", i, &cache.arena.items[node.arg_index], node.parent, node.kind, node.input_paths.count, node.output_paths.count); + } + printf("-----\n"); +} + +#else +static void nob__ptrace_cache_print_makefile(Nob_Ptrace_Cache cache) +{ + printf("Ptrace not implmented on this platform\n"); +} + +static void nob__ptrace_cache_print_debug(Nob_Ptrace_Cache cache) +{ + printf("Ptrace not implmented on this platform\n"); +} +#endif // NOB_HAS_PTRACE_CACHE diff --git a/nob.h b/nob.h index c573a4f..19636b3 100644 --- a/nob.h +++ b/nob.h @@ -167,6 +167,24 @@ # include #endif +#if defined(__has_include) +# define NOB__HAS_INCLUDE(path) __has_include(path) +#else +# define NOB__HAS_INCLUDE(path) +#endif + +#if !defined(NOB_HAS_PTRACE_CACHE) +# if NOB__HAS_INCLUDE() +# define NOB_HAS_PTRACE_CACHE 1 +# endif +#endif + +#if NOB_HAS_PTRACE_CACHE +# include +# include +# include +#endif + #ifdef _WIN32 # define NOB_LINE_END "\r\n" #else @@ -236,6 +254,7 @@ NOBDEF Nob_File_Type nob_get_file_type(const char *path); NOBDEF bool nob_delete_file(const char *path); #define nob_return_defer(value) do { result = (value); goto defer; } while(0) +#define nob__return_defer_msg(value, msg, ...) do { result = (value); nob_log(NOB_INFO, "[" __FILE__ ":%d]" msg, __LINE__, ##__VA_ARGS__); goto defer; } while(0) // Initial capacity of a dynamic array #ifndef NOB_DA_INIT_CAP @@ -390,15 +409,15 @@ typedef struct { size_t capacity; } Nob_Cmd; -typedef struct Nob_Strace_Cache Nob_Strace_Cache; +typedef struct Nob_Ptrace_Cache Nob_Ptrace_Cache; // Options for nob_cmd_run_opt() function. typedef struct { // Run the command asynchronously appending its Nob_Proc to the provided Nob_Procs array Nob_Procs *async; // Maximum processes allowed in the .async list. Zero implies nob_nprocs(). size_t max_procs; - // Caching system that uses strace to determin what files are input and outputs to the command (Only on linux.) - Nob_Strace_Cache *strace_cache; + // Caching system that uses ptrace to determin what files are input and outputs to the command (Only on linux.) + Nob_Ptrace_Cache *ptrace_cache; // Redirect stdin to file const char *stdin_path; // Redirect stdout to file @@ -555,6 +574,7 @@ NOBDEF void nob_temp_rewind(size_t checkpoint); // "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory" NOBDEF const char *nob_path_name(const char *path); NOBDEF bool nob_rename(const char *old_path, const char *new_path); +NOBDEF int nob__needs_rebuild_ex(const char *output_path, const char **input_paths, size_t input_paths_count, bool allow_inputs_to_not_exist); NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path); NOBDEF int nob_file_exists(const char *file_path); @@ -670,7 +690,6 @@ typedef struct { NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv); NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); -NOBDEF Nob_String_View nob_sv_chop_by_delim_sv(Nob_String_View *sv, Nob_String_View delim); NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n); NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv); NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv); @@ -678,12 +697,14 @@ NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv); NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b); NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr); NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix); -NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value); NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr); NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) +static bool nob__sv_contains(Nob_String_View sv, Nob_String_View value); +static Nob_String_View nob__sv_relative_to_absolute(Nob_String_View sv); + // printf macros for String_View #ifndef SV_Fmt #define SV_Fmt "%.*s" @@ -695,41 +716,72 @@ NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); // String_View name = ...; // printf("Name: "SV_Fmt"\n", SV_Arg(name)); -typedef struct Nob_String_Builders { - Nob_String_Builder *items; - size_t count; - size_t capacity; - - size_t alloc_count; -} Nob_String_Builders; - -NOBDEF void nob_string_builders_push_unique(Nob_String_Builders *sbs, Nob_String_View sv); - -typedef struct Nob_Strace_Cache_Indexes { - int *items; - size_t count; - size_t capacity; -} Nob_Strace_Cache_Indexes; -typedef struct Nob_Strace_Cache_Node { - char *argv; - Nob_Strace_Cache_Indexes children; - Nob_String_Builders input_files; - Nob_String_Builders output_files; -} Nob_Strace_Cache_Node; - -typedef struct Nob_Strace_Cache_Nodes { - Nob_Strace_Cache_Node *items; +#if NOB_HAS_PTRACE_CACHE +typedef enum Nob__Ptrace_Cache_Node_Kind { + Nob__Ptrace_Cache_Node_Arg, + Nob__Ptrace_Cache_Node_Stdin, + Nob__Ptrace_Cache_Node_Stdout, + Nob__Ptrace_Cache_Node_Stderr, +} Nob__Ptrace_Cache_Node_Kind; + +typedef struct Nob__Ints { + int *items; + size_t count, capacity; +} Nob__Ints; + +typedef struct Nob__Ptrace_Cache_Node { + int arg_index; + int parent; + Nob__Ptrace_Cache_Node_Kind kind; + // Indexies into Nob_Ptrace_Cache.arena; + Nob__Ints input_paths; + Nob__Ints output_paths; +} Nob__Ptrace_Cache_Node; + +typedef struct Nob__Ptrace_Cache_Nodes { + Nob__Ptrace_Cache_Node *items; size_t count; size_t capacity; -} Nob_Strace_Cache_Nodes; +} Nob__Ptrace_Cache_Nodes; -struct Nob_Strace_Cache { - Nob_Strace_Cache_Nodes nodes; - Nob_Strace_Cache_Indexes roots; +struct Nob_Ptrace_Cache { + Nob__Ptrace_Cache_Nodes nodes; Nob_Cmd temp_cmd; // Don't worry about it... Nob_String_Builder temp_sb; // Don't worry about it... + Nob_String_Builder arena; + + const char *file_path; // File that will be used to read and write collected information for caching + bool is_loaded; + bool is_disabled; + bool no_absolute; + + bool was_last_cached; +}; + +static void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool absolute_paths); +static void nob__ptrace_read_cstr_from_inferior(Nob_String_Builder* out, Nob_Fd inferior, long* addr); +static bool nob__ptrace_cache_is_cached(Nob_Ptrace_Cache *cache, Nob__Ptrace_Cache_Node node); +static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char* stdin_path, const char* stdout_path, const char* stderr_path); +static bool nob__ptrace_cache_read(Nob_Ptrace_Cache *cache); +static bool nob__ptrace_cache_write(Nob_Ptrace_Cache *cache); +// Ptrace Cache TODOS: +// * Use in combination with async - Idk how... +// * Think about other syscalls that a command can make that can invalidate cache: +// * getenv +// * sockets +// * Do we wanna even support those commands? +// * Maybe store hashes of each files in +// * Make the output format such that the first line of command is cd command to the pwd of command. [how_to/nob.c not cachable because of this] +// * How to do this on BSDs? +// * How to do this on Mac? +// * How to do this on Windows? +// * realpath expands symlinks and that means that if the symlink changes, cache will not be invalidated. Do something about this.. +// * Include files that erred while opening into the .cache file +// * Ignore files that erred out. +#else +struct Nob_Ptrace_Cache { const char *file_path; // File that will be used to read and write collected information for caching bool is_loaded; bool is_disabled; @@ -737,6 +789,9 @@ struct Nob_Strace_Cache { bool was_last_cached; }; +#endif + +NOBDEF void nob_ptrace_cache_finish(Nob_Ptrace_Cache cache); // Usage: /* #define NOB_IMPLEMENTATION @@ -745,46 +800,23 @@ struct Nob_Strace_Cache { int main(void) { Nob_Cmd cmd = { 0 }; - Nob_Strace_Cache cache = { .file_path = "nob.cache" }; + Nob_Ptrace_Cache cache = { .file_path = "nob.cache" }; nob_cmd_append(&cmd, "cc", "-o", "main", "main.c"); - nob_cmd_run(&cmd, .strace_cache = &cache); + nob_cmd_run(&cmd, .ptrace_cache = &cache); - nob_strace_cache_finish(cache); + nob_ptrace_cache_finish(cache); nob_cmd_free(cmd); } */ -void nob_strace_cache_finish(Nob_Strace_Cache cache); - -static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd); -static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexes *indexes, bool dup); -static Nob_String_View nob_strace_cache_relative_to_absolute_maybe(Nob_String_View sv, bool for_real); -static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output, bool absolute_paths); -static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd); -static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files, bool absolute_paths); -static bool nob_strace_cache_read(Nob_Strace_Cache *cache); -static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexes roots); -static bool nob_strace_cache_write(Nob_Strace_Cache *cache); -// Strace Cache TODOS: -// * Use in combination with stdin/out - easy -// * Use in combination with stderr -hard -// * Use in combination with async - Idk how... -// * Think about other syscalls that a command can make that can invalidate cache: -// * getenv -// * sockets -// * Do we wanna even support those commands? -// * Maybe store hashes of each files in -// * Make the output format such that the first line of command is cd command to the pwd of command. [how_to/nob.c not cachable because of this] -// * How to do this on BSDs? -// * How to do this on Mac? -// * How to do this on Windows? -// * Check how stable strace output is. -// * realpath expands symlinks and that means that if the symlink changes, cache will not be invalidated. Do something about this.. -// * Use insted of strace utility. Check how to use at https://github.com/strace/strace -// strace is just a program that is not on all distributions. is part of glibc and should be more cross platform. -// * Do something about programs that don't read or write anything.. -// * Handle spaces in command arguments and file paths.. +typedef enum Nob__Ptrace_Cache_Run_Status { + Nob__Ptrace_Cache_Run_Ptrace_Error = -1, + Nob__Ptrace_Cache_Run_False = 0, + Nob__Ptrace_Cache_Run_True = 1, +} Nob__Ptrace_Cache_Run_Status; + +static Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt); // DEPRECATED: Usage of the bundled minirent.h below is deprecated, because it introduces more // problems than it solves. It will be removed in the next major release of nob.h. In the meantime, @@ -1130,61 +1162,14 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1; -#if defined(_WIN32) - // TODO: strace cache Windows - opt.strace_cache = NULL; -#endif - - if (opt.strace_cache && false == opt.strace_cache->is_disabled) { - Nob_Strace_Cache *cache = opt.strace_cache; - if (false == cache->is_loaded) { - if (false == nob_strace_cache_read(cache)) goto disabled_strace_cache; - } - - if (opt.async) NOB_TODO("Strace cache and async is not implemented."); - if (opt.stdin_path) NOB_TODO("Strace cache and stdin_path is not implemented."); - if (opt.stdout_path) NOB_TODO("Strace cache and stdout_path is not implemented."); - if (opt.stderr_path) NOB_TODO("Strace cache and stderr_path is not implemented."); - - cache->temp_cmd.count = 0; - if (true == nob_cmd_strace_is_cached(cache, *cmd)) { - cache->temp_sb.count = 0; - nob_cmd_render(*cmd, &cache->temp_sb); - nob_log(NOB_INFO, "CACHED: %.*s", (int)cache->temp_sb.count, cache->temp_sb.items); - cache->was_last_cached = true; - nob_return_defer(true); - } - cache->was_last_cached = false; - cache->temp_cmd.count = 0; - // TODO: Add -o [Output trace into a file] flag. It's not that easy becase it will create out-file. foreach pid that is created by the command. - // So that would imply that we need to read multiple files in a directory... - // Folow forks Only trace openat syscalls Don't trace failed syscalls - nob_cmd_append(&cache->temp_cmd, "strace", "-ff" , "-e", "trace=openat" , "-z" ); - for (size_t i = 0; i < cmd->count; ++i) { - nob_cmd_append(&cache->temp_cmd, cmd->items[i]); - } - - const char *strace_path = nob_temp_sprintf("%s.temp", cache->file_path); - if (false == nob_cmd_run(&cache->temp_cmd, .stderr_path = strace_path)) { - if (true == nob_read_entire_file(strace_path, &cache->temp_sb)) { - nob_log(NOB_ERROR, "%.*s", cache->temp_sb.count, cache->temp_sb.items); - } - nob_log(NOB_ERROR, "Failed to run strace: %s", strerror(errno)); - nob_return_defer(false); - } - - Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, *cmd, &cache->roots, true); - nob_strace_cache_parse_output(strace_path, &node->input_files, &node->output_files, &cache->temp_sb, !cache->no_absolute); - // Add a file that was executed because it's not picked up by strace.. - if (nob_file_exists(cmd->items[0]) > 0) { - // But only if it exists, if it doesn't it means that the binary was picked up from the path and that means it's no relevant. - // (maybe...) - nob_string_builders_push_unique(&node->input_files, nob_strace_cache_relative_to_absolute_maybe(nob_sv_from_cstr(cmd->items[0]), !cache->no_absolute)); - } - nob_return_defer(true); + Nob__Ptrace_Cache_Run_Status cache_status = nob__cmd_run_ptrace(cmd, opt); + switch (cache_status) { + case Nob__Ptrace_Cache_Run_True: nob_return_defer(true); + case Nob__Ptrace_Cache_Run_False: nob_return_defer(false); + case Nob__Ptrace_Cache_Run_Ptrace_Error: break; // Ptrace failed so we try running the command without ptrace. + default: NOB_UNREACHABLE("cache_status"); } -disabled_strace_cache: if (opt.async && max_procs > 0) { while (opt.async->count >= max_procs) { for (size_t i = 0; i < opt.async->count; ++i) { @@ -1921,7 +1906,7 @@ NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv) return result; } -NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) +int nob__needs_rebuild_ex(const char *output_path, const char **input_paths, size_t input_paths_count, bool allow_inputs_to_not_exist) { #ifdef _WIN32 BOOL bSuccess; @@ -1953,6 +1938,7 @@ NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); CloseHandle(input_path_fd); if (!bSuccess) { + if (allow_inputs_to_not_exist) continue; nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError())); return -1; } @@ -1976,6 +1962,7 @@ NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, for (size_t i = 0; i < input_paths_count; ++i) { const char *input_path = input_paths[i]; if (stat(input_path, &statbuf) < 0) { + if (allow_inputs_to_not_exist) continue; // NOTE: non-existing input is an error cause it is needed for building in the first place nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno)); return -1; @@ -1989,6 +1976,11 @@ NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, #endif } +NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) +{ + return nob__needs_rebuild_ex(output_path, input_paths, input_paths_count, false); +} + NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path) { return nob_needs_rebuild(output_path, &input_path, 1); @@ -2083,28 +2075,6 @@ NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) return n; } -void nob_string_builders_push_unique(Nob_String_Builders *sbs, Nob_String_View sv) -{ - for (size_t i = 0; i < sbs->count; ++i) { - if (nob_sv_eq(nob_sb_to_sv(sbs->items[i]), sv)) return; - } - - if (sbs->count == sbs->alloc_count) { - sbs->alloc_count += 1; - Nob_String_Builder new_sb = { 0 }; - nob_sb_appendf(&new_sb, "%.*s", (int)sv.count, sv.data); - nob_sb_append_null(&new_sb); - new_sb.count -= 1; - nob_da_append(sbs, new_sb); - } else { - Nob_String_Builder *old_sb = &sbs->items[sbs->count++]; - old_sb->count = 0; - nob_sb_appendf(old_sb, "%.*s", (int)sv.count, sv.data); - nob_sb_append_null(old_sb); - old_sb->count -= 1; - } -} - NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) { size_t i = 0; @@ -2125,40 +2095,6 @@ NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) return result; } -NOBDEF Nob_String_View nob_sv_chop_by_delim_sv(Nob_String_View *sv, Nob_String_View delim) -{ - if (delim.count == 0) return nob_sv_from_parts(sv->data, 0); - - size_t i = 0; - - if (sv->count >= delim.count) { - for (; i < sv->count + 1 - delim.count; ++i) { - bool is_match = true; - for (size_t j = 0; j < delim.count; ++j) { - if (sv->data[i + j] != delim.data[j]) { - is_match = false; - break; - } - } - - if (is_match) break; - } - } - Nob_String_View result = nob_sv_from_parts(sv->data, i); - - i += delim.count - 1; - - if (i < sv->count) { - sv->count -= i + 1; - sv->data += i + 1; - } else { - sv->count -= i; - sv->data += i; - } - - return result; -} - NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n) { if (n > sv->count) { @@ -2231,7 +2167,6 @@ NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr) return false; } - NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix) { if (expected_prefix.count <= sv.count) { @@ -2242,7 +2177,7 @@ NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_pref return false; } -NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value) +static bool nob__sv_contains(Nob_String_View sv, Nob_String_View value) { if (sv.count < value.count) return false; @@ -2260,317 +2195,416 @@ NOBDEF bool nob_sv_contains(Nob_String_View sv, Nob_String_View value) return false; } -NOBDEF void nob_strace_cache_finish(Nob_Strace_Cache cache) +static Nob_String_View nob__sv_relative_to_absolute(Nob_String_View sv) +{ +#if _WIN32 + // TODO(ptrace): Windows support + nob_log(NOB_WARNING, "relative_to_absolute not implemented on windows. Returning relative path..."); + return sv; +#elif defined(_GNU_SOURCE) + static char temp_buffer[PATH_MAX]; + const char* temp_input = nob_temp_sv_to_cstr(sv); + if (NULL == realpath(temp_input, temp_buffer)) { + return sv; + } + return nob_sv_from_cstr(temp_buffer); +#else + nob_log(NOB_WARNING, "relative_to_absolute not implemented on non gnu systems. Returning relative path..."); + return sv; +#endif +} + + +NOBDEF void nob_ptrace_cache_finish(Nob_Ptrace_Cache cache) { - nob_strace_cache_write(&cache); - const char* strace_output_path = nob_temp_sprintf("%s.temp", cache.file_path); - if (nob_file_exists(strace_output_path) > 0) { - nob_delete_file(strace_output_path); +#if NOB_HAS_PTRACE_CACHE + nob__ptrace_cache_write(&cache); + const char* ptrace_output_path = nob_temp_sprintf("%s.temp", cache.file_path); + if (nob_file_exists(ptrace_output_path) > 0) { + nob_delete_file(ptrace_output_path); } - nob_da_free(cache.roots); nob_cmd_free(cache.temp_cmd); nob_sb_free(cache.temp_sb); for (size_t i = 0; i < cache.nodes.count; ++i) { - Nob_Strace_Cache_Node node = cache.nodes.items[i]; - nob_da_free(node.children); - for (size_t j = 0; j < node.input_files.alloc_count; ++j) nob_sb_free(node.input_files.items[j]); - for (size_t j = 0; j < node.output_files.alloc_count; ++j) nob_sb_free(node.output_files.items[j]); - free(node.argv); - nob_da_free(node.input_files); - nob_da_free(node.output_files); + Nob__Ptrace_Cache_Node node = cache.nodes.items[i]; + nob_da_free(node.input_paths); + nob_da_free(node.output_paths); } nob_da_free(cache.nodes); + nob_sb_free(cache.arena); +#endif } -static bool nob_cmd_strace_is_cached(Nob_Strace_Cache *cache, Nob_Cmd cmd) -{ - int cmd_index = 0; - int node_index = -1; - - for (size_t i = 0; i < cache->roots.count; ++i) { - int cur_node_index = cache->roots.items[i]; - Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; - if (strcmp(cmd.items[0], node.argv) == 0) { - node_index = cur_node_index; - } - } - ++cmd_index; - - if (node_index < 0) return false; - Nob_Strace_Cache_Node node = cache->nodes.items[node_index]; - while (cmd_index < (int)cmd.count) { - Nob_Strace_Cache_Node child_node; - int child_index; - for (size_t i = 0; i < node.children.count; ++i) { - child_index = node.children.items[i]; - child_node = cache->nodes.items[child_index]; - if (strcmp(cmd.items[cmd_index], child_node.argv) == 0) goto found; - } - return false; -found: - node = child_node; - node_index = child_index; - ++cmd_index; - } +#if NOB_HAS_PTRACE_CACHE +static void nob__ptrace_cache_node_push_file(Nob_Ptrace_Cache* cache, Nob__Ints* indexes, const char* file_path) +{ + nob_da_foreach(int, index, indexes) { + if (0 == strcmp(&cache->arena.items[*index], file_path)) return; + } + nob_da_append(indexes, cache->arena.count); + nob_sb_append_cstr(&cache->arena, file_path); + nob_sb_append_null(&cache->arena); +} - cache->temp_cmd.count = 0; - for (size_t i = 0; i < node.input_files.count; ++i) { - nob_cmd_append(&cache->temp_cmd, node.input_files.items[i].items); - } +void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool absolute_paths) +{ + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/usr/"))) return; + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/etc/"))) return; + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/tmp/"))) return; + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/sys/"))) return; + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/proc/"))) return; + if (nob__sv_contains(file_path, nob_sv_from_cstr("/.cache/"))) return; + if (nob__sv_contains(file_path, nob_sv_from_cstr("/run/"))) return; + if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/dev/"))) return; - for (size_t i = 0; i < node.output_files.count; ++i) { - if (nob_needs_rebuild(node.output_files.items[i].items, cache->temp_cmd.items, cache->temp_cmd.count)) return false; - } + if (absolute_paths) file_path = nob__sv_relative_to_absolute(file_path); - return true; + int output_mask = O_APPEND | O_CREAT | O_WRONLY; + if (output_mask & mode) nob__ptrace_cache_node_push_file(cache, &node->output_paths, file_path.data); + else nob__ptrace_cache_node_push_file(cache, &node->input_paths, file_path.data); } -static Nob_Strace_Cache_Node *nob_cmd_strace_cache_node(Nob_Strace_Cache *cache, Nob_Cmd cmd, Nob_Strace_Cache_Indexes *indexes, bool dup) +void nob__ptrace_read_cstr_from_inferior(Nob_String_Builder* out, Nob_Fd inferior, long* addr) { - NOB_ASSERT(cmd.count != 0); + out->count = 0; - int node_index = -1; + // While reading the docs I didn't find anything more humane than this... + while (true) { + union { + long value; + char buff[sizeof(long)]; + } temp; - for (size_t i = 0; i < indexes->count; ++i) { - int cur_node_index = indexes->items[i]; - Nob_Strace_Cache_Node node = cache->nodes.items[cur_node_index]; - if (strcmp(cmd.items[0], node.argv) == 0) { - node_index = cur_node_index; - break; + temp.value = ptrace(PTRACE_PEEKTEXT, inferior, addr++, NULL); + for (size_t i = 0; i < sizeof(long); ++i) { + nob_da_append(out, temp.buff[i]); + if (0 == temp.buff[i]) return; } } +} - Nob_Strace_Cache_Node *next = NULL; - if (node_index == -1) { - Nob_Strace_Cache_Node new_node = { - .argv = dup ? strdup(cmd.items[0]) : (char*)cmd.items[0], - }; - nob_da_append(indexes, cache->nodes.count); - nob_da_append(&cache->nodes, new_node); - next = &nob_da_last(&cache->nodes); - } else { - // TODO: Nasty hack. If it needs to be free but the place is already taken... - if (false == dup) free((char*)cmd.items[0]); - next = &cache->nodes.items[node_index]; - } +static bool nob__ptrace_cache_is_cached(Nob_Ptrace_Cache *cache, Nob__Ptrace_Cache_Node node) +{ + if (node.input_paths.count == 0 && node.output_paths.count == 0) return false; - if (cmd.count == 1) return next; + cache->temp_cmd.count = 0; + for (size_t i = 0; i < node.input_paths.count; ++i) { + int index = node.input_paths.items[i]; + nob_cmd_append(&cache->temp_cmd, &cache->arena.items[index]); + } - ++cmd.items; - --cmd.count; + for (size_t i = 0; i < node.output_paths.count; ++i) { + int index = node.output_paths.items[i]; + if (nob__needs_rebuild_ex(&cache->arena.items[index], cache->temp_cmd.items, cache->temp_cmd.count, true)) return false; + } - return nob_cmd_strace_cache_node(cache, cmd, &next->children, dup); + return true; } -static Nob_String_View nob_strace_cache_relative_to_absolute_maybe(Nob_String_View sv, bool for_real) +static int nob__ptrace_cache_node2(Nob_Ptrace_Cache *cache, const char *to_find, int parent, Nob__Ptrace_Cache_Node_Kind kind) { - if (!for_real) return sv; + int found_index = -1; - // TODO: Windows support -#if _WIN32 - nob_log(NOB_WARNING, "relative_to_absolute not implemented on windows. Returning relative path..."); - return sv; -#else - static char temp_buffer[PATH_MAX]; - const char* temp_input = nob_temp_sv_to_cstr(sv); - if (NULL == realpath(temp_input, temp_buffer)) { - nob_log(NOB_WARNING, "Failed to get an absolute path for %s: %s. Using relative", temp_input, strerror(errno)); - return sv; + for (size_t i = 0; i < cache->nodes.count; ++i) { + Nob__Ptrace_Cache_Node node = cache->nodes.items[i]; + if (node.parent != parent) continue; + if (node.kind != kind) continue; + if (0 != strcmp(to_find, &cache->arena.items[node.arg_index])) continue; + found_index = i; + break; } - return nob_sv_from_cstr(temp_buffer); -#endif + + if (found_index >= 0) return found_index; + int index = cache->arena.count; + nob_sb_append_cstr(&cache->arena, to_find); + nob_sb_append_null(&cache->arena); + Nob__Ptrace_Cache_Node new_node = { + .arg_index = index, + .parent = parent, + .kind = kind + }; + nob_da_append(&cache->nodes, new_node); + return cache->nodes.count - 1; } -static bool nob_strace_cache_parse_output(const char *strace_output_path, Nob_String_Builders *input_files, Nob_String_Builders *output_files, Nob_String_Builder *strace_output, bool absolute_paths) -{ - strace_output->count = 0; - if (!nob_read_entire_file(strace_output_path, strace_output)) return false; - - Nob_String_View iter = nob_sb_to_sv(*strace_output); - Nob_String_View openat_delim = nob_sv_from_cstr("openat("); - Nob_String_View _ = { 0 }; - (void)_; - - input_files->count = 0; - output_files->count = 0; - while (iter.count > 0) { - _ = nob_sv_chop_by_delim_sv(&iter, openat_delim); - _ = nob_sv_chop_by_delim(&iter, '"'); - Nob_String_View path = nob_sv_chop_by_delim(&iter, '"'); - if (path.count == 0) break; - - if (nob_sv_starts_with(path, nob_sv_from_cstr("/usr/"))) continue; // SKIP system files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/etc/"))) continue; // SKIP etc files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/tmp/"))) continue; // SKIP tmp files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/sys/"))) continue; // SKIP sys files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/proc/"))) continue; // SKIP proc files... - if (nob_sv_contains(path, nob_sv_from_cstr("/.cache/"))) continue; // SKIP cache files... - if (nob_sv_contains(path, nob_sv_from_cstr("/run/"))) continue; // SKIP cache files... - if (nob_sv_starts_with(path, nob_sv_from_cstr("/dev/"))) continue; // SKIP dev files... - // TODO: Think about adding a flag to enable system/etc/tmp files - - Nob_String_View rw_args = nob_sv_chop_by_delim(&iter, ')'); - if (rw_args.count == 0) break; - - bool is_read = nob_sv_contains(rw_args, nob_sv_from_cstr("O_RDONLY")); - bool is_write = nob_sv_contains(rw_args, nob_sv_from_cstr("O_RDWR")) || - nob_sv_contains(rw_args, nob_sv_from_cstr("O_WRONLY")); - - if (is_write) { - nob_string_builders_push_unique(output_files, nob_strace_cache_relative_to_absolute_maybe(path, absolute_paths)); - } else if (is_read) { - nob_string_builders_push_unique(input_files, nob_strace_cache_relative_to_absolute_maybe(path, absolute_paths)); - } else { - nob_log(NOB_WARNING, "Unknown rw_args(%zu): `%.*s`", rw_args.count, (int)rw_args.count, rw_args.data); - return false; - } +static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char *stdin_path, const char *stdout_path, const char *stderr_path) +{ + int current = -1; + for (size_t i = 0; i < cmd.count; ++i) { + current = nob__ptrace_cache_node2(cache, cmd.items[i], current, Nob__Ptrace_Cache_Node_Arg); } - return true; + if (stdin_path) current = nob__ptrace_cache_node2(cache, stdin_path, current, Nob__Ptrace_Cache_Node_Stdin); + if (stdout_path) current = nob__ptrace_cache_node2(cache, stdout_path, current, Nob__Ptrace_Cache_Node_Stdout); + if (stderr_path) current = nob__ptrace_cache_node2(cache, stderr_path, current, Nob__Ptrace_Cache_Node_Stderr); + + return &cache->nodes.items[current]; } -static bool nob_strace_cache_parse_cmd(Nob_String_View sv, Nob_Cmd *cmd) +static bool nob__ptrace_cache_write(Nob_Ptrace_Cache *cache) { - // TODO: Handle spaces.. - sv = nob_sv_trim_left(sv); - while (sv.count > 0) - { - Nob_String_View arg = nob_sv_chop_by_delim(&sv, ' '); - char *arg_dup = strdup(nob_temp_sprintf("%.*s", (int)arg.count, arg.data)); - nob_cmd_append(cmd, arg_dup); - sv = nob_sv_trim_left(sv); + bool result = false; + Nob_Fd fd = nob_fd_open_for_write(cache->file_path); + if (NOB_INVALID_FD == fd) nob_return_defer(false); + +#define NOB__FD_WRITE_N(VALUE, N) do { \ + if (((ssize_t)(N) * (ssize_t)sizeof((VALUE))) != write(fd, &(VALUE), (N) * sizeof((VALUE)))) { \ + nob_log(NOB_ERROR, "Failed to write %s: %s", #VALUE, strerror(errno)); \ + nob_return_defer(false); \ + } \ +} while (0) +#define NOB__FD_WRITE(VALUE) NOB__FD_WRITE_N(VALUE, 1); + + NOB__FD_WRITE(cache->arena.count); + NOB__FD_WRITE_N(*cache->arena.items, cache->arena.count); + + NOB__FD_WRITE(cache->nodes.count); + for (size_t i = 0; i < cache->nodes.count; ++i) { + Nob__Ptrace_Cache_Node node = cache->nodes.items[i]; + NOB__FD_WRITE(node.arg_index); + NOB__FD_WRITE(node.parent); + NOB__FD_WRITE(node.kind); + NOB__FD_WRITE(node.input_paths.count); + NOB__FD_WRITE_N(node.input_paths.items[0], node.input_paths.count); + NOB__FD_WRITE(node.output_paths.count); + NOB__FD_WRITE_N(node.output_paths.items[0], node.output_paths.count); } - return true; + +defer: + if (NOB_INVALID_FD != fd) nob_fd_close(fd); + return result; +#undef NOB__FD_WRITE_N +#undef NOB__FD_WRITE } -static bool nob_strace_cache_parse_file_list(Nob_String_View sv, Nob_String_Builders *files, bool absolute_paths) +static bool nob__ptrace_cache_read(Nob_Ptrace_Cache *cache) { - // TODO: Handle spaces.. - sv = nob_sv_trim_left(sv); - while (sv.count > 0) - { - Nob_String_View file_view = nob_sv_chop_by_delim(&sv, ' '); - Nob_String_View arg = nob_sv_from_cstr(nob_temp_sprintf("%.*s", (int)file_view.count, file_view.data)); - nob_string_builders_push_unique(files, nob_strace_cache_relative_to_absolute_maybe(arg, absolute_paths)); - sv = nob_sv_trim_left(sv); + Nob_Fd fd; + bool result = true; + int exists; + + cache->is_loaded = true; + + exists = nob_file_exists(cache->file_path); + if (0 == exists) return true; + else if (-1 == exists) return false; + + fd = nob_fd_open_for_read(cache->file_path); + if (NOB_INVALID_FD == fd) nob_return_defer(false); + +#define NOB__FD_READ_N(VALUE, N) do { \ + if (((ssize_t)(N) * (ssize_t)sizeof((VALUE))) != read(fd, &(VALUE), (N) * sizeof((VALUE)))) { \ + nob_log(NOB_ERROR, "Failed to read %s: %s", #VALUE, strerror(errno)); \ + nob_return_defer(false); \ + } \ +} while (0) +#define NOB__FD_READ(VALUE) NOB__FD_READ_N(VALUE, 1); + + NOB__FD_READ(cache->arena.count); + if (cache->arena.count > 1<<30) nob__return_defer_msg(false, "Ptrace cache invalid: Arena size = %zu GB", cache->arena.count >> 30ULL); + nob_da_reserve(&cache->arena, cache->arena.count); + NOB__FD_READ_N(cache->arena.items[0], cache->arena.count); + + NOB__FD_READ(cache->nodes.count); + if (cache->nodes.count > cache->arena.count) nob__return_defer_msg(false, "Ptrace cache invlid: %zu nodes, %zu arena count", cache->nodes.count, cache->arena.count); + nob_da_reserve(&cache->nodes, cache->nodes.count); + for (size_t i = 0; i < cache->nodes.count; ++i) { + Nob__Ptrace_Cache_Node *node = &cache->nodes.items[i]; + + NOB__FD_READ(node->arg_index); + if (node->arg_index < 0 || node->arg_index >= (int)cache->arena.count) nob__return_defer_msg(false, "Ptrace cache invalid: %d arg_index", node->arg_index); + NOB__FD_READ(node->parent); + if (node->parent < -1 || node->parent > (int)cache->nodes.count) nob__return_defer_msg(false, "Ptrace cache invalid: %d parent index", node->parent); + NOB__FD_READ(node->kind); + if (node->kind != Nob__Ptrace_Cache_Node_Arg && + node->kind != Nob__Ptrace_Cache_Node_Stdin && + node->kind != Nob__Ptrace_Cache_Node_Stdout && + node->kind != Nob__Ptrace_Cache_Node_Stderr) nob__return_defer_msg(false, "Ptrace cache invalid: %d node kind", node->kind); + + NOB__FD_READ(node->input_paths.count); + if (node->input_paths.count > cache->arena.count) { + nob__return_defer_msg(false, "Ptrace cache invalid: %zu input files, %zu arena size", node->input_paths.count, cache->arena.count); + } + nob_da_reserve(&node->input_paths, node->input_paths.count); + NOB__FD_READ_N(node->input_paths.items[0], node->input_paths.count); + // TODO(ptrace): validate all input_paths + + NOB__FD_READ(node->output_paths.count); + if (node->output_paths.count > cache->arena.count) { + nob__return_defer_msg(false, "Ptrace cache invalid: %zu output files, %zu arena size", node->output_paths.count, cache->arena.count); + } + nob_da_reserve(&node->output_paths, node->input_paths.count); + NOB__FD_READ_N(node->output_paths.items[0], node->output_paths.count); + // TODO(ptrace): validate all output_paths + } + +defer: + if (NOB_INVALID_FD != fd) nob_fd_close(fd); + if (false == result) { + // TODO(ptrace): Leak + memset(&cache->nodes, 0, sizeof(cache->nodes)); + memset(&cache->arena, 0, sizeof(cache->arena)); } return true; +#undef NOB__FD_READ_N +#undef NOB__FD_READ } -static bool nob_strace_cache_read(Nob_Strace_Cache *cache) +#endif // NOB_HAS_PTRACE_CACHE + +Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) { - NOB_ASSERT(cache->is_loaded == false); - cache->is_loaded = true; +#if NOB_HAS_PTRACE_CACHE + if (NULL == opt.ptrace_cache || opt.ptrace_cache->is_disabled) return Nob__Ptrace_Cache_Run_Ptrace_Error; - // Check if strace exists on the system - Nob_Cmd cmd = { 0 }; - nob_cmd_append(&cmd, "strace", "-V"); - bool test_strace_result = nob_cmd_run(&cmd); - nob_cmd_free(cmd); - if (false == test_strace_result) { - nob_log(NOB_WARNING, "Failed to run `strace -V` Continuing without caching."); - cache->is_disabled = true; - return false; + Nob_Ptrace_Cache *cache = opt.ptrace_cache; + if (false == cache->is_loaded) { + if (false == nob__ptrace_cache_read(cache)) return Nob__Ptrace_Cache_Run_Ptrace_Error; } - if (NULL == cache->file_path) return true; + if (opt.async) NOB_TODO("Ptrace cache and async is not implemented."); - int exists = nob_file_exists(cache->file_path); - if (exists == -1) return false; + Nob__Ptrace_Cache_Node *node = nob__ptrace_cache_node(cache, *cmd, opt.stdin_path, opt.stdout_path, opt.stderr_path); - // File doesn't exist but that is ok because, maybe, it's the first time building. - // And we don't need to do anything else. - if (exists == 0) { - nob_log(NOB_INFO, "Cache Cold"); - return true; + bool is_cached = nob__ptrace_cache_is_cached(cache, *node); + cache->temp_sb.count = 0; + if (is_cached) { + nob_cmd_render(*cmd, &cache->temp_sb); + nob_log(NOB_INFO, "CACHED: %.*s", (int)cache->temp_sb.count, cache->temp_sb.items); + cache->was_last_cached = true; + return Nob__Ptrace_Cache_Run_True; + } else { + nob_cmd_render(*cmd, &cache->temp_sb); + nob_log(NOB_INFO, "PTRACE CMD: %.*s", (int)cache->temp_sb.count, cache->temp_sb.items); } + cache->was_last_cached = false; + cache->temp_cmd.count = 0; + node->input_paths.count = 0; + node->output_paths.count = 0; - if (false == nob_read_entire_file(cache->file_path, &cache->temp_sb)) return false; + pid_t cpid = fork(); + if (cpid < 0) { + nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } - Nob_String_View iter = nob_sb_to_sv(cache->temp_sb); - iter = nob_sv_trim_left(iter); - while (iter.count > 0) { - Nob_String_View outputs = nob_sv_chop_by_delim(&iter, ':'); - if (outputs.count == 0) { - nob_log(NOB_ERROR, "Failed to read outputs, expected:\n: \n\t\n"); - return false; - } - if (iter.count == 0) { - nob_log(NOB_ERROR, "Expected inputs after outputs:\n: \n\t\n"); - return false; + if (cpid == 0) { + int ret = ptrace(PTRACE_TRACEME, 0, NULL, NULL); + if (ret < 0) { + nob_log(NOB_ERROR, "Failed to ptrace: %s", strerror(errno)); + exit(1); } + raise(SIGSTOP); - Nob_String_View inputs = nob_sv_chop_by_delim(&iter, '\n'); - if (inputs.count == 0) { - nob_log(NOB_ERROR, "Failed to read inputs, expected:\n: \n\t\n"); - return false; + if (opt.stdin_path) { + if (dup2(nob_fd_open_for_read(opt.stdin_path), STDIN_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno)); + exit(1); + } } - if (iter.count == 0) { - nob_log(NOB_ERROR, "Expected command after inputs:\n: \n\t\n"); - return false; + + if (opt.stdout_path) { + if (dup2(nob_fd_open_for_write(opt.stdout_path), STDOUT_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno)); + exit(1); + } } - Nob_String_View command = iter; - Nob_String_View command2 = nob_sv_chop_by_delim(&iter, '\n'); - // Handle the case where file ends without new line - if (command2.count > 0) command = command2; + if (opt.stderr_path) { + if (dup2(nob_fd_open_for_write(opt.stderr_path), STDERR_FILENO) < 0) { + nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); + exit(1); + } + } + // TODO(ptrace): Leak + nob_cmd_append(cmd, NULL); - if (false == nob_strace_cache_parse_cmd(command, &cache->temp_cmd)) return false; - Nob_Strace_Cache_Node *node = nob_cmd_strace_cache_node(cache, cache->temp_cmd, &cache->roots, false); - if (false == nob_strace_cache_parse_file_list(inputs, &node->input_files, !cache->no_absolute)) { - nob_log(NOB_ERROR, "Failed to parse input file list: `%.*s`", (int)inputs.count, inputs.data); - return false; + if (execvp(cmd->items[0], (char * const*) cmd->items) < 0) { + nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd->items[0], strerror(errno)); + exit(1); } - if (false == nob_strace_cache_parse_file_list(outputs, &node->output_files, !cache->no_absolute)) { - nob_log(NOB_ERROR, "Failed to parse output file list: `%.*s`", (int)outputs.count, outputs.data); - return false; + NOB_UNREACHABLE("nob_cmd_run_async_redirect"); + } else { + void* addr = 0; + int ret, child_count = 1, status = 0; + long ptrace_data; + Nob_Fd cur_child = waitpid(-1, &status, 0); + Nob_String_Builder file_path = { 0 }; + + ret = ptrace(PTRACE_SETOPTIONS, cur_child, 1, PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE); + if (ret < 0) { + nob_log(NOB_ERROR, "Failed to set ptrace options: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; } - cache->temp_cmd.count = 0; - iter = nob_sv_trim_left(iter); - } - return true; -} -static bool nob_strace_cache_write_rec(Nob_Strace_Cache *cache, Nob_String_Builder *out, Nob_Strace_Cache_Indexes roots) -{ - size_t old_count = cache->temp_sb.count; - for (size_t i = 0; i < roots.count; ++i) { - Nob_Strace_Cache_Node node = cache->nodes.items[roots.items[i]]; - nob_sb_append_cstr(&cache->temp_sb, node.argv); - if (node.input_files.count > 0 || node.output_files.count > 0) { - for (size_t j = 0; j < node.output_files.count; ++j) { - if (j + 1 < node.output_files.count) nob_sb_appendf(out, "%.*s ", (int)node.output_files.items[j].count, node.output_files.items[j].items); - else nob_sb_appendf(out, "%.*s: ", (int)node.output_files.items[j].count, node.output_files.items[j].items); + ret = ptrace(PTRACE_SYSCALL, cur_child, addr, NULL); + if (ret < 0) { + nob_log(NOB_ERROR, "Failed to continue child process using ptrace: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } + + while (child_count > 0) { + cur_child = waitpid(-1, &status, 0); + if (WIFEXITED(status)) { + child_count -= 1; + continue; } - for (size_t j = 0; j < node.input_files.count; ++j) { - if (j + 1 < node.input_files.count) nob_sb_appendf(out, "%.*s ", (int)node.input_files.items[j].count, node.input_files.items[j].items); - else nob_sb_appendf(out, "%.*s\n\t", (int)node.input_files.items[j].count, node.input_files.items[j].items); + + status >>= 16; + bool is_fork = (status & PTRACE_EVENT_FORK) > 0; + bool is_vfork = (status & PTRACE_EVENT_VFORK) > 0; + bool is_clone = (status & PTRACE_EVENT_CLONE) > 0; + if (is_fork || is_vfork || is_clone) { + child_count += 1; + Nob_Fd gchild; + if (0 > ptrace(PTRACE_GETEVENTMSG, cur_child, 0, &ptrace_data)) { + nob_log(NOB_ERROR, "Failed to get grand child pid: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } + gchild = ptrace_data; + if (0 > ptrace(PTRACE_SYSCALL, cur_child, addr, NULL)) { + nob_log(NOB_ERROR, "Failed to continue process after syscall: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } + cur_child = gchild; + cur_child = waitpid(cur_child, &status, 0); + if (0 > ptrace(PTRACE_SYSCALL, cur_child, addr, NULL)) { + nob_log(NOB_ERROR, "Failed to continue grand child process after it was forked: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } + continue; } - if (node.input_files.count == 0) nob_sb_appendf(out, "\n\t"); - nob_sb_appendf(out, "%.*s\n\n", (int)cache->temp_sb.count, cache->temp_sb.items); - } else { - // TODO: if an arg contains space or other whitespaces, escape them.. - nob_sb_append_cstr(&cache->temp_sb, " "); - nob_strace_cache_write_rec(cache, out, node.children); - } - cache->temp_sb.count = old_count; - } - return true; -} + struct ptrace_syscall_info sc = { 0 }; + if (0 > ptrace(PTRACE_GET_SYSCALL_INFO, cur_child, sizeof(sc), &sc)) { + nob_log(NOB_ERROR, "Failed to get syscall info: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } -static bool nob_strace_cache_write(Nob_Strace_Cache *cache) -{ - Nob_String_Builder out = { 0 }; - cache->temp_sb.count = 0; - if (false == nob_strace_cache_write_rec(cache, &out, cache->roots)) return false; - if (false == nob_write_entire_file(cache->file_path, out.items, out.count)) return false; - nob_sb_free(out); - return true; -} + if (sc.op == PTRACE_SYSCALL_INFO_ENTRY) { + if (sc.entry.nr == /* sys_openat */257) { + long* file_path_in_inferior = (long*)sc.entry.args[1]; + nob__ptrace_read_cstr_from_inferior(&file_path, cur_child, file_path_in_inferior); + unsigned long long mode = sc.entry.args[2]; + nob__ptrace_append_file(cache, node, nob_sb_to_sv(file_path), mode, false == cache->no_absolute); + } + } + if (0 > ptrace(PTRACE_SYSCALL, cur_child, addr, NULL)) { + nob_log(NOB_ERROR, "Failed to get syscall info: %s", strerror(errno)); + return Nob__Ptrace_Cache_Run_Ptrace_Error; + } + } + nob_sb_free(file_path); + if (WEXITSTATUS(status) == 0) return Nob__Ptrace_Cache_Run_True; + else return Nob__Ptrace_Cache_Run_False; + } +#else + return Nob__Ptrace_Cache_Run_Ptrace_Error; +#endif // NOB_HAS_PTRACE_CACHE +} // RETURNS: // 0 - file does not exists From 67145ac09c0522d3c8d529eebed75724edeeb1fc Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 6 Sep 2025 08:18:32 +0200 Subject: [PATCH 14/18] Fixed one memory leak. Resume inferior faster. Don't include input files that don't exist. --- nob.h | 53 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/nob.h b/nob.h index 19636b3..2fb21ac 100644 --- a/nob.h +++ b/nob.h @@ -759,7 +759,7 @@ struct Nob_Ptrace_Cache { bool was_last_cached; }; -static void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool absolute_paths); +static void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool exists, bool absolute_paths); static void nob__ptrace_read_cstr_from_inferior(Nob_String_Builder* out, Nob_Fd inferior, long* addr); static bool nob__ptrace_cache_is_cached(Nob_Ptrace_Cache *cache, Nob__Ptrace_Cache_Node node); static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char* stdin_path, const char* stdout_path, const char* stderr_path); @@ -1167,7 +1167,7 @@ NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) case Nob__Ptrace_Cache_Run_True: nob_return_defer(true); case Nob__Ptrace_Cache_Run_False: nob_return_defer(false); case Nob__Ptrace_Cache_Run_Ptrace_Error: break; // Ptrace failed so we try running the command without ptrace. - default: NOB_UNREACHABLE("cache_status"); + default: NOB_UNREACHABLE("Nob__Ptrace_Cache_Run_Status"); } if (opt.async && max_procs > 0) { @@ -2248,7 +2248,7 @@ static void nob__ptrace_cache_node_push_file(Nob_Ptrace_Cache* cache, Nob__Ints* nob_sb_append_null(&cache->arena); } -void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool absolute_paths) +void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool exists, bool absolute_paths) { if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/usr/"))) return; if (nob_sv_starts_with(file_path, nob_sv_from_cstr("/etc/"))) return; @@ -2263,7 +2263,7 @@ void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* no int output_mask = O_APPEND | O_CREAT | O_WRONLY; if (output_mask & mode) nob__ptrace_cache_node_push_file(cache, &node->output_paths, file_path.data); - else nob__ptrace_cache_node_push_file(cache, &node->input_paths, file_path.data); + else if (exists) nob__ptrace_cache_node_push_file(cache, &node->input_paths, file_path.data); } void nob__ptrace_read_cstr_from_inferior(Nob_String_Builder* out, Nob_Fd inferior, long* addr) @@ -2424,20 +2424,24 @@ static bool nob__ptrace_cache_read(Nob_Ptrace_Cache *cache) node->kind != Nob__Ptrace_Cache_Node_Stderr) nob__return_defer_msg(false, "Ptrace cache invalid: %d node kind", node->kind); NOB__FD_READ(node->input_paths.count); - if (node->input_paths.count > cache->arena.count) { - nob__return_defer_msg(false, "Ptrace cache invalid: %zu input files, %zu arena size", node->input_paths.count, cache->arena.count); + if (node->input_paths.count > 0) { + if (node->input_paths.count > cache->arena.count) { + nob__return_defer_msg(false, "Ptrace cache invalid: %zu input files, %zu arena size", node->input_paths.count, cache->arena.count); + } + nob_da_reserve(&node->input_paths, node->input_paths.count); + NOB__FD_READ_N(node->input_paths.items[0], node->input_paths.count); + // TODO(ptrace): validate all input_paths } - nob_da_reserve(&node->input_paths, node->input_paths.count); - NOB__FD_READ_N(node->input_paths.items[0], node->input_paths.count); - // TODO(ptrace): validate all input_paths NOB__FD_READ(node->output_paths.count); - if (node->output_paths.count > cache->arena.count) { - nob__return_defer_msg(false, "Ptrace cache invalid: %zu output files, %zu arena size", node->output_paths.count, cache->arena.count); + if (node->output_paths.count > 0) { + if (node->output_paths.count > cache->arena.count) { + nob__return_defer_msg(false, "Ptrace cache invalid: %zu output files, %zu arena size", node->output_paths.count, cache->arena.count); + } + nob_da_reserve(&node->output_paths, node->output_paths.count); + NOB__FD_READ_N(node->output_paths.items[0], node->output_paths.count); + // TODO(ptrace): validate all output_paths } - nob_da_reserve(&node->output_paths, node->input_paths.count); - NOB__FD_READ_N(node->output_paths.items[0], node->output_paths.count); - // TODO(ptrace): validate all output_paths } defer: @@ -2464,7 +2468,7 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) if (false == nob__ptrace_cache_read(cache)) return Nob__Ptrace_Cache_Run_Ptrace_Error; } - if (opt.async) NOB_TODO("Ptrace cache and async is not implemented."); + if (opt.async) NOB_TODO("Ptrace cache and async is not implemented."); Nob__Ptrace_Cache_Node *node = nob__ptrace_cache_node(cache, *cmd, opt.stdin_path, opt.stdout_path, opt.stderr_path); @@ -2484,9 +2488,12 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) node->input_paths.count = 0; node->output_paths.count = 0; + nob_cmd_append(cmd, NULL); + pid_t cpid = fork(); if (cpid < 0) { nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); + cmd->count -= 1; return Nob__Ptrace_Cache_Run_Ptrace_Error; } @@ -2518,8 +2525,6 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) exit(1); } } - // TODO(ptrace): Leak - nob_cmd_append(cmd, NULL); if (execvp(cmd->items[0], (char * const*) cmd->items) < 0) { nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd->items[0], strerror(errno)); @@ -2527,12 +2532,15 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) } NOB_UNREACHABLE("nob_cmd_run_async_redirect"); } else { + cmd->count -= 1; + void* addr = 0; int ret, child_count = 1, status = 0; long ptrace_data; Nob_Fd cur_child = waitpid(-1, &status, 0); Nob_String_Builder file_path = { 0 }; + ret = ptrace(PTRACE_SETOPTIONS, cur_child, 1, PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE); if (ret < 0) { nob_log(NOB_ERROR, "Failed to set ptrace options: %s", strerror(errno)); @@ -2583,12 +2591,14 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) return Nob__Ptrace_Cache_Run_Ptrace_Error; } + bool should_add = false; + unsigned long long mode; if (sc.op == PTRACE_SYSCALL_INFO_ENTRY) { if (sc.entry.nr == /* sys_openat */257) { long* file_path_in_inferior = (long*)sc.entry.args[1]; nob__ptrace_read_cstr_from_inferior(&file_path, cur_child, file_path_in_inferior); - unsigned long long mode = sc.entry.args[2]; - nob__ptrace_append_file(cache, node, nob_sb_to_sv(file_path), mode, false == cache->no_absolute); + mode = sc.entry.args[2]; + should_add = true; } } @@ -2596,6 +2606,11 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) nob_log(NOB_ERROR, "Failed to get syscall info: %s", strerror(errno)); return Nob__Ptrace_Cache_Run_Ptrace_Error; } + + if (should_add) { + bool exists = nob_file_exists(file_path.items); + nob__ptrace_append_file(cache, node, nob_sb_to_sv(file_path), mode, exists, false == cache->no_absolute); + } } nob_sb_free(file_path); if (WEXITSTATUS(status) == 0) return Nob__Ptrace_Cache_Run_True; From 81dd957a0842d5f47242cf3ed02a187053fbed33 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 6 Sep 2025 18:56:32 +0200 Subject: [PATCH 15/18] Ptrace only on linux. Include cwd into command cache entry. --- nob.h | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/nob.h b/nob.h index 2fb21ac..df24116 100644 --- a/nob.h +++ b/nob.h @@ -167,16 +167,8 @@ # include #endif -#if defined(__has_include) -# define NOB__HAS_INCLUDE(path) __has_include(path) -#else -# define NOB__HAS_INCLUDE(path) -#endif - -#if !defined(NOB_HAS_PTRACE_CACHE) -# if NOB__HAS_INCLUDE() -# define NOB_HAS_PTRACE_CACHE 1 -# endif +#if !defined(NOB_HAS_PTRACE_CACHE) && defined(__linux__) +# define NOB_HAS_PTRACE_CACHE 1 #endif #if NOB_HAS_PTRACE_CACHE @@ -719,6 +711,7 @@ static Nob_String_View nob__sv_relative_to_absolute(Nob_String_View sv); #if NOB_HAS_PTRACE_CACHE typedef enum Nob__Ptrace_Cache_Node_Kind { + Nob__Ptrace_Cache_Node_Cwd, Nob__Ptrace_Cache_Node_Arg, Nob__Ptrace_Cache_Node_Stdin, Nob__Ptrace_Cache_Node_Stdout, @@ -762,7 +755,7 @@ struct Nob_Ptrace_Cache { static void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool exists, bool absolute_paths); static void nob__ptrace_read_cstr_from_inferior(Nob_String_Builder* out, Nob_Fd inferior, long* addr); static bool nob__ptrace_cache_is_cached(Nob_Ptrace_Cache *cache, Nob__Ptrace_Cache_Node node); -static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char* stdin_path, const char* stdout_path, const char* stderr_path); +static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char* cwd, const char* stdin_path, const char* stdout_path, const char* stderr_path); static bool nob__ptrace_cache_read(Nob_Ptrace_Cache *cache); static bool nob__ptrace_cache_write(Nob_Ptrace_Cache *cache); // Ptrace Cache TODOS: @@ -1969,7 +1962,10 @@ int nob__needs_rebuild_ex(const char *output_path, const char **input_paths, siz } int input_path_time = statbuf.st_mtime; // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild - if (input_path_time > output_path_time) return 1; + if (input_path_time > output_path_time) { + nob_log(NOB_INFO, "File %s is fresher than %s", input_path, output_path); + return 1; + } } return 0; @@ -2201,7 +2197,7 @@ static Nob_String_View nob__sv_relative_to_absolute(Nob_String_View sv) // TODO(ptrace): Windows support nob_log(NOB_WARNING, "relative_to_absolute not implemented on windows. Returning relative path..."); return sv; -#elif defined(_GNU_SOURCE) +#elif _XOPEN_SOURCE >= 500 || _DEFAULT_SOURCE || _BSD_SOURCE static char temp_buffer[PATH_MAX]; const char* temp_input = nob_temp_sv_to_cstr(sv); if (NULL == realpath(temp_input, temp_buffer)) { @@ -2329,9 +2325,10 @@ static int nob__ptrace_cache_node2(Nob_Ptrace_Cache *cache, const char *to_find, return cache->nodes.count - 1; } -static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char *stdin_path, const char *stdout_path, const char *stderr_path) +static Nob__Ptrace_Cache_Node *nob__ptrace_cache_node(Nob_Ptrace_Cache *cache, Nob_Cmd cmd, const char *cwd, const char *stdin_path, const char *stdout_path, const char *stderr_path) { - int current = -1; + int current = nob__ptrace_cache_node2(cache, cwd, -1, Nob__Ptrace_Cache_Node_Cwd); + for (size_t i = 0; i < cmd.count; ++i) { current = nob__ptrace_cache_node2(cache, cmd.items[i], current, Nob__Ptrace_Cache_Node_Arg); } @@ -2418,7 +2415,8 @@ static bool nob__ptrace_cache_read(Nob_Ptrace_Cache *cache) NOB__FD_READ(node->parent); if (node->parent < -1 || node->parent > (int)cache->nodes.count) nob__return_defer_msg(false, "Ptrace cache invalid: %d parent index", node->parent); NOB__FD_READ(node->kind); - if (node->kind != Nob__Ptrace_Cache_Node_Arg && + if (node->kind != Nob__Ptrace_Cache_Node_Cwd && + node->kind != Nob__Ptrace_Cache_Node_Arg && node->kind != Nob__Ptrace_Cache_Node_Stdin && node->kind != Nob__Ptrace_Cache_Node_Stdout && node->kind != Nob__Ptrace_Cache_Node_Stderr) nob__return_defer_msg(false, "Ptrace cache invalid: %d node kind", node->kind); @@ -2470,7 +2468,7 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) if (opt.async) NOB_TODO("Ptrace cache and async is not implemented."); - Nob__Ptrace_Cache_Node *node = nob__ptrace_cache_node(cache, *cmd, opt.stdin_path, opt.stdout_path, opt.stderr_path); + Nob__Ptrace_Cache_Node *node = nob__ptrace_cache_node(cache, *cmd, nob_get_current_dir_temp(), opt.stdin_path, opt.stdout_path, opt.stderr_path); bool is_cached = nob__ptrace_cache_is_cached(cache, *node); cache->temp_sb.count = 0; From 8a8ab72a86ab9b1f67ec577b5c3142643fe65327 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 6 Sep 2025 21:03:44 +0200 Subject: [PATCH 16/18] Strace -> Ptrace. --- how_to/nob.c | 2 +- how_to/{strace_cache => ptrace_cache}/.gitignore | 0 how_to/{strace_cache => ptrace_cache}/nob.c | 0 how_to/{strace_cache => ptrace_cache}/nob.h | 0 how_to/{strace_cache => ptrace_cache}/src/foo.c | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename how_to/{strace_cache => ptrace_cache}/.gitignore (100%) rename how_to/{strace_cache => ptrace_cache}/nob.c (100%) rename how_to/{strace_cache => ptrace_cache}/nob.h (100%) rename how_to/{strace_cache => ptrace_cache}/src/foo.c (100%) diff --git a/how_to/nob.c b/how_to/nob.c index cc31288..a1c74d0 100644 --- a/how_to/nob.c +++ b/how_to/nob.c @@ -10,7 +10,7 @@ const char *examples[] = { "001_basic_usage", "005_parallel_build", "010_nob_two_stage", - "strace_cache", + "ptrace_cache", }; int main(int argc, char **argv) diff --git a/how_to/strace_cache/.gitignore b/how_to/ptrace_cache/.gitignore similarity index 100% rename from how_to/strace_cache/.gitignore rename to how_to/ptrace_cache/.gitignore diff --git a/how_to/strace_cache/nob.c b/how_to/ptrace_cache/nob.c similarity index 100% rename from how_to/strace_cache/nob.c rename to how_to/ptrace_cache/nob.c diff --git a/how_to/strace_cache/nob.h b/how_to/ptrace_cache/nob.h similarity index 100% rename from how_to/strace_cache/nob.h rename to how_to/ptrace_cache/nob.h diff --git a/how_to/strace_cache/src/foo.c b/how_to/ptrace_cache/src/foo.c similarity index 100% rename from how_to/strace_cache/src/foo.c rename to how_to/ptrace_cache/src/foo.c From 2bdd181199d6440f3c0e214d1e3efb8c8a7499f6 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 6 Sep 2025 21:09:26 +0200 Subject: [PATCH 17/18] Formating. --- nob.h | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/nob.h b/nob.h index df24116..f7e2f3c 100644 --- a/nob.h +++ b/nob.h @@ -724,32 +724,32 @@ typedef struct Nob__Ints { } Nob__Ints; typedef struct Nob__Ptrace_Cache_Node { - int arg_index; - int parent; - Nob__Ptrace_Cache_Node_Kind kind; - // Indexies into Nob_Ptrace_Cache.arena; - Nob__Ints input_paths; - Nob__Ints output_paths; + int arg_index; + int parent; + Nob__Ptrace_Cache_Node_Kind kind; + // Indexies into Nob_Ptrace_Cache.arena; + Nob__Ints input_paths; + Nob__Ints output_paths; } Nob__Ptrace_Cache_Node; typedef struct Nob__Ptrace_Cache_Nodes { - Nob__Ptrace_Cache_Node *items; - size_t count; - size_t capacity; + Nob__Ptrace_Cache_Node *items; + size_t count; + size_t capacity; } Nob__Ptrace_Cache_Nodes; struct Nob_Ptrace_Cache { - Nob__Ptrace_Cache_Nodes nodes; - Nob_Cmd temp_cmd; // Don't worry about it... - Nob_String_Builder temp_sb; // Don't worry about it... - Nob_String_Builder arena; + Nob__Ptrace_Cache_Nodes nodes; + Nob_Cmd temp_cmd; // Don't worry about it... + Nob_String_Builder temp_sb; // Don't worry about it... + Nob_String_Builder arena; - const char *file_path; // File that will be used to read and write collected information for caching - bool is_loaded; - bool is_disabled; - bool no_absolute; + const char *file_path; // File that will be used to read and write collected information for caching + bool is_loaded; + bool is_disabled; + bool no_absolute; - bool was_last_cached; + bool was_last_cached; }; static void nob__ptrace_append_file(Nob_Ptrace_Cache* cache, Nob__Ptrace_Cache_Node* node, Nob_String_View file_path, int mode, bool exists, bool absolute_paths); @@ -775,12 +775,12 @@ static bool nob__ptrace_cache_write(Nob_Ptrace_Cache *cache); #else struct Nob_Ptrace_Cache { - const char *file_path; // File that will be used to read and write collected information for caching - bool is_loaded; - bool is_disabled; - bool no_absolute; + const char *file_path; // File that will be used to read and write collected information for caching + bool is_loaded; + bool is_disabled; + bool no_absolute; - bool was_last_cached; + bool was_last_cached; }; #endif From 89452452a2e69f5b77f2f397ad5a8598008c6a87 Mon Sep 17 00:00:00 2001 From: "Branimir Ri\\v{c}ko" Date: Sat, 13 Sep 2025 15:50:17 +0200 Subject: [PATCH 18/18] Don't cache the command if it failed. Also cache more often. --- nob.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nob.h b/nob.h index f7e2f3c..e1bea9f 100644 --- a/nob.h +++ b/nob.h @@ -2611,8 +2611,14 @@ Nob__Ptrace_Cache_Run_Status nob__cmd_run_ptrace(Nob_Cmd *cmd, Nob_Cmd_Opt opt) } } nob_sb_free(file_path); - if (WEXITSTATUS(status) == 0) return Nob__Ptrace_Cache_Run_True; - else return Nob__Ptrace_Cache_Run_False; + if (WEXITSTATUS(status) == 0) { + nob__ptrace_cache_write(cache); + return Nob__Ptrace_Cache_Run_True; + } else { + node->input_paths.count = 0; + node->output_paths.count = 0; + return Nob__Ptrace_Cache_Run_False; + } } #else return Nob__Ptrace_Cache_Run_Ptrace_Error;