dep

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

main.c (11871B)


      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 depname_copy[256];
    110   strncpy(depname_copy, depname, sizeof(depname_copy) - 1);
    111   depname_copy[sizeof(depname_copy) - 1] = '\0';
    112 
    113   char version_from_depname[256];
    114   extract_version_from_depname(depname_copy, depname, version_from_depname, depname_size, sizeof(version_from_depname));
    115 
    116   if (version_from_depname[0] != '\0') {
    117     strncpy(version, version_from_depname, version_size - 1);
    118     version[version_size - 1] = '\0';
    119   } else {
    120     version[0] = '\0';
    121   }
    122 
    123   free(line_copy);
    124   return result;
    125 }
    126 
    127 static int version_matches(const char *requested, const char *available) {
    128   if (!requested || requested[0] == '\0') {
    129     return 1;
    130   }
    131   if (!available || available[0] == '\0') {
    132     return 0;
    133   }
    134   return strcmp(requested, available) == 0;
    135 }
    136 
    137 static int append_to_dep_file(const char *name, const char *spec) {
    138   const char *dep_path = ".dep";
    139   FILE       *f        = fopen(dep_path, "a");
    140   if (!f) {
    141     fprintf(stderr, "Error: could not open .dep file for writing\n");
    142     return -1;
    143   }
    144 
    145   fseek(f, 0, SEEK_END);
    146   long pos = ftell(f);
    147   if (pos > 0) {
    148     fputc('\n', f);
    149   }
    150 
    151   if (spec && spec[0] != '\0') {
    152     fprintf(f, "%s %s\n", name, spec);
    153   } else {
    154     fprintf(f, "%s\n", name);
    155   }
    156 
    157   fclose(f);
    158   return 0;
    159 }
    160 
    161 static int add_from_url(const char *name, const char *url) {
    162   int result = append_to_dep_file(name, url);
    163   if (result == 0) {
    164     printf("Added %s to .dep\n", name);
    165   }
    166   return result;
    167 }
    168 
    169 static int add_from_repository(const char *name, const char *requested_version) {
    170   char *repo_dir = get_repo_dir();
    171   if (!repo_dir) {
    172     return -1;
    173   }
    174 
    175   char *cache_dir = get_cache_dir();
    176   if (!cache_dir) {
    177     free(repo_dir);
    178     return -1;
    179   }
    180 
    181   mkdir_recursive(cache_dir);
    182 
    183   DIR *dir = opendir(repo_dir);
    184   if (!dir) {
    185     fprintf(stderr, "Error: could not open repository directory\n");
    186     free(repo_dir);
    187     free(cache_dir);
    188     return -1;
    189   }
    190 
    191   struct dirent *entry;
    192   int            found = 0;
    193 
    194   while (!found && (entry = readdir(dir)) != NULL) {
    195     if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
    196 
    197     char *repo_dir2 = get_repo_dir();
    198     if (!repo_dir2) continue;
    199 
    200     char filepath[PATH_MAX];
    201     snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name);
    202     free(repo_dir2);
    203 
    204     struct stat st;
    205     if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue;
    206 
    207     FILE *repo_file = fopen(filepath, "r");
    208     if (!repo_file) continue;
    209 
    210     char line[LINE_MAX];
    211     while (!found && fgets(line, sizeof(line), repo_file)) {
    212       char *comment = strchr(line, '#');
    213       if (comment) *comment = '\0';
    214 
    215       char *trimmed = trim_whitespace(line);
    216       if (strlen(trimmed) == 0) continue;
    217 
    218       char repo_name[256]     = {0};
    219       char manifest_url[1024] = {0};
    220 
    221       char *space_pos = strchr(trimmed, ' ');
    222       char *tab_pos   = strchr(trimmed, '\t');
    223       char *first_ws  = NULL;
    224 
    225       if (space_pos && tab_pos) {
    226         first_ws = (space_pos < tab_pos) ? space_pos : tab_pos;
    227       } else if (space_pos) {
    228         first_ws = space_pos;
    229       } else if (tab_pos) {
    230         first_ws = tab_pos;
    231       }
    232 
    233       if (!first_ws) continue;
    234 
    235       size_t name_len = first_ws - trimmed;
    236       if (name_len >= sizeof(repo_name)) {
    237         name_len = sizeof(repo_name) - 1;
    238       }
    239       strncpy(repo_name, trimmed, name_len);
    240       repo_name[name_len] = '\0';
    241 
    242       char *url_start = trim_whitespace(first_ws + 1);
    243       strncpy(manifest_url, url_start, sizeof(manifest_url) - 1);
    244       manifest_url[sizeof(manifest_url) - 1] = '\0';
    245 
    246       if (strlen(manifest_url) == 0) continue;
    247 
    248       char cache_filename[256];
    249       url_to_cache_filename(manifest_url, cache_filename, sizeof(cache_filename));
    250 
    251       char cache_path[PATH_MAX];
    252       snprintf(cache_path, sizeof(cache_path), "%s%s", cache_dir, cache_filename);
    253 
    254       if (is_cache_outdated(cache_path)) {
    255         printf("Updating cache for repository %s...\n", repo_name);
    256         size_t size;
    257         char  *manifest_content = download_url(manifest_url, &size);
    258         if (manifest_content) {
    259           FILE *cache_file = fopen(cache_path, "w");
    260           if (cache_file) {
    261             fwrite(manifest_content, 1, size, cache_file);
    262             fclose(cache_file);
    263           }
    264           free(manifest_content);
    265         }
    266       }
    267 
    268       FILE *cache_file = fopen(cache_path, "r");
    269       if (!cache_file) continue;
    270 
    271       char manifest_line[LINE_MAX];
    272       while (!found && fgets(manifest_line, sizeof(manifest_line), cache_file)) {
    273         char depname[256]      = {0};
    274         char version[256]      = {0};
    275         char tarball_url[1024] = {0};
    276 
    277         if (parse_manifest_line(manifest_line, depname, version, tarball_url, sizeof(depname), sizeof(version),
    278                                 sizeof(tarball_url)) != 0) {
    279           continue;
    280         }
    281 
    282         if (strcmp(depname, name) != 0) continue;
    283 
    284         if (!version_matches(requested_version, version)) continue;
    285 
    286         if (strlen(tarball_url) == 0) continue;
    287 
    288         printf("Found %s", name);
    289         if (version[0] != '\0') {
    290           printf(" version %s", version);
    291         }
    292         printf(" in repository %s\n", repo_name);
    293 
    294         fclose(cache_file);
    295         closedir(dir);
    296         free(repo_dir);
    297         free(cache_dir);
    298         return append_to_dep_file(name, tarball_url);
    299       }
    300 
    301       fclose(cache_file);
    302     }
    303 
    304     fclose(repo_file);
    305   }
    306 
    307   closedir(dir);
    308   free(repo_dir);
    309   free(cache_dir);
    310 
    311   return found ? 0 : -1;
    312 }
    313 
    314 static int add_from_github(const char *name, const char *requested_version) {
    315   char *full_ref = NULL;
    316 
    317   if (requested_version && requested_version[0] != '\0') {
    318     full_ref = github_matching_ref(name, requested_version);
    319     if (!full_ref) {
    320       fprintf(stderr, "Error: ref '%s' not found for %s on GitHub\n", requested_version, name);
    321       return -1;
    322     }
    323     free(full_ref);
    324     printf("Found %s version %s on GitHub\n", name, requested_version);
    325     fflush(stdout);
    326     return append_to_dep_file(name, requested_version);
    327   } else {
    328     char *branch = github_default_branch(name);
    329     if (!branch) {
    330       fprintf(stderr, "Error: could not determine default branch for %s on GitHub\n", name);
    331       return -1;
    332     }
    333     printf("Found %s on GitHub (default branch: %s)\n", name, branch);
    334     fflush(stdout);
    335     free(branch);
    336     return append_to_dep_file(name, "");
    337   }
    338 }
    339 
    340 static int cmd_add(int argc, const char **argv) {
    341   struct argparse_option options[] = {
    342       OPT_HELP(),
    343       OPT_END(),
    344   };
    345 
    346   struct argparse argparse;
    347   argparse_init(&argparse, options, NULL, 0);
    348   argc = argparse_parse(&argparse, argc, argv);
    349 
    350   if (argc < 1) {
    351     fprintf(stderr, "Error: add requires <name> [version]\n");
    352     fprintf(stderr, "Usage: dep add <name> [version]\n");
    353     return 1;
    354   }
    355 
    356   const char *name    = argv[0];
    357   const char *version = (argc > 1) ? argv[1] : NULL;
    358 
    359   if (version && is_url(version)) {
    360     printf("Adding %s as direct URL: %s\n", name, version);
    361     return add_from_url(name, version);
    362   }
    363 
    364   printf("Searching repositories for %s", name);
    365   if (version && strlen(version) > 0) {
    366     printf(" with version %s", version);
    367   }
    368   printf("...\n");
    369   fflush(stdout);
    370 
    371   int result = add_from_repository(name, version);
    372   if (result == 0) {
    373     printf("Added %s to .dep\n", name);
    374     fflush(stdout);
    375     return 0;
    376   }
    377 
    378   printf("Not found in repositories, trying GitHub...\n");
    379   fflush(stdout);
    380   result = add_from_github(name, version);
    381   if (result == 0) {
    382     printf("Added %s to .dep\n", name);
    383     return 0;
    384   }
    385 
    386   fprintf(stderr, "Error: package '%s' not found\n", name);
    387   return 1;
    388 }
    389 
    390 void __attribute__((constructor)) cmd_add_setup(void) {
    391   struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct));
    392   if (!cmd) {
    393     fprintf(stderr, "Failed to allocate memory for add command\n");
    394     return;
    395   }
    396   cmd->next                      = commands;
    397   cmd->fn                        = cmd_add;
    398   static const char *add_names[] = {"add", "a", NULL};
    399   cmd->name                      = add_names;
    400   cmd->display                   = "a(dd)";
    401   cmd->description               = "Add a new dependency to the project";
    402   cmd->help_text =
    403       "dep add - Add a new dependency to the project\n"
    404       "\n"
    405       "Usage:\n"
    406       "  dep add <name>\n"
    407       "  dep add <name> <version>\n"
    408       "  dep add <name> <url>\n"
    409       "\n"
    410       "Description:\n"
    411       "  Add a package to the project's .dep file.\n"
    412       "\n"
    413       "  If a version is not specified, the latest version from the repository\n"
    414       "  will be used, or the default branch from GitHub.\n"
    415       "\n"
    416       "  You can also add a dependency with a direct URL.\n"
    417       "\n"
    418       "Examples:\n"
    419       "  dep add finwo/palloc           # Latest from repo or GitHub main branch\n"
    420       "  dep add finwo/palloc edge     # Specific version/branch\n"
    421       "  dep add finwo/palloc v1.0.0   # Specific tag\n"
    422       "  dep add mylib https://example.com/mylib.tar.gz\n";
    423   commands = cmd;
    424 }