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 }