dep

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

main.c (11730B)


      1 #include <dirent.h>
      2 #include <limits.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <sys/stat.h>
      7 #include <time.h>
      8 #include <unistd.h>
      9 
     10 #include "cofyc/argparse.h"
     11 #include "command/command.h"
     12 #include "common/fs-utils.h"
     13 #include "common/github-utils.h"
     14 #include "common/net-utils.h"
     15 
     16 #define CACHE_MAX_AGE_SECONDS (7 * 24 * 60 * 60)
     17 
     18 static unsigned long hash_string(const char *str) {
     19   unsigned long hash = 5381;
     20   int           c;
     21   while ((c = *str++)) {
     22     hash = ((hash << 5) + hash) + c;
     23   }
     24   return hash;
     25 }
     26 
     27 static void url_to_cache_filename(const char *url, char *filename, size_t size) {
     28   unsigned long hash = hash_string(url);
     29   snprintf(filename, size, "%lu", hash);
     30 }
     31 
     32 static int is_cache_outdated(const char *cache_path) {
     33   struct stat st;
     34   if (stat(cache_path, &st) != 0) {
     35     return 1;
     36   }
     37   time_t now      = time(NULL);
     38   time_t file_age = now - st.st_mtime;
     39   return file_age > CACHE_MAX_AGE_SECONDS;
     40 }
     41 
     42 static int extract_version_from_depname(const char *depname_with_version, char *depname, char *version,
     43                                         size_t depname_size, size_t version_size) {
     44   const char *at_pos = strchr(depname_with_version, '@');
     45   if (!at_pos) {
     46     strncpy(depname, depname_with_version, depname_size - 1);
     47     depname[depname_size - 1] = '\0';
     48     version[0]                = '\0';
     49     return 0;
     50   }
     51 
     52   size_t depname_len = at_pos - depname_with_version;
     53   if (depname_len >= depname_size) {
     54     depname_len = depname_size - 1;
     55   }
     56   strncpy(depname, depname_with_version, depname_len);
     57   depname[depname_len] = '\0';
     58 
     59   strncpy(version, at_pos + 1, version_size - 1);
     60   version[version_size - 1] = '\0';
     61   return 0;
     62 }
     63 
     64 static int parse_manifest_line(const char *line, char *depname, char *version, char *url, size_t depname_size,
     65                                size_t version_size, size_t url_size) {
     66   char *line_copy = strdup(line);
     67   if (!line_copy) return -1;
     68 
     69   char *comment = strchr(line_copy, '#');
     70   if (comment) *comment = '\0';
     71 
     72   char *trimmed = trim_whitespace(line_copy);
     73   if (strlen(trimmed) == 0) {
     74     free(line_copy);
     75     return -1;
     76   }
     77 
     78   char *space_pos = strchr(trimmed, ' ');
     79   char *tab_pos   = strchr(trimmed, '\t');
     80   char *first_ws  = NULL;
     81 
     82   if (space_pos && tab_pos) {
     83     first_ws = (space_pos < tab_pos) ? space_pos : tab_pos;
     84   } else if (space_pos) {
     85     first_ws = space_pos;
     86   } else if (tab_pos) {
     87     first_ws = tab_pos;
     88   }
     89 
     90   int result = 0;
     91 
     92   if (first_ws) {
     93     size_t name_len = first_ws - trimmed;
     94     if (name_len >= depname_size) {
     95       name_len = depname_size - 1;
     96     }
     97     strncpy(depname, trimmed, name_len);
     98     depname[name_len] = '\0';
     99 
    100     char *url_start = trim_whitespace(first_ws + 1);
    101     strncpy(url, url_start, url_size - 1);
    102     url[url_size - 1] = '\0';
    103   } else {
    104     strncpy(depname, trimmed, depname_size - 1);
    105     depname[depname_size - 1] = '\0';
    106     url[0]                    = '\0';
    107   }
    108 
    109   char version_from_depname[256];
    110   extract_version_from_depname(depname, depname, version_from_depname, depname_size, sizeof(version_from_depname));
    111 
    112   if (version_from_depname[0] != '\0') {
    113     strncpy(version, version_from_depname, version_size - 1);
    114     version[version_size - 1] = '\0';
    115   } else {
    116     version[0] = '\0';
    117   }
    118 
    119   free(line_copy);
    120   return result;
    121 }
    122 
    123 static int version_matches(const char *requested, const char *available) {
    124   if (!requested || requested[0] == '\0') {
    125     return 1;
    126   }
    127   if (!available || available[0] == '\0') {
    128     return 0;
    129   }
    130   return strcmp(requested, available) == 0;
    131 }
    132 
    133 static int append_to_dep_file(const char *name, const char *spec) {
    134   const char *dep_path = ".dep";
    135   FILE       *f        = fopen(dep_path, "a");
    136   if (!f) {
    137     fprintf(stderr, "Error: could not open .dep file for writing\n");
    138     return -1;
    139   }
    140 
    141   fseek(f, 0, SEEK_END);
    142   long pos = ftell(f);
    143   if (pos > 0) {
    144     fputc('\n', f);
    145   }
    146 
    147   if (spec && spec[0] != '\0') {
    148     fprintf(f, "%s %s\n", name, spec);
    149   } else {
    150     fprintf(f, "%s\n", name);
    151   }
    152 
    153   fclose(f);
    154   return 0;
    155 }
    156 
    157 static int add_from_url(const char *name, const char *url) {
    158   int result = append_to_dep_file(name, url);
    159   if (result == 0) {
    160     printf("Added %s to .dep\n", name);
    161   }
    162   return result;
    163 }
    164 
    165 static int add_from_repository(const char *name, const char *requested_version) {
    166   char *repo_dir = get_repo_dir();
    167   if (!repo_dir) {
    168     return -1;
    169   }
    170 
    171   char *cache_dir = get_cache_dir();
    172   if (!cache_dir) {
    173     free(repo_dir);
    174     return -1;
    175   }
    176 
    177   mkdir_recursive(cache_dir);
    178 
    179   DIR *dir = opendir(repo_dir);
    180   if (!dir) {
    181     fprintf(stderr, "Error: could not open repository directory\n");
    182     free(repo_dir);
    183     free(cache_dir);
    184     return -1;
    185   }
    186 
    187   struct dirent *entry;
    188   int            found = 0;
    189 
    190   while (!found && (entry = readdir(dir)) != NULL) {
    191     if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
    192 
    193     char *repo_dir2 = get_repo_dir();
    194     if (!repo_dir2) continue;
    195 
    196     char filepath[PATH_MAX];
    197     snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name);
    198     free(repo_dir2);
    199 
    200     struct stat st;
    201     if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue;
    202 
    203     FILE *repo_file = fopen(filepath, "r");
    204     if (!repo_file) continue;
    205 
    206     char line[LINE_MAX];
    207     while (!found && fgets(line, sizeof(line), repo_file)) {
    208       char *comment = strchr(line, '#');
    209       if (comment) *comment = '\0';
    210 
    211       char *trimmed = trim_whitespace(line);
    212       if (strlen(trimmed) == 0) continue;
    213 
    214       char repo_name[256]     = {0};
    215       char manifest_url[1024] = {0};
    216 
    217       char *space_pos = strchr(trimmed, ' ');
    218       char *tab_pos   = strchr(trimmed, '\t');
    219       char *first_ws  = NULL;
    220 
    221       if (space_pos && tab_pos) {
    222         first_ws = (space_pos < tab_pos) ? space_pos : tab_pos;
    223       } else if (space_pos) {
    224         first_ws = space_pos;
    225       } else if (tab_pos) {
    226         first_ws = tab_pos;
    227       }
    228 
    229       if (!first_ws) continue;
    230 
    231       size_t name_len = first_ws - trimmed;
    232       if (name_len >= sizeof(repo_name)) {
    233         name_len = sizeof(repo_name) - 1;
    234       }
    235       strncpy(repo_name, trimmed, name_len);
    236       repo_name[name_len] = '\0';
    237 
    238       char *url_start = trim_whitespace(first_ws + 1);
    239       strncpy(manifest_url, url_start, sizeof(manifest_url) - 1);
    240       manifest_url[sizeof(manifest_url) - 1] = '\0';
    241 
    242       if (strlen(manifest_url) == 0) continue;
    243 
    244       char cache_filename[256];
    245       url_to_cache_filename(manifest_url, cache_filename, sizeof(cache_filename));
    246 
    247       char cache_path[PATH_MAX];
    248       snprintf(cache_path, sizeof(cache_path), "%s%s", cache_dir, cache_filename);
    249 
    250       if (is_cache_outdated(cache_path)) {
    251         printf("Updating cache for repository %s...\n", repo_name);
    252         size_t size;
    253         char  *manifest_content = download_url(manifest_url, &size);
    254         if (manifest_content) {
    255           FILE *cache_file = fopen(cache_path, "w");
    256           if (cache_file) {
    257             fwrite(manifest_content, 1, size, cache_file);
    258             fclose(cache_file);
    259           }
    260           free(manifest_content);
    261         }
    262       }
    263 
    264       FILE *cache_file = fopen(cache_path, "r");
    265       if (!cache_file) continue;
    266 
    267       char manifest_line[LINE_MAX];
    268       while (!found && fgets(manifest_line, sizeof(manifest_line), cache_file)) {
    269         char depname[256]      = {0};
    270         char version[256]      = {0};
    271         char tarball_url[1024] = {0};
    272 
    273         if (parse_manifest_line(manifest_line, depname, version, tarball_url, sizeof(depname), sizeof(version),
    274                                 sizeof(tarball_url)) != 0) {
    275           continue;
    276         }
    277 
    278         if (strcmp(depname, name) != 0) continue;
    279 
    280         if (!version_matches(requested_version, version)) continue;
    281 
    282         if (strlen(tarball_url) == 0) continue;
    283 
    284         printf("Found %s", name);
    285         if (version[0] != '\0') {
    286           printf(" version %s", version);
    287         }
    288         printf(" in repository %s\n", repo_name);
    289 
    290         fclose(cache_file);
    291         closedir(dir);
    292         free(repo_dir);
    293         free(cache_dir);
    294         return append_to_dep_file(name, tarball_url);
    295       }
    296 
    297       fclose(cache_file);
    298     }
    299 
    300     fclose(repo_file);
    301   }
    302 
    303   closedir(dir);
    304   free(repo_dir);
    305   free(cache_dir);
    306 
    307   return found ? 0 : -1;
    308 }
    309 
    310 static int add_from_github(const char *name, const char *requested_version) {
    311   char *full_ref = NULL;
    312 
    313   if (requested_version && requested_version[0] != '\0') {
    314     full_ref = github_matching_ref(name, requested_version);
    315     if (!full_ref) {
    316       fprintf(stderr, "Error: ref '%s' not found for %s on GitHub\n", requested_version, name);
    317       return -1;
    318     }
    319     free(full_ref);
    320     printf("Found %s version %s on GitHub\n", name, requested_version);
    321     fflush(stdout);
    322     return append_to_dep_file(name, requested_version);
    323   } else {
    324     char *branch = github_default_branch(name);
    325     if (!branch) {
    326       fprintf(stderr, "Error: could not determine default branch for %s on GitHub\n", name);
    327       return -1;
    328     }
    329     printf("Found %s on GitHub (default branch: %s)\n", name, branch);
    330     fflush(stdout);
    331     free(branch);
    332     return append_to_dep_file(name, "");
    333   }
    334 }
    335 
    336 static int cmd_add(int argc, const char **argv) {
    337   struct argparse_option options[] = {
    338       OPT_HELP(),
    339       OPT_END(),
    340   };
    341 
    342   struct argparse argparse;
    343   argparse_init(&argparse, options, NULL, 0);
    344   argc = argparse_parse(&argparse, argc, argv);
    345 
    346   if (argc < 1) {
    347     fprintf(stderr, "Error: add requires <name> [version]\n");
    348     fprintf(stderr, "Usage: dep add <name> [version]\n");
    349     return 1;
    350   }
    351 
    352   const char *name    = argv[0];
    353   const char *version = (argc > 1) ? argv[1] : NULL;
    354 
    355   if (version && is_url(version)) {
    356     printf("Adding %s as direct URL: %s\n", name, version);
    357     return add_from_url(name, version);
    358   }
    359 
    360   printf("Searching repositories for %s", name);
    361   if (version && strlen(version) > 0) {
    362     printf(" with version %s", version);
    363   }
    364   printf("...\n");
    365   fflush(stdout);
    366 
    367   int result = add_from_repository(name, version);
    368   if (result == 0) {
    369     printf("Added %s to .dep\n", name);
    370     fflush(stdout);
    371     return 0;
    372   }
    373 
    374   printf("Not found in repositories, trying GitHub...\n");
    375   fflush(stdout);
    376   result = add_from_github(name, version);
    377   if (result == 0) {
    378     printf("Added %s to .dep\n", name);
    379     return 0;
    380   }
    381 
    382   fprintf(stderr, "Error: package '%s' not found\n", name);
    383   return 1;
    384 }
    385 
    386 void __attribute__((constructor)) cmd_add_setup(void) {
    387   struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
    388   if (!cmd) {
    389     fprintf(stderr, "Failed to allocate memory for add command\n");
    390     return;
    391   }
    392   cmd->next                      = commands;
    393   cmd->fn                        = cmd_add;
    394   static const char *add_names[] = {"add", "a", NULL};
    395   cmd->name                      = add_names;
    396   cmd->display                   = "a(dd)";
    397   cmd->description               = "Add a new dependency to the project";
    398   cmd->help_text =
    399       "dep add - Add a new dependency to the project\n"
    400       "\n"
    401       "Usage:\n"
    402       "  dep add <name>\n"
    403       "  dep add <name> <version>\n"
    404       "  dep add <name> <url>\n"
    405       "\n"
    406       "Description:\n"
    407       "  Add a package to the project's .dep file.\n"
    408       "\n"
    409       "  If a version is not specified, the latest version from the repository\n"
    410       "  will be used, or the default branch from GitHub.\n"
    411       "\n"
    412       "  You can also add a dependency with a direct URL.\n"
    413       "\n"
    414       "Examples:\n"
    415       "  dep add finwo/palloc           # Latest from repo or GitHub main branch\n"
    416       "  dep add finwo/palloc edge     # Specific version/branch\n"
    417       "  dep add finwo/palloc v1.0.0   # Specific tag\n"
    418       "  dep add mylib https://example.com/mylib.tar.gz\n";
    419   commands = cmd;
    420 }