From 4250f9196970f8088f8f9cdd0c8de99c746f3b85 Mon Sep 17 00:00:00 2001 From: anth64 Date: Sat, 7 Mar 2026 11:34:23 +0100 Subject: [PATCH] feat(stk.h, module, stk): improve dependency failure logging - Add STK_MOD_DEP_LOG_BUFFER (2048) to stk.h for the dep failure message buffer size. - Add stk_log_dependency_failures(index, action) to module.c. Walks all deps for the given module, skips satisfied ones, and builds a single log line listing every unmet dep with its reason: "not found" or "requires , have ". The action parameter ("Deferring" / "Unloading") lets call sites produce contextually appropriate messages: - Deferring 'test_mod_dep': unmet deps: test_mod (not found) - Unloading 'test_mod_dep': unmet deps: test_mod (not found), renderer (requires ^2.0.0, have 1.3.0) - Replace the silent defer at init and the vague "unmet dependencies" cascade log in stk_poll() with calls to stk_log_dependency_failures(). --- include/stk.h | 1 + src/module.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/stk.c | 108 +++++++++++++++++++++------------------------- 3 files changed, 166 insertions(+), 60 deletions(-) diff --git a/include/stk.h b/include/stk.h index f44a1a6..5c01907 100644 --- a/include/stk.h +++ b/include/stk.h @@ -7,6 +7,7 @@ /* Buffers */ #define STK_LOG_PREFIX_BUFFER 64 #define STK_MOD_DEP_OPERATOR_BUFFER 3 +#define STK_MOD_DEP_LOG_BUFFER 2048 #define STK_MOD_DESC_BUFFER 256 #define STK_MOD_DIR_BUFFER 256 #define STK_MOD_ID_BUFFER 64 diff --git a/src/module.c b/src/module.c index e473b7d..e106ac4 100644 --- a/src/module.c +++ b/src/module.c @@ -206,8 +206,18 @@ unsigned char stk_validate_dependencies(size_t count) return result; } +/* + * has_dep(i, j, ctx): return 1 if node i depends on node j, 0 otherwise. + * Used by stk_kahn_sort to abstract over loaded modules vs incoming batch. + */ typedef int (*stk_dep_query_fn)(size_t i, size_t j, void *ctx); +/* + * Core Kahn's topological sort. Produces dependencies-first ordering. + * on_cycle is called for each node not reachable (i.e. in a cycle). + * Returns STK_MOD_INIT_SUCCESS or STK_MOD_DEP_CIRCULAR_ERROR / + * STK_MOD_REALLOC_FAILURE. + */ static unsigned char stk_kahn_sort(size_t count, size_t *order, stk_dep_query_fn has_dep, void *ctx, void (*on_cycle)(size_t i)) @@ -472,6 +482,87 @@ unsigned char stk_validate_dependencies_single(size_t index) return STK_MOD_INIT_SUCCESS; } +void stk_log_dependency_failures(size_t index, const char *action) +{ + char buf[STK_MOD_DEP_LOG_BUFFER]; + size_t d, pos, len; + int found; + int first = 1; + + if (stk_modules[index].dep_count == 0) + return; + + pos = 0; + for (d = 0; d < stk_modules[index].dep_count; d++) { + found = is_mod_loaded(stk_modules[index].deps[d].id); + + if (found >= 0 && + (!stk_modules[index].deps[d].version[0] || + stk_validate_constraint(stk_modules[index].deps[d].version, + stk_modules[found].version))) + continue; + + if (!first && pos < sizeof(buf) - 2) { + buf[pos++] = ','; + buf[pos++] = ' '; + } + first = 0; + + if (found < 0) { + len = strlen(stk_modules[index].deps[d].id); + if (pos + len + 12 < sizeof(buf)) { + memcpy(buf + pos, stk_modules[index].deps[d].id, + len); + pos += len; + memcpy(buf + pos, " (not found)", 12); + pos += 12; + } + } else { + len = strlen(stk_modules[index].deps[d].id); + if (pos + len < sizeof(buf)) { + memcpy(buf + pos, stk_modules[index].deps[d].id, + len); + pos += len; + } + if (pos < sizeof(buf) - 1) { + buf[pos++] = ' '; + } + buf[pos++] = '('; + len = strlen("requires "); + if (pos + len < sizeof(buf)) { + memcpy(buf + pos, "requires ", len); + pos += len; + } + len = strlen(stk_modules[index].deps[d].version); + if (pos + len < sizeof(buf)) { + memcpy(buf + pos, + stk_modules[index].deps[d].version, len); + pos += len; + } + len = strlen(", have "); + if (pos + len < sizeof(buf)) { + memcpy(buf + pos, ", have ", len); + pos += len; + } + len = strlen(stk_modules[found].version); + if (pos + len < sizeof(buf)) { + memcpy(buf + pos, stk_modules[found].version, + len); + pos += len; + } + if (pos < sizeof(buf) - 1) + buf[pos++] = ')'; + } + } + + if (first) + return; + + buf[pos] = '\0'; + stk_log(STK_LOG_WARN, "%s '%s': unmet deps: %s", action, + stk_modules[index].id, buf); +} + unsigned char stk_module_load(const char *path, int index) { unsigned char result; @@ -631,6 +722,11 @@ static int stk_batch_has_dep(size_t i, size_t j, void *ctx) return result; } +/* + * Sort file_indices[] (length n) so that within the batch, modules whose ids + * appear as dependencies of others come first. Uses already-copied tmp files + * to read dep symbols. Falls back to original order if sort fails. + */ void stk_sort_load_order(int *file_indices, size_t n, char (*file_names)[STK_PATH_MAX], const char *tmp_dir) { @@ -665,6 +761,12 @@ cleanup: free(result); } +/* + * Expand indices[0..]*in_count to include all loaded modules that transitively + * depend on any module already in the set. Writes the expanded set back into + * indices[] and updates *out_count. indices[] must have capacity module_count. + * Returns 1 if any new dependents were added, 0 otherwise. + */ void stk_collect_dependents(size_t *indices, size_t *count) { size_t i, d; @@ -673,6 +775,7 @@ void stk_collect_dependents(size_t *indices, size_t *count) do { changed = 0; for (i = 0; i < module_count; i++) { + /* skip if already in the set */ in_set = 0; { size_t k; @@ -686,6 +789,7 @@ void stk_collect_dependents(size_t *indices, size_t *count) if (in_set) continue; + /* check if any of its deps are in the set */ for (d = 0; d < stk_modules[i].dep_count; d++) { int dep_idx = is_mod_loaded(stk_modules[i].deps[d].id); @@ -708,6 +812,11 @@ void stk_collect_dependents(size_t *indices, size_t *count) } while (changed); } +/* + * Sort indices[] (length n) so that dependents come before their dependencies. + * This is the reverse of topological order. indices[] is sorted in-place. + * Falls back to reverse-index order if topo sort fails or alloc fails. + */ void stk_sort_unload_order(size_t *indices, size_t n) { size_t *topo = NULL; @@ -727,6 +836,10 @@ void stk_sort_unload_order(size_t *indices, size_t n) if (stk_topo_sort(module_count, topo) != STK_MOD_INIT_SUCCESS) goto fallback; + /* + * topo[] is dependencies-first. Walk it in reverse to get + * dependents-first, picking only indices that are in our set. + */ k = 0; for (i = module_count; i > 0; --i) { size_t mod = topo[i - 1]; @@ -753,6 +866,7 @@ void stk_sort_unload_order(size_t *indices, size_t n) fallback: free(topo); free(result); + /* reverse index order: higher indices (dependents) first */ for (i = 0; i < n / 2; i++) { size_t tmp = indices[i]; indices[i] = indices[n - 1 - i]; @@ -777,6 +891,7 @@ void stk_module_unload_all(void) stk_module_unload(order[i]); free(order); } else { + /* fallback: reverse index order */ for (i = module_count; i > 0; --i) stk_module_unload(i - 1); } @@ -828,6 +943,7 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count) if (!paths || count == 0) return; + /* First pass: overwrite existing entries and count truly new ones */ new_count = 0; for (i = 0; i < count; i++) { int found = 0; @@ -863,6 +979,7 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count) free(stk_pending); stk_pending = new_pending; + /* Second pass: append only the truly new ones */ for (i = 0; i < count; i++) { int found = 0; extract_module_id(paths[i], incoming_id); diff --git a/src/stk.c b/src/stk.c index 58e5529..7ae5d3c 100644 --- a/src/stk.c +++ b/src/stk.c @@ -48,6 +48,7 @@ size_t stk_module_count(void); unsigned char stk_module_preload(const char *path, int index); unsigned char stk_module_activate(size_t index); unsigned char stk_validate_dependencies_single(size_t index); +void stk_log_dependency_failures(size_t index, const char *action); void stk_module_discard(size_t index); unsigned char stk_module_load(const char *path, int index); unsigned char stk_module_load_init(const char *path, int index); @@ -130,9 +131,12 @@ static void stk_log_modules(void) unsigned char stk_init(void) { char (*files)[STK_PATH_MAX] = NULL; - size_t file_count, i, successful_loads = 0; + char (*test_scan)[STK_PATH_MAX]; + size_t file_count, i, j, write, successful_loads = 0; + size_t index, test_count; char full_path[STK_PATH_MAX_OS]; char tmp_path[STK_PATH_MAX_OS]; + char mod_tmp_path[STK_PATH_MAX_OS]; int load_result; unsigned char dep_result; size_t *order = NULL; @@ -140,9 +144,6 @@ unsigned char stk_init(void) platform_mkdir(stk_mod_dir); build_path(stk_tmp_dir, sizeof(stk_tmp_dir), stk_mod_dir, stk_tmp_name); if (platform_mkdir(stk_tmp_dir) != STK_PLATFORM_OPERATION_SUCCESS) { - char (*test_scan)[STK_PATH_MAX]; - size_t test_count; - test_scan = platform_directory_init_scan(stk_tmp_dir, &test_count); if (test_scan) @@ -197,61 +198,50 @@ unsigned char stk_init(void) if (module_count == 0) goto scanned; - { - size_t j, write; - char mod_tmp_path[STK_PATH_MAX_OS]; - - order = malloc(module_count * sizeof(size_t)); - if (order) { - dep_result = stk_topo_sort(module_count, order); - if (dep_result != STK_MOD_INIT_SUCCESS) - stk_log(STK_LOG_ERROR, - "Dependency sort failed: %s", - stk_error_string(dep_result)); - } else { - order = malloc(module_count * sizeof(size_t)); - if (order) - for (j = 0; j < module_count; j++) - order[j] = j; - } - - for (j = 0; j < module_count; j++) { - size_t idx = order ? order[j] : j; - dep_result = stk_validate_dependencies_single(idx); - if (dep_result != STK_MOD_INIT_SUCCESS) { - build_path(mod_tmp_path, sizeof(mod_tmp_path), - stk_tmp_dir, stk_modules[idx].id); - strncat(mod_tmp_path, STK_MODULE_EXT, - sizeof(mod_tmp_path) - - strlen(mod_tmp_path) - 1); - stk_pending_add(mod_tmp_path); - stk_module_discard(idx); - continue; - } - if (stk_module_activate(idx) != STK_MOD_INIT_SUCCESS) { - stk_log(STK_LOG_ERROR, - "Failed to init module %s", - stk_modules[idx].id); - stk_module_discard(idx); - } - } - - if (order) { - free(order); - order = NULL; - } - - write = 0; - for (j = 0; j < module_count; j++) { - if (stk_modules[j].handle != NULL) { - if (write != j) - stk_modules[write] = stk_modules[j]; - write++; - } - } - module_count = write; + order = malloc(module_count * sizeof(size_t)); + if (order) { + dep_result = stk_topo_sort(module_count, order); + if (dep_result != STK_MOD_INIT_SUCCESS) + stk_log(STK_LOG_ERROR, "Dependency sort failed: %s", + stk_error_string(dep_result)); } + for (j = 0; j < module_count; j++) { + index = order ? order[j] : j; + dep_result = stk_validate_dependencies_single(index); + if (dep_result != STK_MOD_INIT_SUCCESS) { + stk_log_dependency_failures(index, "Deferring"); + build_path(mod_tmp_path, sizeof(mod_tmp_path), + stk_tmp_dir, stk_modules[index].id); + strncat(mod_tmp_path, STK_MODULE_EXT, + sizeof(mod_tmp_path) - strlen(mod_tmp_path) - + 1); + stk_pending_add(mod_tmp_path); + stk_module_discard(index); + continue; + } + if (stk_module_activate(index) != STK_MOD_INIT_SUCCESS) { + stk_log(STK_LOG_ERROR, "Failed to init module %s", + stk_modules[index].id); + stk_module_discard(index); + } + } + + if (order) { + free(order); + order = NULL; + } + + write = 0; + for (j = 0; j < module_count; j++) { + if (stk_modules[j].handle != NULL) { + if (write != j) + stk_modules[write] = stk_modules[j]; + write++; + } + } + module_count = write; + scanned: watch_handle = platform_directory_watch_start(stk_mod_dir); if (!watch_handle) { @@ -581,9 +571,7 @@ validate_deps: for (j = 0; j < cascade_count; j++) { index = cascade_indices[j]; - stk_log(STK_LOG_WARN, - "Unloading '%s': unmet dependencies", - stk_modules[index].id); + stk_log_dependency_failures(index, "Unloading"); build_path(cascade_tmp_path, sizeof(cascade_tmp_path), stk_tmp_dir, stk_modules[index].id); strncat(cascade_tmp_path, STK_MODULE_EXT,