kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

install.c (8018B)


      1 #include <kit/core.h>
      2 #include <stddef.h>
      3 #include <stdint.h>
      4 #include <string.h>
      5 
      6 #include "driver.h"
      7 #include "env.h"
      8 
      9 /* `kit install` — busybox-style toolchain installer. Populates a target
     10  * directory with one entry per kit tool, each pointing at the running kit
     11  * binary, so the tools can be invoked by their bare names (cc, ld, nm, ...)
     12  * as a drop-in toolchain. Entries are symlinks on POSIX and hard links on
     13  * Windows (where unprivileged symlinks aren't generally available); either
     14  * can be forced with -s/-H.
     15  *
     16  * Multi-call dispatch in main.c resolves a link named `cc` to the cc tool by
     17  * argv[0]'s basename, so no extra wiring is needed for the links to work.
     18  *
     19  * The default set (no TOOL args, no --all) is the tools tagged with a non-zero
     20  * DriverToolGroup in the centralized table: the binutils/compiler toolchain
     21  * plus the standard-named byte utilities (xxd, cmp). */
     22 
     23 #define INSTALL_TOOL "install"
     24 
     25 void driver_help_install(void) {
     26   driver_printf(
     27       "kit install — symlink the kit tools into a directory\n"
     28       "\n"
     29       "USAGE\n"
     30       "  kit install [OPTIONS] DIR [TOOL...]\n"
     31       "\n"
     32       "DESCRIPTION\n"
     33       "  Populates DIR with one entry per kit tool, each pointing at the\n"
     34       "  running kit binary, so the tools can be invoked by their bare\n"
     35       "  names (cc, ld, nm, ...) as a drop-in toolchain. Entries are\n"
     36       "  symlinks on POSIX and hard links on Windows by default.\n"
     37       "\n"
     38       "  With no TOOL given, installs the default set: the binutils/compiler\n"
     39       "  toolchain (cc cpp as ld ar ranlib strip objcopy objdump nm size\n"
     40       "  addr2line strings) plus the standard-named byte utilities (xxd cmp\n"
     41       "  sha256sum b2sum crc32 gzip gunzip lz4 lz4c). Use --all for every\n"
     42       "  tool, or name specific TOOLs to install just those.\n"
     43       "\n"
     44       "OPTIONS\n"
     45       "  -s, --symlink    create symlinks (default except on Windows)\n"
     46       "  -H, --hardlink   create hard links (default on Windows)\n"
     47       "  -a, --all        install every tool compiled into this binary\n"
     48       "  -f, --force      replace existing entries\n"
     49       "  -n, --dry-run    print what would be done; change nothing\n"
     50       "  -v, --verbose    print each link as it is created\n"
     51       "  -h, --help       show this help\n"
     52       "\n"
     53       "EXIT CODES\n"
     54       "  0   success      1   one or more links failed      2   bad usage\n");
     55 }
     56 
     57 /* Join DIR + "/" + NAME into a freshly allocated, NUL-terminated path. Returns
     58  * NULL on allocation failure; on success stores the allocation size in
     59  * *out_size for driver_free. */
     60 static char* install_join(DriverEnv* env, const char* dir, const char* name,
     61                           size_t* out_size) {
     62   size_t dl = driver_strlen(dir);
     63   size_t nl = driver_strlen(name);
     64   int slash = dl > 0 && dir[dl - 1u] != '/';
     65   size_t size = dl + (slash ? 1u : 0u) + nl + 1u;
     66   size_t off;
     67   char* p = (char*)driver_alloc(env, size);
     68   if (!p) return NULL;
     69   driver_memcpy(p, dir, dl);
     70   off = dl;
     71   if (slash) p[off++] = '/';
     72   driver_memcpy(p + off, name, nl);
     73   off += nl;
     74   p[off] = '\0';
     75   *out_size = size;
     76   return p;
     77 }
     78 
     79 /* Create one link DIR/NAME -> self. Returns 0 on success, nonzero on failure
     80  * (already emitted a diagnostic). Honors dry-run (prints, no change), force
     81  * (replace existing), and verbose. */
     82 static int install_one(DriverEnv* env, const char* dir, const char* name,
     83                        const char* self, int want_hard, int force, int dry,
     84                        int verbose) {
     85   size_t path_size = 0;
     86   char* link_path = install_join(env, dir, name, &path_size);
     87   int rc = 0;
     88 
     89   if (!link_path) {
     90     driver_errf(INSTALL_TOOL, "out of memory building path for %s", name);
     91     return 1;
     92   }
     93 
     94   if (driver_path_lexists(link_path)) {
     95     if (!force) {
     96       driver_errf(INSTALL_TOOL, "%s already exists (use -f to overwrite)",
     97                   link_path);
     98       rc = 1;
     99       goto done;
    100     }
    101     if (!dry && driver_remove_file(link_path) != 0) {
    102       driver_errf(INSTALL_TOOL, "cannot replace %s", link_path);
    103       rc = 1;
    104       goto done;
    105     }
    106   }
    107 
    108   if (dry) {
    109     driver_printf("%s -> %s (dry-run)\n", link_path, self);
    110     goto done;
    111   }
    112 
    113   rc = want_hard ? driver_create_hardlink(self, link_path)
    114                  : driver_create_symlink(self, link_path);
    115   if (rc != 0) {
    116     driver_errf(INSTALL_TOOL, "failed to %s %s",
    117                 want_hard ? "hard-link" : "symlink", link_path);
    118     rc = 1;
    119     goto done;
    120   }
    121   if (verbose) driver_printf("%s -> %s\n", link_path, self);
    122 
    123 done:
    124   driver_free(env, link_path, path_size);
    125   return rc;
    126 }
    127 
    128 int driver_install(int argc, char** argv) {
    129   DriverEnv env;
    130   const char* dir = NULL;
    131   const char** explicit_tools = NULL;
    132   int nexplicit = 0;
    133   int want_hard = -1; /* -1 = pick by host OS; 0 = symlink; 1 = hard link */
    134   int all = 0, force = 0, dry = 0, verbose = 0, opts_done = 0;
    135   char* self = NULL;
    136   size_t self_size = 0;
    137   unsigned done_count = 0, failures = 0;
    138   int i, rc = 2;
    139 
    140   if (driver_argv_wants_help(argc, argv, 1)) {
    141     driver_help_install();
    142     return 0;
    143   }
    144 
    145   driver_env_init(&env);
    146 
    147   explicit_tools =
    148       (const char**)driver_alloc(&env, (size_t)argc * sizeof(*explicit_tools));
    149   if (!explicit_tools) {
    150     driver_errf(INSTALL_TOOL, "out of memory");
    151     rc = 1;
    152     goto done;
    153   }
    154 
    155   for (i = 1; i < argc; ++i) {
    156     const char* a = argv[i];
    157     if (!opts_done && driver_streq(a, "--")) {
    158       opts_done = 1;
    159       continue;
    160     }
    161     if (!opts_done && a[0] == '-' && a[1] != '\0') {
    162       if (driver_streq(a, "-s") || driver_streq(a, "--symlink")) {
    163         want_hard = 0;
    164       } else if (driver_streq(a, "-H") || driver_streq(a, "--hardlink")) {
    165         want_hard = 1;
    166       } else if (driver_streq(a, "-a") || driver_streq(a, "--all")) {
    167         all = 1;
    168       } else if (driver_streq(a, "-f") || driver_streq(a, "--force")) {
    169         force = 1;
    170       } else if (driver_streq(a, "-n") || driver_streq(a, "--dry-run")) {
    171         dry = 1;
    172       } else if (driver_streq(a, "-v") || driver_streq(a, "--verbose")) {
    173         verbose = 1;
    174       } else {
    175         driver_errf(INSTALL_TOOL, "unknown option: %s", a);
    176         goto done;
    177       }
    178       continue;
    179     }
    180     if (!dir)
    181       dir = a;
    182     else
    183       explicit_tools[nexplicit++] = a;
    184   }
    185 
    186   if (!dir) {
    187     driver_errf(INSTALL_TOOL, "missing target directory");
    188     goto done;
    189   }
    190 
    191   /* Validate explicit tool names up front so a typo fails before we touch the
    192    * filesystem. */
    193   for (i = 0; i < nexplicit; ++i) {
    194     if (driver_tool_find(explicit_tools[i]) < 0) {
    195       driver_errf(INSTALL_TOOL, "no such tool: %s", explicit_tools[i]);
    196       goto done;
    197     }
    198   }
    199 
    200   if (want_hard < 0)
    201     want_hard = (driver_host_target().os == KIT_OS_WINDOWS) ? 1 : 0;
    202 
    203   if (driver_self_exe_path(&env, &self, &self_size) != 0) {
    204     driver_errf(INSTALL_TOOL, "cannot determine path to the kit binary");
    205     rc = 1;
    206     goto done;
    207   }
    208 
    209   if (!dry && driver_mkdir_p(&env, dir) != 0) {
    210     driver_errf(INSTALL_TOOL, "cannot create directory %s", dir);
    211     rc = 1;
    212     goto done;
    213   }
    214 
    215   if (nexplicit > 0) {
    216     for (i = 0; i < nexplicit; ++i) {
    217       if (install_one(&env, dir, explicit_tools[i], self, want_hard, force, dry,
    218                       verbose) != 0)
    219         ++failures;
    220       else
    221         ++done_count;
    222     }
    223   } else {
    224     unsigned n = driver_tool_count();
    225     unsigned j;
    226     for (j = 0; j < n; ++j) {
    227       if (!all && driver_tool_groups(j) == 0) continue;
    228       if (install_one(&env, dir, driver_tool_name(j), self, want_hard, force,
    229                       dry, verbose) != 0)
    230         ++failures;
    231       else
    232         ++done_count;
    233     }
    234   }
    235 
    236   driver_printf("install: %s %u tool%s in %s\n",
    237                 dry ? "would install" : "installed", done_count,
    238                 done_count == 1u ? "" : "s", dir);
    239   rc = failures ? 1 : 0;
    240 
    241 done:
    242   if (self) driver_free(&env, self, self_size);
    243   if (explicit_tools)
    244     driver_free(&env, explicit_tools, (size_t)argc * sizeof(*explicit_tools));
    245   driver_env_fini(&env);
    246   return rc;
    247 }