dep

Package manager for embedded C libraries
git clone git://git.finwo.net/app/dep
Log | Files | Refs | README | LICENSE

commit 1b2e44bd8f461c591b9be2094bcef358346a9a19
parent 066dbf88d713b1d0f27c1002dfc96a2b7c324836
Author: finwo <finwo@pm.me>
Date:   Fri, 10 Apr 2026 01:47:00 +0200

Fix broken generated config.mk after partial install

Diffstat:
Msrc/command/install/main.c | 428++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
1 file changed, 302 insertions(+), 126 deletions(-)

diff --git a/src/command/install/main.c b/src/command/install/main.c @@ -27,6 +27,56 @@ /* Forward declarations */ static int install_dependency(const char *name, const char *spec); +static int process_dep_config_mk(const char *name, FILE *dst_config); + +/* Visited dependency tracking */ +static char **visited_deps = NULL; +static int visited_deps_count = 0; +static int visited_deps_capacity = 0; + +static int is_visited(const char *name) { + for (int i = 0; i < visited_deps_count; i++) { + if (strcmp(visited_deps[i], name) == 0) { + return 1; + } + } + return 0; +} + +static int mark_visited(const char *name) { + if (is_visited(name)) { + return 0; + } + + if (visited_deps_count >= visited_deps_capacity) { + int new_capacity = visited_deps_capacity == 0 ? 16 : visited_deps_capacity * 2; + char **new_deps = realloc(visited_deps, new_capacity * sizeof(char *)); + if (!new_deps) { + fprintf(stderr, "Error: failed to allocate memory for visited deps\n"); + return -1; + } + visited_deps = new_deps; + visited_deps_capacity = new_capacity; + } + + visited_deps[visited_deps_count] = strdup(name); + if (!visited_deps[visited_deps_count]) { + fprintf(stderr, "Error: failed to allocate memory for dep name\n"); + return -1; + } + visited_deps_count++; + return 1; +} + +static void clear_visited(void) { + for (int i = 0; i < visited_deps_count; i++) { + free(visited_deps[i]); + } + free(visited_deps); + visited_deps = NULL; + visited_deps_count = 0; + visited_deps_capacity = 0; +} static int dir_exists(const char *path) { struct stat st; @@ -337,163 +387,286 @@ static void template_callback(void *data, const char *str, size_t len) { fwrite(str, 1, len, ctx->fp); } -static int install_dependency(const char *name, const char *spec) { +static int process_dep_config_mk(const char *name, FILE *dst_config) { char lib_path[PATH_MAX]; snprintf(lib_path, sizeof(lib_path), "lib/%s", name); - if (dir_exists(lib_path)) { - printf("Skipping %s (already installed)\n", name); + char src_config_path[PATH_MAX]; + snprintf(src_config_path, sizeof(src_config_path), "%s/config.mk", lib_path); + + FILE *dep_config = fopen(src_config_path, "r"); + if (!dep_config) { return 0; } - char *url = spec_to_url(name, spec); - if (!url) { - fprintf(stderr, "Error: failed to resolve spec for %s\n", name); - return -1; - } + char line[LINE_MAX]; + while (fgets(line, sizeof(line), dep_config)) { + size_t linelen = strlen(line); + if (linelen > 0 && line[linelen - 1] == '\n') { + line[--linelen] = '\0'; + } + if (linelen > 0 && line[linelen - 1] == '\r') { + line[--linelen] = '\0'; + } - printf("Installing %s from %s\n", name, url); + if (linelen == 0) { + fputc('\n', dst_config); + continue; + } - mkdir_recursive(lib_path); + tinytemplate_instr_t program[256]; + size_t num_instr = 0; + char errmsg[256]; - if (download_and_extract(url, lib_path) != 0) { - fprintf(stderr, "Error: failed to install %s\n", name); - free(url); - return -1; - } - free(url); + if (tinytemplate_compile(line, linelen, program, 256, &num_instr, errmsg, sizeof(errmsg)) != + TINYTEMPLATE_STATUS_DONE) { + fprintf(stderr, "Warning: template compile failed: %s\n", errmsg); + fputs(line, dst_config); + fputc('\n', dst_config); + continue; + } - // Process .dep.chain recursively - char dep_chain_path[PATH_MAX]; - while (1) { - // Build .dep.chain path - snprintf(dep_chain_path, sizeof(dep_chain_path), "%s/.dep.chain", lib_path); + struct template_ctx ctx = {lib_path, dst_config}; - // Check if .dep.chain exists - FILE *chain_file = fopen(dep_chain_path, "r"); - if (!chain_file) { - break; // No more chaining + if (tinytemplate_eval(line, program, &ctx, template_getter, template_callback, errmsg, sizeof(errmsg)) != + TINYTEMPLATE_STATUS_DONE) { + fprintf(stderr, "Warning: template eval failed: %s\n", errmsg); } + fputc('\n', dst_config); + } - // Read spec (single line) - char spec[1024] = {0}; - if (!fgets(spec, sizeof(spec), chain_file)) { - fclose(chain_file); - fprintf(stderr, "Error: failed to read .dep.chain\n"); + fputc('\n', dst_config); + fclose(dep_config); + return 0; +} + +static int install_dependency(const char *name, const char *spec) { + if (is_visited(name)) { + return 0; + } + + char lib_path[PATH_MAX]; + snprintf(lib_path, sizeof(lib_path), "lib/%s", name); + + int already_installed = dir_exists(lib_path); + + if (already_installed) { + printf("Skipping %s (already installed)\n", name); + } else { + char *url = spec_to_url(name, spec); + if (!url) { + fprintf(stderr, "Error: failed to resolve spec for %s\n", name); return -1; } - fclose(chain_file); - // Delete .dep.chain file - if (remove(dep_chain_path) != 0) { - fprintf(stderr, "Warning: failed to remove .dep.chain\n"); - // Continue anyway - spec was read + printf("Installing %s from %s\n", name, url); + + mkdir_recursive(lib_path); + + if (download_and_extract(url, lib_path) != 0) { + fprintf(stderr, "Error: failed to install %s\n", name); + free(url); + return -1; } + free(url); - // Trim whitespace/newline from spec - char *trimmed = trim_whitespace(spec); - if (strlen(trimmed) == 0) { - fprintf(stderr, "Warning: empty spec in .dep.chain\n"); - continue; + // Process .dep.chain recursively + char dep_chain_path[PATH_MAX]; + while (1) { + snprintf(dep_chain_path, sizeof(dep_chain_path), "%s/.dep.chain", lib_path); + + FILE *chain_file = fopen(dep_chain_path, "r"); + if (!chain_file) { + break; + } + + char chain_spec[1024] = {0}; + if (!fgets(chain_spec, sizeof(chain_spec), chain_file)) { + fclose(chain_file); + fprintf(stderr, "Error: failed to read .dep.chain\n"); + return -1; + } + fclose(chain_file); + + if (remove(dep_chain_path) != 0) { + fprintf(stderr, "Warning: failed to remove .dep.chain\n"); + } + + char *trimmed = trim_whitespace(chain_spec); + if (strlen(trimmed) == 0) { + fprintf(stderr, "Warning: empty spec in .dep.chain\n"); + continue; + } + + printf("Found .dep.chain, chaining to: %s\n", trimmed); + + char *overlay_url = spec_to_url(name, trimmed); + if (!overlay_url) { + fprintf(stderr, "Error: failed to resolve chained spec '%s'\n", trimmed); + return -1; + } + + printf("Overlaying %s from %s\n", name, overlay_url); + if (download_and_extract(overlay_url, lib_path) != 0) { + fprintf(stderr, "Error: failed to overlay chained dependency\n"); + free(overlay_url); + return -1; + } + free(overlay_url); } - printf("Found .dep.chain, chaining to: %s\n", trimmed); + // Process .dep file in the dependency's directory + if (process_dep_file_in_dir(lib_path) != 0) { + fprintf(stderr, "Warning: failed to process .dep file for %s\n", name); + } - // Resolve spec to URL - char *overlay_url = spec_to_url(name, trimmed); - if (!overlay_url) { - fprintf(stderr, "Error: failed to resolve chained spec '%s'\n", trimmed); + // Execute postinstall hook if present + if (execute_postinstall_hook(lib_path) != 0) { + fprintf(stderr, "Error: postinstall hook failed for %s\n", name); return -1; } - // Overlay extract (directly over existing files) - printf("Overlaying %s from %s\n", name, overlay_url); - if (download_and_extract(overlay_url, lib_path) != 0) { - fprintf(stderr, "Error: failed to overlay chained dependency\n"); - free(overlay_url); - return -1; + // Handle .dep.export file + if (process_dep_export_file(lib_path, name) != 0) { + fprintf(stderr, "Warning: failed to process .dep.export file for %s\n", name); } - free(overlay_url); - // Loop continues to check for new .dep.chain - } - // Process .dep file in the dependency's directory - if (process_dep_file_in_dir(lib_path) != 0) { - fprintf(stderr, "Warning: failed to process .dep file for %s\n", name); - // Not returning error because the dependency itself installed successfully. + printf("Installed %s\n", name); } - // Execute postinstall hook if present - if (execute_postinstall_hook(lib_path) != 0) { - fprintf(stderr, "Error: postinstall hook failed for %s\n", name); + // Mark as visited after processing sub-dependencies (so they come first in config.mk order) + if (mark_visited(name) < 0) { return -1; } - // Handle config.mk: append dependency's config.mk to lib/.dep/config.mk + return 0; +} + +static int process_dep_file_for_config_mk(const char *dep_path); + +static int process_dep_file_for_config_mk(const char *dep_path) { + FILE *f = fopen(dep_path, "r"); + if (!f) { + return 0; + } + + char line[LINE_MAX]; + while (fgets(line, sizeof(line), f)) { + char *comment = strchr(line, '#'); + if (comment) *comment = '\0'; + + char *trimmed = trim_whitespace(line); + if (strlen(trimmed) == 0) continue; + + char name[256] = {0}; + char spec[1024] = {0}; + + char *space_pos = strchr(trimmed, ' '); + char *tab_pos = strchr(trimmed, '\t'); + char *first_whitespace = NULL; + if (space_pos && tab_pos) { + first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; + } else if (space_pos) { + first_whitespace = space_pos; + } else if (tab_pos) { + first_whitespace = tab_pos; + } + + if (first_whitespace) { + size_t name_len = first_whitespace - trimmed; + strncpy(name, trimmed, name_len); + name[name_len] = '\0'; + strcpy(spec, trim_whitespace(first_whitespace + 1)); + } else { + strncpy(name, trimmed, sizeof(name) - 1); + name[sizeof(name) - 1] = '\0'; + } + + if (strlen(name) == 0) continue; + + // Process sub-dependencies first + char sub_dep_path[PATH_MAX]; + snprintf(sub_dep_path, sizeof(sub_dep_path), "lib/%s/.dep", name); + process_dep_file_for_config_mk(sub_dep_path); + + // Then process this dependency if not already visited + if (!is_visited(name)) { + continue; + } + } + + fclose(f); + return 0; +} + +static int rebuild_config_mk(void) { char dep_dir[PATH_MAX]; snprintf(dep_dir, sizeof(dep_dir), "lib/.dep"); mkdir_recursive(dep_dir); - char src_config_path[PATH_MAX]; - snprintf(src_config_path, sizeof(src_config_path), "%s/config.mk", lib_path); + char config_mk_path[PATH_MAX]; + snprintf(config_mk_path, sizeof(config_mk_path), "%s/config.mk", dep_dir); - char dst_config_path[PATH_MAX]; - snprintf(dst_config_path, sizeof(dst_config_path), "lib/.dep/config.mk"); + FILE *config_mk = fopen(config_mk_path, "w"); + if (!config_mk) { + fprintf(stderr, "Error: could not create %s\n", config_mk_path); + return -1; + } - FILE *dep_config = fopen(src_config_path, "r"); - if (dep_config) { - FILE *dst_config = fopen(dst_config_path, "a"); - if (dst_config) { - char line[LINE_MAX]; - while (fgets(line, sizeof(line), dep_config)) { - size_t linelen = strlen(line); - if (linelen > 0 && line[linelen - 1] == '\n') { - line[--linelen] = '\0'; - } - if (linelen > 0 && line[linelen - 1] == '\r') { - line[--linelen] = '\0'; - } - - tinytemplate_instr_t program[256]; - size_t num_instr = 0; - char errmsg[256]; - - if (linelen == 0) { - fputc('\n', dst_config); - continue; - } - - if (tinytemplate_compile(line, linelen, program, 256, &num_instr, errmsg, sizeof(errmsg)) != - TINYTEMPLATE_STATUS_DONE) { - fprintf(stderr, "Warning: template compile failed: %s\n", errmsg); - fputs(line, dst_config); - fputc('\n', dst_config); - continue; - } - - struct template_ctx ctx = {lib_path, dst_config}; - - if (tinytemplate_eval(line, program, &ctx, template_getter, template_callback, errmsg, sizeof(errmsg)) != - TINYTEMPLATE_STATUS_DONE) { - fprintf(stderr, "Warning: template eval failed: %s\n", errmsg); - } - fputc('\n', dst_config); - } - // Ensure a newline at end of appended content if not already ending with newline - // (optional, but we can add a newline to separate entries) - fputc('\n', dst_config); - fclose(dst_config); + fputs("CFLAGS+=-Ilib/.dep/include\n", config_mk); + + // Process dependencies in order, adding their config.mk content + FILE *f = fopen(".dep", "r"); + if (!f) { + fclose(config_mk); + return 0; + } + + char line[LINE_MAX]; + while (fgets(line, sizeof(line), f)) { + char *comment = strchr(line, '#'); + if (comment) *comment = '\0'; + + char *trimmed = trim_whitespace(line); + if (strlen(trimmed) == 0) continue; + + char name[256] = {0}; + char spec[1024] = {0}; + + char *space_pos = strchr(trimmed, ' '); + char *tab_pos = strchr(trimmed, '\t'); + char *first_whitespace = NULL; + if (space_pos && tab_pos) { + first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; + } else if (space_pos) { + first_whitespace = space_pos; + } else if (tab_pos) { + first_whitespace = tab_pos; + } + + if (first_whitespace) { + size_t name_len = first_whitespace - trimmed; + strncpy(name, trimmed, name_len); + name[name_len] = '\0'; + strcpy(spec, trim_whitespace(first_whitespace + 1)); } else { - fprintf(stderr, "Warning: could not open %s for appending\n", dst_config_path); + strncpy(name, trimmed, sizeof(name) - 1); + name[sizeof(name) - 1] = '\0'; } - fclose(dep_config); - } - if (process_dep_export_file(lib_path, name) != 0) { - fprintf(stderr, "Warning: failed to process .dep.export file for %s\n", name); + if (strlen(name) == 0) continue; + + // Process sub-dependencies first (recursively) + char sub_dep_path[PATH_MAX]; + snprintf(sub_dep_path, sizeof(sub_dep_path), "lib/%s/.dep", name); + process_dep_file_for_config_mk(sub_dep_path); + + // Then add this dependency's config.mk + process_dep_config_mk(name, config_mk); } - printf("Installed %s\n", name); + fclose(f); + fclose(config_mk); return 0; } @@ -520,19 +693,10 @@ static int cmd_install(int argc, const char **argv) { snprintf(dep_dir, sizeof(dep_dir), "lib/.dep"); mkdir_recursive(dep_dir); - char config_mk_path[PATH_MAX]; - snprintf(config_mk_path, sizeof(config_mk_path), "%s/config.mk", dep_dir); - FILE *config_mk = fopen(config_mk_path, "w"); - if (config_mk) { - fputs("CFLAGS+=-Ilib/.dep/include\n", config_mk); - fclose(config_mk); - } else { - fprintf(stderr, "Warning: could not create %s\n", config_mk_path); - } - char line[LINE_MAX]; int has_deps = 0; + // First pass: install all dependencies while (fgets(line, sizeof(line), f)) { char *comment = strchr(line, '#'); if (comment) *comment = '\0'; @@ -568,11 +732,23 @@ static int cmd_install(int argc, const char **argv) { if (strlen(name) == 0) continue; - install_dependency(name, spec); + if (install_dependency(name, spec) != 0) { + fclose(f); + clear_visited(); + return 1; + } } fclose(f); + // Rebuild config.mk from all installed dependencies + if (rebuild_config_mk() != 0) { + clear_visited(); + return 1; + } + + clear_visited(); + if (!has_deps) { printf("No dependencies to install\n"); }