main.c (21466B)
1 #ifndef _GNU_SOURCE 2 #define _GNU_SOURCE 3 #endif 4 #include <errno.h> 5 #include <fcntl.h> 6 #include <limits.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <sys/stat.h> 11 #include <sys/types.h> 12 #include <sys/wait.h> 13 #include <unistd.h> 14 15 #ifdef __APPLE__ 16 #include <crt_externs.h> 17 #define environ (*_NSGetEnviron()) 18 #endif 19 20 #include "command/command.h" 21 #include "common/github-utils.h" 22 #include "common/net-utils.h" 23 #include "cozis/tinytemplate.h" 24 #include "emmanuel-marty/em_inflate.h" 25 #include "erkkah/naett.h" 26 #include "rxi/microtar.h" 27 28 /* Forward declarations */ 29 static int install_dependency(const char *name, const char *spec); 30 static int process_dep_config_mk(const char *name, FILE *dst_config); 31 static int process_dep_for_config(const char *name, FILE *dst_config); 32 33 /* Config-phase visited dependency tracking (used only in Pass 2) */ 34 static char **config_visited_deps = NULL; 35 static int config_visited_deps_count = 0; 36 static int config_visited_deps_capacity = 0; 37 38 static int config_is_visited(const char *name) { 39 for (int i = 0; i < config_visited_deps_count; i++) { 40 if (strcmp(config_visited_deps[i], name) == 0) { 41 return 1; 42 } 43 } 44 return 0; 45 } 46 47 static int config_mark_visited(const char *name) { 48 if (config_is_visited(name)) { 49 return 0; 50 } 51 52 if (config_visited_deps_count >= config_visited_deps_capacity) { 53 int new_capacity = config_visited_deps_capacity == 0 ? 16 : config_visited_deps_capacity * 2; 54 char **new_deps = realloc(config_visited_deps, new_capacity * sizeof(char *)); 55 if (!new_deps) { 56 fprintf(stderr, "Error: failed to allocate memory for config visited deps\n"); 57 return -1; 58 } 59 config_visited_deps = new_deps; 60 config_visited_deps_capacity = new_capacity; 61 } 62 63 config_visited_deps[config_visited_deps_count] = strdup(name); 64 if (!config_visited_deps[config_visited_deps_count]) { 65 fprintf(stderr, "Error: failed to allocate memory for config dep name\n"); 66 return -1; 67 } 68 config_visited_deps_count++; 69 return 1; 70 } 71 72 static void config_clear_visited(void) { 73 for (int i = 0; i < config_visited_deps_count; i++) { 74 free(config_visited_deps[i]); 75 } 76 free(config_visited_deps); 77 config_visited_deps = NULL; 78 config_visited_deps_count = 0; 79 config_visited_deps_capacity = 0; 80 } 81 82 static int dir_exists(const char *path) { 83 struct stat st; 84 return stat(path, &st) == 0 && S_ISDIR(st.st_mode); 85 } 86 87 static int mkdir_recursive(const char *path) { 88 char tmp[PATH_MAX]; 89 char *p = NULL; 90 size_t len; 91 92 snprintf(tmp, sizeof(tmp), "%s", path); 93 len = strlen(tmp); 94 if (tmp[len - 1] == '/') { 95 tmp[len - 1] = '\0'; 96 } 97 98 for (p = tmp + 1; *p; p++) { 99 if (*p == '/') { 100 *p = '\0'; 101 mkdir(tmp, 0755); 102 *p = '/'; 103 } 104 } 105 return mkdir(tmp, 0755); 106 } 107 108 static char *trim_whitespace(char *str) { 109 while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r') str++; 110 if (*str == '\0') return str; 111 char *end = str + strlen(str) - 1; 112 while (end > str && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) { 113 *end = '\0'; 114 end--; 115 } 116 return str; 117 } 118 119 static char *spec_to_url(const char *name, const char *spec) { 120 if (strlen(spec) > 0 && is_url(spec)) { 121 return strdup(spec); 122 } 123 124 char *full_ref = NULL; 125 126 if (strlen(spec) > 0) { 127 full_ref = github_matching_ref(name, spec); 128 if (!full_ref) { 129 fprintf(stderr, "Error: ref '%s' not found for %s\n", spec, name); 130 return NULL; 131 } 132 } else { 133 char *branch = github_default_branch(name); 134 if (!branch) { 135 fprintf(stderr, "Warning: could not determine default branch for %s, using 'main'\n", name); 136 branch = strdup("main"); 137 } 138 full_ref = malloc(256); 139 if (full_ref) { 140 snprintf(full_ref, 256, "refs/heads/%s", branch); 141 } 142 free(branch); 143 } 144 145 if (!full_ref) { 146 fprintf(stderr, "Error: could not determine ref for %s\n", name); 147 return NULL; 148 } 149 150 char *url = malloc(2048); 151 if (!url) { 152 free(full_ref); 153 return NULL; 154 } 155 snprintf(url, 2048, "https://github.com/%s/archive/%s.tar.gz", name, full_ref); 156 free(full_ref); 157 return url; 158 } 159 160 static int process_dep_export_file(const char *dep_dir, const char *name) { 161 char export_path[PATH_MAX]; 162 snprintf(export_path, sizeof(export_path), "%s/.dep.export", dep_dir); 163 FILE *f = fopen(export_path, "r"); 164 if (!f) { 165 return 0; 166 } 167 168 char dep_base[PATH_MAX]; 169 if (getcwd(dep_base, sizeof(dep_base)) == NULL) { 170 fprintf(stderr, "Error: failed to get current working directory\n"); 171 fclose(f); 172 return -1; 173 } 174 175 char line[PATH_MAX]; 176 while (fgets(line, sizeof(line), f)) { 177 char *comment = strchr(line, '#'); 178 if (comment) *comment = '\0'; 179 180 char *trimmed = trim_whitespace(line); 181 if (strlen(trimmed) == 0) continue; 182 183 char source[512] = {0}; 184 char target[512] = {0}; 185 186 char *space_pos = strchr(trimmed, ' '); 187 char *tab_pos = strchr(trimmed, '\t'); 188 char *first_whitespace = NULL; 189 if (space_pos && tab_pos) { 190 first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; 191 } else if (space_pos) { 192 first_whitespace = space_pos; 193 } else if (tab_pos) { 194 first_whitespace = tab_pos; 195 } 196 197 if (first_whitespace) { 198 size_t source_len = first_whitespace - trimmed; 199 strncpy(source, trimmed, source_len); 200 source[source_len] = '\0'; 201 strcpy(target, trim_whitespace(first_whitespace + 1)); 202 } else { 203 continue; 204 } 205 206 if (strlen(source) == 0 || strlen(target) == 0) continue; 207 208 char source_parent[PATH_MAX]; 209 strncpy(source_parent, source, sizeof(source_parent) - 1); 210 source_parent[sizeof(source_parent) - 1] = '\0'; 211 char *last_slash = strrchr(source_parent, '/'); 212 if (last_slash) { 213 *last_slash = '\0'; 214 char parent_dir[PATH_MAX]; 215 snprintf(parent_dir, sizeof(parent_dir), "lib/.dep/%s", source_parent); 216 mkdir_recursive(parent_dir); 217 } else { 218 mkdir_recursive("lib/.dep"); 219 } 220 221 char target_abs[PATH_MAX]; 222 snprintf(target_abs, sizeof(target_abs), "%s/lib/%s/%s", dep_base, name, target); 223 224 char link_path[PATH_MAX]; 225 snprintf(link_path, sizeof(link_path), "lib/.dep/%s", source); 226 227 unlink(link_path); 228 if (symlink(target_abs, link_path) != 0) { 229 fprintf(stderr, "Warning: failed to create symlink %s -> %s\n", link_path, target_abs); 230 } else { 231 printf("Exported %s -> %s\n", source, target_abs); 232 } 233 } 234 235 fclose(f); 236 return 0; 237 } 238 239 static int process_dep_file_in_dir(const char *dep_dir); 240 static int execute_postinstall_hook(const char *dep_dir); 241 242 static int process_dep_file_in_dir(const char *dep_dir) { 243 char dep_path[PATH_MAX]; 244 snprintf(dep_path, sizeof(dep_path), "%s/.dep", dep_dir); 245 FILE *f = fopen(dep_path, "r"); 246 if (!f) { 247 // No .dep file is not an error 248 return 0; 249 } 250 251 char line[LINE_MAX]; 252 while (fgets(line, sizeof(line), f)) { 253 char *comment = strchr(line, '#'); 254 if (comment) *comment = '\0'; 255 256 char *trimmed = trim_whitespace(line); 257 if (strlen(trimmed) == 0) continue; 258 259 char name[256] = {0}; 260 char spec[1024] = {0}; 261 262 char *space_pos = strchr(trimmed, ' '); 263 char *tab_pos = strchr(trimmed, '\t'); 264 char *first_whitespace = NULL; 265 if (space_pos && tab_pos) { 266 first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; 267 } else if (space_pos) { 268 first_whitespace = space_pos; 269 } else if (tab_pos) { 270 first_whitespace = tab_pos; 271 } 272 273 if (first_whitespace) { 274 size_t name_len = first_whitespace - trimmed; 275 strncpy(name, trimmed, name_len); 276 name[name_len] = '\0'; 277 strcpy(spec, trim_whitespace(first_whitespace + 1)); 278 } else { 279 strncpy(name, trimmed, sizeof(name) - 1); 280 name[sizeof(name) - 1] = '\0'; 281 } 282 283 if (strlen(name) == 0) continue; 284 285 install_dependency(name, spec); 286 } 287 288 fclose(f); 289 return 0; 290 } 291 292 static int execute_postinstall_hook(const char *dep_dir) { 293 char cwd[PATH_MAX]; 294 if (getcwd(cwd, sizeof(cwd)) == NULL) { 295 fprintf(stderr, "Error: failed to get current working directory\n"); 296 return -1; 297 } 298 299 if (chdir(dep_dir) != 0) { 300 fprintf(stderr, "Error: failed to change to dependency directory\n"); 301 return -1; 302 } 303 304 char hook_path[PATH_MAX]; 305 snprintf(hook_path, sizeof(hook_path), "./.dep.hook.postinstall"); 306 307 struct stat st; 308 if (stat(hook_path, &st) != 0) { 309 chdir(cwd); 310 return 0; 311 } 312 313 int exec_bits = S_IXUSR | S_IXGRP | S_IXOTH; 314 if (!(st.st_mode & exec_bits)) { 315 fprintf(stderr, "Warning: %s is not executable, making executable...\n", hook_path); 316 if (chmod(hook_path, st.st_mode | exec_bits) != 0) { 317 fprintf(stderr, "Error: failed to make hook executable\n"); 318 chdir(cwd); 319 return -1; 320 } 321 if (stat(hook_path, &st) != 0) { 322 chdir(cwd); 323 return 0; 324 } 325 } 326 327 pid_t pid = fork(); 328 if (pid == 0) { 329 char *const argv[] = {hook_path, NULL}; 330 execve(hook_path, argv, environ); 331 fprintf(stderr, "Error: execve failed: errno=%d\n", errno); 332 _exit(127); 333 } else if (pid < 0) { 334 chdir(cwd); 335 fprintf(stderr, "Error: failed to fork for postinstall hook\n"); 336 return -1; 337 } 338 339 int status; 340 waitpid(pid, &status, 0); 341 342 chdir(cwd); 343 344 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 345 printf("Executed postinstall hook for %s\n", dep_dir); 346 return 0; 347 } else { 348 fprintf(stderr, "Error: postinstall hook failed with exit code %d\n", WIFEXITED(status) ? WEXITSTATUS(status) : -1); 349 return -1; 350 } 351 } 352 353 static int install_dependency(const char *name, const char *spec); 354 355 struct template_ctx { 356 const char *lib_path; 357 FILE *fp; 358 }; 359 360 static bool module_dict_getter(void *data, const char *name, size_t len, tinytemplate_value_t *value) { 361 const char *lib_path = data; 362 if (len == 7 && strncmp(name, "dirname", 7) == 0) { 363 tinytemplate_set_string(value, lib_path, strlen(lib_path)); 364 return true; 365 } 366 char orig[256]; 367 int n = snprintf(orig, sizeof(orig), "{%.*s}", (int)len, name); 368 tinytemplate_set_string(value, orig, n); 369 return true; 370 } 371 372 static bool template_getter(void *data, const char *name, size_t len, tinytemplate_value_t *value) { 373 struct template_ctx *ctx = data; 374 375 if (len == 6 && strncmp(name, "module", 6) == 0) { 376 tinytemplate_set_dict(value, (void *)ctx->lib_path, module_dict_getter); 377 return true; 378 } 379 380 char orig[256]; 381 int n = snprintf(orig, sizeof(orig), "{{%.*s}}", (int)len, name); 382 tinytemplate_set_string(value, orig, n); 383 return true; 384 } 385 386 static void template_callback(void *data, const char *str, size_t len) { 387 struct template_ctx *ctx = data; 388 fwrite(str, 1, len, ctx->fp); 389 } 390 391 static int process_dep_config_mk(const char *name, FILE *dst_config) { 392 char lib_path[PATH_MAX]; 393 snprintf(lib_path, sizeof(lib_path), "lib/%s", name); 394 395 char src_config_path[PATH_MAX]; 396 // Try export.mk first (new preferred name), fall back to config.mk 397 snprintf(src_config_path, sizeof(src_config_path), "%s/export.mk", lib_path); 398 399 FILE *dep_config = fopen(src_config_path, "r"); 400 int using_legacy = 0; 401 if (!dep_config) { 402 // Fall back to legacy config.mk 403 snprintf(src_config_path, sizeof(src_config_path), "%s/config.mk", lib_path); 404 dep_config = fopen(src_config_path, "r"); 405 if (dep_config) { 406 using_legacy = 1; 407 fprintf(stderr, "Deprecation warning: %s uses config.mk, please rename to export.mk\n", name); 408 } else { 409 return 0; 410 } 411 } 412 413 // Write dependency name as a comment 414 fprintf(dst_config, "\n# %s\n", name); 415 416 char line[LINE_MAX]; 417 while (fgets(line, sizeof(line), dep_config)) { 418 size_t linelen = strlen(line); 419 if (linelen > 0 && line[linelen - 1] == '\n') { 420 line[--linelen] = '\0'; 421 } 422 if (linelen > 0 && line[linelen - 1] == '\r') { 423 line[--linelen] = '\0'; 424 } 425 426 if (linelen == 0) { 427 fputc('\n', dst_config); 428 continue; 429 } 430 431 tinytemplate_instr_t program[256]; 432 size_t num_instr = 0; 433 char errmsg[256]; 434 435 if (tinytemplate_compile(line, linelen, program, 256, &num_instr, errmsg, sizeof(errmsg)) != 436 TINYTEMPLATE_STATUS_DONE) { 437 fprintf(stderr, "Warning: template compile failed: %s\n", errmsg); 438 fputs(line, dst_config); 439 fputc('\n', dst_config); 440 continue; 441 } 442 443 struct template_ctx ctx = {lib_path, dst_config}; 444 445 if (tinytemplate_eval(line, program, &ctx, template_getter, template_callback, errmsg, sizeof(errmsg)) != 446 TINYTEMPLATE_STATUS_DONE) { 447 fprintf(stderr, "Warning: template eval failed: %s\n", errmsg); 448 } 449 fputc('\n', dst_config); 450 } 451 452 fputc('\n', dst_config); 453 fclose(dep_config); 454 return 0; 455 } 456 457 static int install_dependency(const char *name, const char *spec) { 458 char lib_path[PATH_MAX]; 459 snprintf(lib_path, sizeof(lib_path), "lib/%s", name); 460 461 if (dir_exists(lib_path)) { 462 return 0; 463 } 464 465 char *url = spec_to_url(name, spec); 466 if (!url) { 467 fprintf(stderr, "Error: failed to resolve spec for %s\n", name); 468 return -1; 469 } 470 471 printf("Installing %s from %s\n", name, url); 472 473 mkdir_recursive(lib_path); 474 475 if (download_and_extract(url, lib_path) != 0) { 476 fprintf(stderr, "Error: failed to install %s\n", name); 477 free(url); 478 return -1; 479 } 480 free(url); 481 482 // Process .dep.chain recursively 483 char dep_chain_path[PATH_MAX]; 484 while (1) { 485 snprintf(dep_chain_path, sizeof(dep_chain_path), "%s/.dep.chain", lib_path); 486 487 FILE *chain_file = fopen(dep_chain_path, "r"); 488 if (!chain_file) { 489 break; 490 } 491 492 char chain_spec[1024] = {0}; 493 if (!fgets(chain_spec, sizeof(chain_spec), chain_file)) { 494 fclose(chain_file); 495 fprintf(stderr, "Error: failed to read .dep.chain\n"); 496 return -1; 497 } 498 fclose(chain_file); 499 500 if (remove(dep_chain_path) != 0) { 501 fprintf(stderr, "Warning: failed to remove .dep.chain\n"); 502 } 503 504 char *trimmed = trim_whitespace(chain_spec); 505 if (strlen(trimmed) == 0) { 506 fprintf(stderr, "Warning: empty spec in .dep.chain\n"); 507 continue; 508 } 509 510 printf("Found .dep.chain, chaining to: %s\n", trimmed); 511 512 char *overlay_url = spec_to_url(name, trimmed); 513 if (!overlay_url) { 514 fprintf(stderr, "Error: failed to resolve chained spec '%s'\n", trimmed); 515 return -1; 516 } 517 518 printf("Overlaying %s from %s\n", name, overlay_url); 519 if (download_and_extract(overlay_url, lib_path) != 0) { 520 fprintf(stderr, "Error: failed to overlay chained dependency\n"); 521 free(overlay_url); 522 return -1; 523 } 524 free(overlay_url); 525 } 526 527 // Process .dep file in the dependency's directory 528 if (process_dep_file_in_dir(lib_path) != 0) { 529 fprintf(stderr, "Warning: failed to process .dep file for %s\n", name); 530 } 531 532 // Execute postinstall hook if present 533 if (execute_postinstall_hook(lib_path) != 0) { 534 fprintf(stderr, "Error: postinstall hook failed for %s\n", name); 535 return -1; 536 } 537 538 // Handle .dep.export file 539 if (process_dep_export_file(lib_path, name) != 0) { 540 fprintf(stderr, "Warning: failed to process .dep.export file for %s\n", name); 541 } 542 543 printf("Installed %s\n", name); 544 545 return 0; 546 } 547 548 static int process_dep_for_config(const char *name, FILE *dst_config) { 549 // Skip if already processed 550 if (config_is_visited(name)) { 551 return 0; 552 } 553 554 // Recurse into sub-dependencies first 555 char sub_dep_path[PATH_MAX]; 556 snprintf(sub_dep_path, sizeof(sub_dep_path), "lib/%s/.dep", name); 557 FILE *f = fopen(sub_dep_path, "r"); 558 if (f) { 559 char line[LINE_MAX]; 560 while (fgets(line, sizeof(line), f)) { 561 char *comment = strchr(line, '#'); 562 if (comment) *comment = '\0'; 563 564 char *trimmed = trim_whitespace(line); 565 if (strlen(trimmed) == 0) continue; 566 567 char sub_name[256] = {0}; 568 569 char *space_pos = strchr(trimmed, ' '); 570 char *tab_pos = strchr(trimmed, '\t'); 571 char *first_whitespace = NULL; 572 if (space_pos && tab_pos) { 573 first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; 574 } else if (space_pos) { 575 first_whitespace = space_pos; 576 } else if (tab_pos) { 577 first_whitespace = tab_pos; 578 } 579 580 if (first_whitespace) { 581 size_t name_len = first_whitespace - trimmed; 582 strncpy(sub_name, trimmed, name_len); 583 sub_name[name_len] = '\0'; 584 } else { 585 strncpy(sub_name, trimmed, sizeof(sub_name) - 1); 586 sub_name[sizeof(sub_name) - 1] = '\0'; 587 } 588 589 if (strlen(sub_name) == 0) continue; 590 591 process_dep_for_config(sub_name, dst_config); 592 } 593 fclose(f); 594 } 595 596 // Mark as visited and add export.mk content 597 if (config_mark_visited(name) < 0) { 598 return -1; 599 } 600 process_dep_config_mk(name, dst_config); 601 return 0; 602 } 603 604 static int rebuild_config_mk(void) { 605 config_clear_visited(); 606 607 char dep_dir[PATH_MAX]; 608 snprintf(dep_dir, sizeof(dep_dir), "lib/.dep"); 609 mkdir_recursive(dep_dir); 610 611 char config_mk_path[PATH_MAX]; 612 snprintf(config_mk_path, sizeof(config_mk_path), "%s/config.mk", dep_dir); 613 614 FILE *config_mk = fopen(config_mk_path, "w"); 615 if (!config_mk) { 616 fprintf(stderr, "Error: could not create %s\n", config_mk_path); 617 return -1; 618 } 619 620 fputs("CFLAGS+=-Ilib/.dep/include\n", config_mk); 621 622 FILE *f = fopen(".dep", "r"); 623 if (!f) { 624 fclose(config_mk); 625 return 0; 626 } 627 628 char line[LINE_MAX]; 629 while (fgets(line, sizeof(line), f)) { 630 char *comment = strchr(line, '#'); 631 if (comment) *comment = '\0'; 632 633 char *trimmed = trim_whitespace(line); 634 if (strlen(trimmed) == 0) continue; 635 636 char name[256] = {0}; 637 638 char *space_pos = strchr(trimmed, ' '); 639 char *tab_pos = strchr(trimmed, '\t'); 640 char *first_whitespace = NULL; 641 if (space_pos && tab_pos) { 642 first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; 643 } else if (space_pos) { 644 first_whitespace = space_pos; 645 } else if (tab_pos) { 646 first_whitespace = tab_pos; 647 } 648 649 if (first_whitespace) { 650 size_t name_len = first_whitespace - trimmed; 651 strncpy(name, trimmed, name_len); 652 name[name_len] = '\0'; 653 } else { 654 strncpy(name, trimmed, sizeof(name) - 1); 655 name[sizeof(name) - 1] = '\0'; 656 } 657 658 if (strlen(name) == 0) continue; 659 660 process_dep_for_config(name, config_mk); 661 } 662 663 fclose(f); 664 fclose(config_mk); 665 666 config_clear_visited(); 667 return 0; 668 } 669 670 static int cmd_install(int argc, const char **argv) { 671 (void)argc; 672 (void)argv; 673 674 const char *dep_path = ".dep"; 675 FILE *f = fopen(dep_path, "r"); 676 if (!f) { 677 fprintf(stderr, "Error: .dep file not found. Run 'dep init' first.\n"); 678 return 1; 679 } 680 681 if (!dir_exists("lib")) { 682 if (mkdir("lib", 0755) != 0) { 683 fprintf(stderr, "Error: could not create lib directory\n"); 684 fclose(f); 685 return 1; 686 } 687 } 688 689 char dep_dir[PATH_MAX]; 690 snprintf(dep_dir, sizeof(dep_dir), "lib/.dep"); 691 mkdir_recursive(dep_dir); 692 693 char line[LINE_MAX]; 694 int has_deps = 0; 695 696 // First pass: install all dependencies 697 while (fgets(line, sizeof(line), f)) { 698 char *comment = strchr(line, '#'); 699 if (comment) *comment = '\0'; 700 701 char *trimmed = trim_whitespace(line); 702 if (strlen(trimmed) == 0) continue; 703 704 has_deps = 1; 705 706 char name[256] = {0}; 707 char spec[1024] = {0}; 708 709 char *space_pos = strchr(trimmed, ' '); 710 char *tab_pos = strchr(trimmed, '\t'); 711 char *first_whitespace = NULL; 712 if (space_pos && tab_pos) { 713 first_whitespace = (space_pos < tab_pos) ? space_pos : tab_pos; 714 } else if (space_pos) { 715 first_whitespace = space_pos; 716 } else if (tab_pos) { 717 first_whitespace = tab_pos; 718 } 719 720 if (first_whitespace) { 721 size_t name_len = first_whitespace - trimmed; 722 strncpy(name, trimmed, name_len); 723 name[name_len] = '\0'; 724 strcpy(spec, trim_whitespace(first_whitespace + 1)); 725 } else { 726 strncpy(name, trimmed, sizeof(name) - 1); 727 name[sizeof(name) - 1] = '\0'; 728 } 729 730 if (strlen(name) == 0) continue; 731 732 if (install_dependency(name, spec) != 0) { 733 fclose(f); 734 return 1; 735 } 736 } 737 738 fclose(f); 739 740 // Pass 2: Rebuild config.mk from all installed dependencies 741 if (rebuild_config_mk() != 0) { 742 return 1; 743 } 744 745 if (!has_deps) { 746 printf("No dependencies to install\n"); 747 } 748 749 return 0; 750 } 751 752 void __attribute__((constructor)) cmd_install_setup(void) { 753 struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); 754 if (!cmd) { 755 fprintf(stderr, "Failed to allocate memory for install command\n"); 756 return; 757 } 758 cmd->next = commands; 759 cmd->fn = cmd_install; 760 static const char *install_names[] = {"install", "i", NULL}; 761 cmd->name = install_names; 762 cmd->display = "i(nstall)"; 763 cmd->description = "Install all the project's dependencies"; 764 cmd->help_text = 765 "dep install - Install all the project's dependencies\n" 766 "\n" 767 "Usage:\n" 768 " dep install\n" 769 "\n" 770 "Description:\n" 771 " Install all dependencies listed in the .dep file in the current directory.\n" 772 "\n" 773 " Dependencies are installed to the lib/ directory by default.\n" 774 "\n" 775 " Each dependency is downloaded and extracted to lib/<owner>/<name>/.\n" 776 "\n" 777 " If a dependency itself has dependencies listed in its own .dep file,\n" 778 " those will be installed recursively.\n"; 779 commands = cmd; 780 }