kit

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

make.c (7637B)


      1 #include <kit/core.h>
      2 #include <kit/make.h>
      3 #include <stddef.h>
      4 #include <stdint.h>
      5 
      6 #include "driver.h"
      7 #include "env.h"
      8 
      9 #define MAKE_TOOL "make"
     10 
     11 void driver_help_make(void) {
     12   driver_printf(
     13       "kit make — bring targets up to date from a makefile (POSIX make)\n"
     14       "\n"
     15       "USAGE\n"
     16       "  kit make [-f MAKEFILE] [OPTION]... [MACRO=VALUE]... [TARGET]...\n"
     17       "\n"
     18       "DESCRIPTION\n"
     19       "  Reads one or more makefiles, builds a dependency graph, and runs the\n"
     20       "  recipes needed to bring the requested targets up to date. Recipes run\n"
     21       "  through /bin/sh (or the SHELL macro). Ported from the public-domain\n"
     22       "  pdpmake; supports POSIX-2024 make plus common extensions (include,\n"
     23       "  ifdef/ifeq, ::, ::=/:=, $(shell), -C).\n"
     24       "\n"
     25       "OPTIONS\n"
     26       "  -f FILE      read FILE as a makefile ('-' reads stdin); may repeat\n"
     27       "  -C DIR       change to DIR before reading makefiles\n"
     28       "  -e           environment variables override makefile macros\n"
     29       "  -i           ignore errors from recipe commands\n"
     30       "  -k           keep going with unrelated targets after an error\n"
     31       "  -n           print recipes without running them (dry run)\n"
     32       "  -p           print the macro and target database\n"
     33       "  -q           question mode: exit 0 if up to date, 1 if not\n"
     34       "  -r           do not use the built-in inference rules and macros\n"
     35       "  -s           do not echo recipe lines\n"
     36       "  -S           stop on the first error (undo -k)\n"
     37       "  -t           touch targets instead of running recipes\n"
     38       "  -j N         accepted for compatibility; recipes run serially\n"
     39       "  --posix      enforce strict POSIX mode\n"
     40       "  -h, --help   show this help and exit\n"
     41       "\n"
     42       "EXIT CODES\n"
     43       "  0   success        1   -q: a target is out of date\n"
     44       "  2   build error or bad command-line usage\n");
     45 }
     46 
     47 /* --- KitMakeHost callbacks, backed by the driver host shims --------------- */
     48 
     49 static int mk_mtime(void* user, const char* path, int64_t* out_ns) {
     50   (void)user;
     51   return driver_path_mtime_ns(path, out_ns);
     52 }
     53 
     54 static int mk_touch(void* user, const char* path) {
     55   (void)user;
     56   return driver_touch(path);
     57 }
     58 
     59 static int mk_remove(void* user, const char* path) {
     60   (void)user;
     61   return driver_remove_file(path);
     62 }
     63 
     64 /* An operand of the form NAME=VALUE (with NAME non-empty) is a macro. */
     65 static const char* macro_eq(const char* s) {
     66   const char* eq = driver_strchr(s, '=');
     67   return (eq && eq != s) ? eq : NULL;
     68 }
     69 
     70 int driver_make(int argc, char** argv) {
     71   DriverEnv env;
     72   KitContext ctx;
     73   KitExec ex;
     74   KitMakeHost host;
     75   KitMakeOptions opts = {0};
     76   KitMakeResult result;
     77   KitWriter* out = NULL;
     78   KitWriter* err = NULL;
     79   const char** makefiles = NULL;
     80   const char** targets = NULL;
     81   KitMakeVar* macros = NULL;
     82   size_t nmf = 0, ntg = 0, nmac = 0;
     83   const char* chdir_to = NULL;
     84   int want_stdin = 0;
     85   uint8_t* stdin_buf = NULL;
     86   size_t stdin_len = 0;
     87   char* curdir_buf = NULL;
     88   size_t curdir_size = 0;
     89   size_t cap = (size_t)(argc > 0 ? argc : 1);
     90   int rc = 2;
     91   int i;
     92 
     93   if (driver_argv_wants_help(argc, argv, 1)) {
     94     driver_help_make();
     95     return 0;
     96   }
     97 
     98   driver_env_init(&env);
     99   ex = driver_exec(&env);
    100 
    101   makefiles = driver_alloc_zeroed(&env, cap * sizeof *makefiles);
    102   targets = driver_alloc_zeroed(&env, cap * sizeof *targets);
    103   macros = driver_alloc_zeroed(&env, cap * sizeof *macros);
    104   if (!makefiles || !targets || !macros) {
    105     driver_errf(MAKE_TOOL, "out of memory");
    106     goto done;
    107   }
    108 
    109   for (i = 1; i < argc; ++i) {
    110     const char* a = argv[i];
    111     const char* eq;
    112 
    113     if (a[0] == '-' && a[1] != '\0') {
    114       const char* p;
    115       if (driver_streq(a, "--posix")) {
    116         opts.flags |= KIT_MAKE_F_POSIX;
    117         continue;
    118       }
    119       if (a[1] == '-') {
    120         driver_errf(MAKE_TOOL, "unknown option: %s", a);
    121         goto done;
    122       }
    123       for (p = a + 1; *p; ++p) {
    124         char c = *p;
    125         if (c == 'f' || c == 'C' || c == 'j') {
    126           const char* val = p[1] ? p + 1 : NULL;
    127           if (!val) {
    128             if (i + 1 < argc)
    129               val = argv[++i];
    130             else {
    131               driver_errf(MAKE_TOOL, "option -%c requires an argument", c);
    132               goto done;
    133             }
    134           }
    135           if (c == 'f') {
    136             if (driver_streq(val, "-")) want_stdin = 1;
    137             makefiles[nmf++] = val;
    138           } else if (c == 'C') {
    139             chdir_to = val;
    140           } else {
    141             opts.jobs = 1; /* -j: recipes run serially in v1 */
    142           }
    143           break; /* the rest of the cluster was the argument */
    144         }
    145         switch (c) {
    146           case 'e': opts.flags |= KIT_MAKE_F_ENV_OVERRIDE; break;
    147           case 'i': opts.flags |= KIT_MAKE_F_IGNORE_ERR; break;
    148           case 'k': opts.flags |= KIT_MAKE_F_KEEP_GOING; break;
    149           case 'n': opts.flags |= KIT_MAKE_F_DRY_RUN; break;
    150           case 'p': opts.flags |= KIT_MAKE_F_PRINT_DB; break;
    151           case 'q': opts.flags |= KIT_MAKE_F_QUESTION; break;
    152           case 'r': opts.flags |= KIT_MAKE_F_NO_BUILTINS; break;
    153           case 's': opts.flags |= KIT_MAKE_F_SILENT; break;
    154           case 'S': opts.flags &= ~(uint32_t)KIT_MAKE_F_KEEP_GOING; break;
    155           case 't': opts.flags |= KIT_MAKE_F_TOUCH; break;
    156           default:
    157             driver_errf(MAKE_TOOL, "unknown option: -%c", c);
    158             goto done;
    159         }
    160       }
    161     } else if ((eq = macro_eq(a)) != NULL) {
    162       macros[nmac].name.s = a;
    163       macros[nmac].name.len = (size_t)(eq - a);
    164       macros[nmac].value.s = eq + 1;
    165       macros[nmac].value.len = driver_strlen(eq + 1);
    166       nmac++;
    167     } else {
    168       targets[ntg++] = a;
    169     }
    170   }
    171 
    172   if (want_stdin && !driver_read_stdin(&env, &stdin_buf, &stdin_len)) {
    173     driver_errf(MAKE_TOOL, "failed to read stdin");
    174     goto done;
    175   }
    176 
    177   /* Root/working directory: make resolves relative makefile/target paths
    178    * against it and runs recipes there (the engine never chdir()s). -C selects
    179    * it, otherwise the current directory. Also becomes $(CURDIR). */
    180   {
    181     const char* dir = chdir_to ? chdir_to : ".";
    182     if (driver_path_canonicalize(&env, dir, &curdir_buf, &curdir_size) != 0) {
    183       if (chdir_to) {
    184         driver_errf(MAKE_TOOL, "can't resolve directory %s", chdir_to);
    185         goto done;
    186       }
    187     } else {
    188       opts.curdir = curdir_buf;
    189     }
    190   }
    191 
    192   out = driver_stdout_writer(&env);
    193   err = driver_stderr_writer(&env);
    194   if (!out || !err) {
    195     driver_errf(MAKE_TOOL, "failed to open output streams");
    196     goto done;
    197   }
    198 
    199   host.mtime = mk_mtime;
    200   host.exec = &ex;
    201   host.touch = mk_touch;
    202   host.remove_file = mk_remove;
    203   host.user = &env;
    204 
    205   opts.makefiles = makefiles;
    206   opts.nmakefiles = nmf;
    207   opts.targets = targets;
    208   opts.ntargets = ntg;
    209   opts.macros = macros;
    210   opts.nmacros = nmac;
    211   opts.env = driver_environ();
    212   opts.shell = NULL; /* engine defaults to /bin/sh; SHELL macro can override */
    213   opts.stdin_makefile = stdin_buf;
    214   opts.stdin_makefile_len = stdin_len;
    215   opts.out = out;
    216   opts.err = err;
    217 
    218   ctx = driver_env_to_context(&env);
    219   result.exit_code = 2;
    220   if (kit_make_run(&ctx, &host, &opts, &result) == KIT_OK) rc = result.exit_code;
    221 
    222 done:
    223   if (out) kit_writer_close(out);
    224   if (err) kit_writer_close(err);
    225   if (stdin_buf) driver_free(&env, stdin_buf, stdin_len);
    226   if (curdir_buf) driver_free(&env, curdir_buf, curdir_size);
    227   if (makefiles) driver_free(&env, makefiles, cap * sizeof *makefiles);
    228   if (targets) driver_free(&env, targets, cap * sizeof *targets);
    229   if (macros) driver_free(&env, macros, cap * sizeof *macros);
    230   driver_env_fini(&env);
    231   return rc;
    232 }