├── all.do ├── redo.do ├── links.do ├── redo-targets ├── bootstrap.sh ├── redo-sources ├── README.md └── redo.c /all.do: -------------------------------------------------------------------------------- 1 | redo-ifchange redo links 2 | -------------------------------------------------------------------------------- /redo.do: -------------------------------------------------------------------------------- 1 | redo-ifchange redo.c 2 | cc -g -Os -Wall -Wextra -Wwrite-strings -o $3 $1.c -------------------------------------------------------------------------------- /links.do: -------------------------------------------------------------------------------- 1 | ln -sf redo redo-ifcreate 2 | ln -sf redo redo-ifchange 3 | ln -sf redo redo-always 4 | -------------------------------------------------------------------------------- /redo-targets: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # redo-targets - list files redo can build 3 | 4 | find . -name '.dep.*' | sed 's,\(.*\)/\.dep\.,\1/,' | sort 5 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # bootstrap script 3 | 4 | ( 5 | alias redo-ifchange=: 6 | set -- redo redo redo 7 | set -ex 8 | . ./redo.do 9 | ) 10 | 11 | export PATH=$PWD:$PATH 12 | redo -f links 13 | redo -f all 14 | -------------------------------------------------------------------------------- /redo-sources: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # redo-sources - list dependencies which are not targets 3 | 4 | find . -name '.dep.*' | xargs grep -h '^=' | cut -c84- | 5 | while read f; do 6 | [ -e "$f" ] && 7 | ! [ -e "$(printf '%s' "$f" | sed 's,\(.*/\)\|,\1.dep.,')" ] && 8 | printf '%s\n' "$f" 9 | done | sort -u 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redo-c 2 | 3 | redo-c is an implementation of the redo build system (designed by 4 | Daniel J. Bernstein) in portable C with zero external dependencies. 5 | 6 | ## Documentation 7 | 8 | Please refer to the documentation for 9 | [redo in Python](https://github.com/apenwarr/redo/blob/master/README.md), 10 | or the [tutorial by Jonathan de Boyne Pollard](http://jdebp.info/FGA/introduction-to-redo.html) 11 | for usage instructions. 12 | 13 | ## Notes about the redo-c implementation of redo 14 | 15 | * Without arguments, `redo` behaves like `redo all`. 16 | 17 | * `.do` files always are executed in their directory, arguments are 18 | relative paths. 19 | 20 | * Standard output of `.do` files is only captured as build product if 21 | `redo -s` is used, or the environment variable `REDO_STDOUT` is set to 1. 22 | Else, standard output is simply displayed. 23 | 24 | * Non-executable `.do` files are run with `/bin/sh -e`. 25 | `redo -x` can be utilized to use `/bin/sh -e -x` instead, for 26 | debugging `.do` files or verbose builds. 27 | 28 | * Executable `.do` files are simply executed, and should have a shebang line. 29 | 30 | * When a target makes no output, no target file is created. The target 31 | is considered always out of date. 32 | 33 | * `default.do` files are checked in all parent directories up to `/`. 34 | 35 | * Parallel builds can be started with `redo -j N` (or `JOBS=N redo`), 36 | this uses a job broker similar to but not compatible with GNU make. 37 | 38 | * To detect whether a file has changed, we first compare `ctime` and 39 | in case it differs, a SHA2 hash of the contents. 40 | 41 | * Dependencies are tracked in `.dep.BASENAME` files all over the tree. 42 | This is an implementation detail. 43 | 44 | * Builds can be started from every directory and should yield same results. 45 | 46 | * `redo -f` will consider all targets outdated and force a rebuild. 47 | 48 | * `redo -k` will keep going if a target failed to build. 49 | 50 | ## Copying 51 | 52 | To the extent possible under law, Leah Neukirchen 53 | has waived all copyright and related or neighboring rights to this work. 54 | 55 | http://creativecommons.org/publicdomain/zero/1.0/ 56 | -------------------------------------------------------------------------------- /redo.c: -------------------------------------------------------------------------------- 1 | /* An implementation of the redo build system 2 | in portable C with zero dependencies 3 | 4 | http://cr.yp.to/redo.html 5 | https://jdebp.eu./FGA/introduction-to-redo.html 6 | https://github.com/apenwarr/redo/blob/master/README.md 7 | http://news.dieweltistgarnichtso.net/bin/redo-sh.html 8 | 9 | To the extent possible under law, 10 | Leah Neukirchen 11 | has waived all copyright and related or neighboring rights to this work. 12 | 13 | http://creativecommons.org/publicdomain/zero/1.0/ 14 | */ 15 | 16 | /* 17 | ##% cc -g -Os -Wall -Wextra -Wwrite-strings -o $STEM $FILE 18 | */ 19 | 20 | /* 21 | current bugs: 22 | dependency-loop: unlimited recursion 23 | need locks 24 | 25 | todo: 26 | test job server properly 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | // --------------------------------------------------------------------------- 45 | // from musl/src/crypt/crypt_sha256.c 46 | 47 | /* public domain sha256 implementation based on fips180-3 */ 48 | 49 | struct sha256 { 50 | uint64_t len; /* processed message length */ 51 | uint32_t h[8]; /* hash state */ 52 | uint8_t buf[64]; /* message block buffer */ 53 | }; 54 | 55 | static uint32_t ror(uint32_t n, int k) { return (n >> k) | (n << (32-k)); } 56 | #define Ch(x,y,z) (z ^ (x & (y ^ z))) 57 | #define Maj(x,y,z) ((x & y) | (z & (x | y))) 58 | #define S0(x) (ror(x,2) ^ ror(x,13) ^ ror(x,22)) 59 | #define S1(x) (ror(x,6) ^ ror(x,11) ^ ror(x,25)) 60 | #define R0(x) (ror(x,7) ^ ror(x,18) ^ (x>>3)) 61 | #define R1(x) (ror(x,17) ^ ror(x,19) ^ (x>>10)) 62 | 63 | static const uint32_t K[64] = { 64 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 65 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 66 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 67 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 68 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 69 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 70 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 71 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 72 | }; 73 | 74 | static void processblock(struct sha256 *s, const uint8_t *buf) 75 | { 76 | uint32_t W[64], t1, t2, a, b, c, d, e, f, g, h; 77 | int i; 78 | 79 | for (i = 0; i < 16; i++) { 80 | W[i] = (uint32_t)buf[4*i]<<24; 81 | W[i] |= (uint32_t)buf[4*i+1]<<16; 82 | W[i] |= (uint32_t)buf[4*i+2]<<8; 83 | W[i] |= buf[4*i+3]; 84 | } 85 | for (; i < 64; i++) 86 | W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; 87 | a = s->h[0]; 88 | b = s->h[1]; 89 | c = s->h[2]; 90 | d = s->h[3]; 91 | e = s->h[4]; 92 | f = s->h[5]; 93 | g = s->h[6]; 94 | h = s->h[7]; 95 | for (i = 0; i < 64; i++) { 96 | t1 = h + S1(e) + Ch(e, f, g) + K[i] + W[i]; 97 | t2 = S0(a) + Maj(a, b, c); 98 | h = g; 99 | g = f; 100 | f = e; 101 | e = d + t1; 102 | d = c; 103 | c = b; 104 | b = a; 105 | a = t1 + t2; 106 | } 107 | s->h[0] += a; 108 | s->h[1] += b; 109 | s->h[2] += c; 110 | s->h[3] += d; 111 | s->h[4] += e; 112 | s->h[5] += f; 113 | s->h[6] += g; 114 | s->h[7] += h; 115 | } 116 | 117 | static void pad(struct sha256 *s) 118 | { 119 | unsigned r = s->len % 64; 120 | 121 | s->buf[r++] = 0x80; 122 | if (r > 56) { 123 | memset(s->buf + r, 0, 64 - r); 124 | r = 0; 125 | processblock(s, s->buf); 126 | } 127 | memset(s->buf + r, 0, 56 - r); 128 | s->len *= 8; 129 | s->buf[56] = s->len >> 56; 130 | s->buf[57] = s->len >> 48; 131 | s->buf[58] = s->len >> 40; 132 | s->buf[59] = s->len >> 32; 133 | s->buf[60] = s->len >> 24; 134 | s->buf[61] = s->len >> 16; 135 | s->buf[62] = s->len >> 8; 136 | s->buf[63] = s->len; 137 | processblock(s, s->buf); 138 | } 139 | 140 | static void sha256_init(struct sha256 *s) 141 | { 142 | s->len = 0; 143 | s->h[0] = 0x6a09e667; 144 | s->h[1] = 0xbb67ae85; 145 | s->h[2] = 0x3c6ef372; 146 | s->h[3] = 0xa54ff53a; 147 | s->h[4] = 0x510e527f; 148 | s->h[5] = 0x9b05688c; 149 | s->h[6] = 0x1f83d9ab; 150 | s->h[7] = 0x5be0cd19; 151 | } 152 | 153 | static void sha256_sum(struct sha256 *s, uint8_t *md) 154 | { 155 | int i; 156 | 157 | pad(s); 158 | for (i = 0; i < 8; i++) { 159 | md[4*i] = s->h[i] >> 24; 160 | md[4*i+1] = s->h[i] >> 16; 161 | md[4*i+2] = s->h[i] >> 8; 162 | md[4*i+3] = s->h[i]; 163 | } 164 | } 165 | 166 | static void sha256_update(struct sha256 *s, const void *m, unsigned long len) 167 | { 168 | const uint8_t *p = m; 169 | unsigned r = s->len % 64; 170 | 171 | s->len += len; 172 | if (r) { 173 | if (len < 64 - r) { 174 | memcpy(s->buf + r, p, len); 175 | return; 176 | } 177 | memcpy(s->buf + r, p, 64 - r); 178 | len -= 64 - r; 179 | p += 64 - r; 180 | processblock(s, s->buf); 181 | } 182 | for (; len >= 64; len -= 64, p += 64) 183 | processblock(s, p); 184 | memcpy(s->buf, p, len); 185 | } 186 | 187 | // ---------------------------------------------------------------------- 188 | 189 | int dir_fd = -1; 190 | int dep_fd = -1; 191 | int poolwr_fd = -1; 192 | int poolrd_fd = -1; 193 | int level = -1; 194 | int implicit_jobs = 1; 195 | int kflag, jflag, xflag, fflag, sflag; 196 | 197 | static void 198 | redo_ifcreate(int fd, char *target) 199 | { 200 | dprintf(fd, "-%s\n", target); 201 | } 202 | 203 | static char * 204 | check_dofile(const char *fmt, ...) 205 | { 206 | static char dofile[PATH_MAX]; 207 | 208 | va_list ap; 209 | va_start(ap, fmt); 210 | vsnprintf(dofile, sizeof dofile, fmt, ap); 211 | va_end(ap); 212 | 213 | if (access(dofile, F_OK) == 0) { 214 | return dofile; 215 | } else { 216 | redo_ifcreate(dep_fd, dofile); 217 | return 0; 218 | } 219 | } 220 | 221 | /* 222 | dir/base.a.b 223 | will look for dir/base.a.b.do, 224 | dir/default.a.b.do, dir/default.b.do, dir/default.do, 225 | default.a.b.do, default.b.do, and default.do. 226 | 227 | this function assumes no / in target 228 | */ 229 | static char * 230 | find_dofile(char *target) 231 | { 232 | char updir[PATH_MAX]; 233 | char *u = updir; 234 | char *dofile, *s; 235 | struct stat st, ost; 236 | 237 | dofile = check_dofile("./%s.do", target); 238 | if (dofile) 239 | return dofile; 240 | 241 | *u++ = '.'; 242 | *u++ = '/'; 243 | *u = 0; 244 | 245 | st.st_dev = ost.st_dev = st.st_ino = ost.st_ino = 0; 246 | 247 | while (1) { 248 | ost = st; 249 | 250 | if (stat(updir, &st) < 0) 251 | return 0; 252 | if (ost.st_dev == st.st_dev && ost.st_ino == st.st_ino) 253 | break; // reached root dir, .. = . 254 | 255 | s = target; 256 | while (*s) { 257 | if (*s++ == '.') { 258 | dofile = check_dofile("%sdefault.%s.do", updir, s); 259 | if (dofile) 260 | return dofile; 261 | } 262 | } 263 | 264 | dofile = check_dofile("%sdefault.do", updir); 265 | if (dofile) 266 | return dofile; 267 | 268 | *u++ = '.'; 269 | *u++ = '.'; 270 | *u++ = '/'; 271 | *u = 0; 272 | } 273 | 274 | return 0; 275 | } 276 | 277 | static int 278 | envfd(const char *name) 279 | { 280 | long fd; 281 | 282 | char *s = getenv(name); 283 | if (!s) 284 | return -1; 285 | 286 | fd = strtol(s, 0, 10); 287 | if (fd < 0 || fd > 255) 288 | fd = -1; 289 | 290 | return fd; 291 | } 292 | 293 | static void 294 | setenvfd(const char *name, int i) 295 | { 296 | char buf[16]; 297 | snprintf(buf, sizeof buf, "%d", i); 298 | setenv(name, buf, 1); 299 | } 300 | 301 | static char * 302 | hashfile(int fd) 303 | { 304 | static char hex[16] = "0123456789abcdef"; 305 | static char asciihash[65]; 306 | 307 | struct sha256 ctx; 308 | off_t off = 0; 309 | char buf[4096]; 310 | char *a; 311 | unsigned char hash[32]; 312 | int i; 313 | ssize_t r; 314 | 315 | sha256_init(&ctx); 316 | 317 | while ((r = pread(fd, buf, sizeof buf, off)) > 0) { 318 | sha256_update(&ctx, buf, r); 319 | off += r; 320 | } 321 | 322 | sha256_sum(&ctx, hash); 323 | 324 | for (i = 0, a = asciihash; i < 32; i++) { 325 | *a++ = hex[hash[i] / 16]; 326 | *a++ = hex[hash[i] % 16]; 327 | } 328 | *a = 0; 329 | 330 | return asciihash; 331 | } 332 | 333 | static char * 334 | datefile(int fd) 335 | { 336 | static char hexdate[17]; 337 | struct stat st; 338 | 339 | fstat(fd, &st); 340 | snprintf(hexdate, sizeof hexdate, "%016" PRIx64, (uint64_t)st.st_ctime); 341 | 342 | return hexdate; 343 | } 344 | 345 | static int 346 | keepdir() 347 | { 348 | int fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 349 | if (fd < 0) { 350 | perror("dir open"); 351 | exit(-1); 352 | } 353 | return fd; 354 | } 355 | 356 | static char * 357 | targetchdir(char *target) 358 | { 359 | char *base = strrchr(target, '/'); 360 | if (base) { 361 | int fd; 362 | *base = 0; 363 | fd = openat(dir_fd, target, O_RDONLY | O_DIRECTORY); 364 | if (fd < 0) { 365 | perror("openat dir"); 366 | exit(111); 367 | } 368 | *base = '/'; 369 | if (fchdir(fd) < 0) { 370 | perror("chdir"); 371 | exit(111); 372 | } 373 | close(fd); 374 | return base+1; 375 | } else { 376 | fchdir(dir_fd); 377 | return target; 378 | } 379 | } 380 | 381 | static char * 382 | targetdep(char *target) 383 | { 384 | static char buf[PATH_MAX]; 385 | snprintf(buf, sizeof buf, ".dep.%s", target); 386 | return buf; 387 | } 388 | 389 | static char * 390 | targetlock(char *target) 391 | { 392 | static char buf[PATH_MAX]; 393 | snprintf(buf, sizeof buf, ".lock.%s", target); 394 | return buf; 395 | } 396 | 397 | static int 398 | sourcefile(char *target) 399 | { 400 | if (access(targetdep(target), F_OK) == 0) 401 | return 0; 402 | 403 | if (fflag < 0) 404 | return access(target, F_OK) == 0; 405 | 406 | return find_dofile(target) == 0; 407 | } 408 | 409 | static int 410 | check_deps(char *target) 411 | { 412 | char *depfile; 413 | FILE *f; 414 | int ok = 1; 415 | int fd; 416 | int old_dir_fd = dir_fd; 417 | 418 | target = targetchdir(target); 419 | 420 | if (sourcefile(target)) 421 | return 1; 422 | 423 | if (fflag > 0) 424 | return 0; 425 | 426 | depfile = targetdep(target); 427 | f = fopen(depfile, "r"); 428 | if (!f) 429 | return 0; 430 | 431 | dir_fd = keepdir(); 432 | 433 | while (ok && !feof(f)) { 434 | char line[4096]; 435 | char *hash = line + 1; 436 | char *timestamp = line + 1 + 64 + 1; 437 | char *filename = line + 1 + 64 + 1 + 16 + 1; 438 | 439 | if (fgets(line, sizeof line, f)) { 440 | line[strlen(line)-1] = 0; // strip \n 441 | switch (line[0]) { 442 | case '-': // must not exist 443 | if (access(line+1, F_OK) == 0) 444 | ok = 0; 445 | break; 446 | case '=': // compare hash 447 | fd = open(filename, O_RDONLY); 448 | if (fd < 0) { 449 | ok = 0; 450 | } else { 451 | if (strncmp(timestamp, datefile(fd), 16) != 0 && 452 | strncmp(hash, hashfile(fd), 64) != 0) 453 | ok = 0; 454 | close(fd); 455 | } 456 | // hash is good, recurse into dependencies 457 | if (ok && strcmp(target, filename) != 0) { 458 | ok = check_deps(filename); 459 | fchdir(dir_fd); 460 | } 461 | break; 462 | case '!': // always rebuild 463 | default: // dep file broken, lets recreate it 464 | ok = 0; 465 | } 466 | } else { 467 | if (!feof(f)) { 468 | ok = 0; 469 | break; 470 | } 471 | } 472 | } 473 | 474 | fclose(f); 475 | 476 | close(dir_fd); 477 | dir_fd = old_dir_fd; 478 | 479 | return ok; 480 | } 481 | 482 | void 483 | vacate(int implicit) 484 | { 485 | if (implicit) 486 | implicit_jobs++; 487 | else 488 | write(poolwr_fd, "\0", 1); 489 | } 490 | 491 | struct job { 492 | struct job *next; 493 | pid_t pid; 494 | int lock_fd; 495 | char *target, *temp_depfile, *temp_target; 496 | int implicit; 497 | }; 498 | struct job *jobhead; 499 | 500 | static void 501 | insert_job(struct job *job) 502 | { 503 | job->next = jobhead; 504 | jobhead = job; 505 | } 506 | 507 | static void 508 | remove_job(struct job *job) 509 | { 510 | if (jobhead == job) 511 | jobhead = jobhead->next; 512 | else { 513 | struct job *j = jobhead; 514 | while (j->next != job) 515 | j = j->next; 516 | j->next = j->next->next; 517 | } 518 | } 519 | 520 | static struct job * 521 | find_job(pid_t pid) 522 | { 523 | struct job *j; 524 | 525 | for (j = jobhead; j; j = j->next) { 526 | if (j->pid == pid) 527 | return j; 528 | } 529 | 530 | return 0; 531 | } 532 | 533 | char uprel[PATH_MAX]; 534 | 535 | void 536 | compute_uprel() 537 | { 538 | char *u = uprel; 539 | char *dp = getenv("REDO_DIRPREFIX"); 540 | 541 | *u = 0; 542 | while (dp && *dp) { 543 | *u++ = '.'; 544 | *u++ = '.'; 545 | *u++ = '/'; 546 | *u = 0; 547 | dp = strchr(dp + 1, '/'); 548 | } 549 | } 550 | 551 | static int 552 | write_dep(int dep_fd, char *file) 553 | { 554 | int fd = open(file, O_RDONLY); 555 | if (fd < 0) 556 | return 0; 557 | dprintf(dep_fd, "=%s %s %s%s\n", 558 | hashfile(fd), datefile(fd), (*file == '/' ? "" : uprel), file); 559 | close(fd); 560 | return 0; 561 | } 562 | 563 | int 564 | new_waitjob(int lock_fd, int implicit) 565 | { 566 | pid_t pid; 567 | 568 | pid = fork(); 569 | if (pid < 0) { 570 | perror("fork"); 571 | vacate(implicit); 572 | exit(-1); 573 | } else if (pid == 0) { // child 574 | lockf(lock_fd, F_LOCK, 0); 575 | close(lock_fd); 576 | exit(0); 577 | } else { 578 | struct job *job = malloc(sizeof *job); 579 | if (!job) 580 | exit(-1); 581 | job->target = 0; 582 | job->pid = pid; 583 | job->lock_fd = lock_fd; 584 | job->implicit = implicit; 585 | 586 | insert_job(job); 587 | } 588 | 589 | return 0; 590 | } 591 | 592 | // dofile doesn't contain / 593 | // target can contain / 594 | static char * 595 | redo_basename(char *dofile, char *target) 596 | { 597 | static char buf[PATH_MAX]; 598 | int stripext = 0; 599 | char *s; 600 | 601 | if (strncmp(dofile, "default.", 8) == 0) 602 | for (stripext = -1, s = dofile; *s; s++) 603 | if (*s == '.') 604 | stripext++; 605 | 606 | strncpy(buf, target, sizeof buf); 607 | while (stripext-- > 0) { 608 | if (strchr(buf, '.')) { 609 | char *e = strchr(buf, '\0'); 610 | while (*--e != '.') 611 | *e = 0; 612 | *e = 0; 613 | } 614 | } 615 | 616 | return buf; 617 | } 618 | 619 | static void 620 | run_script(char *target, int implicit) 621 | { 622 | char temp_depfile[] = ".depend.XXXXXX"; 623 | char temp_target_base[] = ".target.XXXXXX"; 624 | char temp_target[PATH_MAX], rel_target[PATH_MAX], cwd[PATH_MAX]; 625 | char *orig_target = target; 626 | int old_dep_fd = dep_fd; 627 | int target_fd; 628 | char *dofile, *dirprefix; 629 | pid_t pid; 630 | 631 | target = targetchdir(target); 632 | 633 | dofile = find_dofile(target); 634 | if (!dofile) { 635 | fprintf(stderr, "no dofile for %s.\n", target); 636 | exit(1); 637 | } 638 | 639 | int lock_fd = open(targetlock(target), 640 | O_WRONLY | O_TRUNC | O_CREAT, 0666); 641 | if (lockf(lock_fd, F_TLOCK, 0) < 0) { 642 | if (errno == EAGAIN) { 643 | fprintf(stderr, "redo: %s already building, waiting.\n", 644 | orig_target); 645 | new_waitjob(lock_fd, implicit); 646 | return; 647 | } else { 648 | perror("lockf"); 649 | exit(111); 650 | } 651 | } 652 | 653 | dep_fd = mkstemp(temp_depfile); 654 | 655 | target_fd = mkstemp(temp_target_base); 656 | 657 | fprintf(stderr, "redo%*.*s %s # %s\n", level*2, level*2, " ", orig_target, dofile); 658 | write_dep(dep_fd, dofile); 659 | 660 | // .do files are called from the directory they reside in, we need to 661 | // prefix the arguments with the path from the dofile to the target 662 | getcwd(cwd, sizeof cwd); 663 | dirprefix = strchr(cwd, '\0'); 664 | dofile += 2; // find_dofile starts with ./ always 665 | while (strncmp(dofile, "../", 3) == 0) { 666 | chdir(".."); 667 | dofile += 3; 668 | while (*--dirprefix != '/') 669 | ; 670 | } 671 | if (*dirprefix) 672 | dirprefix++; 673 | 674 | snprintf(temp_target, sizeof temp_target, 675 | "%s%s%s", dirprefix, (*dirprefix ? "/" : ""), temp_target_base); 676 | snprintf(rel_target, sizeof rel_target, 677 | "%s%s%s", dirprefix, (*dirprefix ? "/" : ""), target); 678 | 679 | setenv("REDO_DIRPREFIX", dirprefix, 1); 680 | 681 | pid = fork(); 682 | if (pid < 0) { 683 | perror("fork"); 684 | vacate(implicit); 685 | exit(-1); 686 | } else if (pid == 0) { // child 687 | /* 688 | djb-style default.o.do: 689 | $1 foo.o 690 | $2 foo 691 | $3 whatever.tmp 692 | 693 | $1 all 694 | $2 all (!!) 695 | $3 whatever.tmp 696 | 697 | $1 subdir/foo.o 698 | $2 subdir/foo 699 | $3 subdir/whatever.tmp 700 | */ 701 | char *basename = redo_basename(dofile, rel_target); 702 | 703 | if (old_dep_fd > 0) 704 | close(old_dep_fd); 705 | close(lock_fd); 706 | setenvfd("REDO_DEP_FD", dep_fd); 707 | setenvfd("REDO_LEVEL", level + 1); 708 | if (sflag > 0) 709 | dup2(target_fd, 1); 710 | else 711 | close(target_fd); 712 | 713 | if (access(dofile, X_OK) != 0) // run -x files with /bin/sh 714 | execl("/bin/sh", "/bin/sh", xflag > 0 ? "-ex" : "-e", 715 | dofile, rel_target, basename, temp_target, (char *)0); 716 | else 717 | execl(dofile, 718 | dofile, rel_target, basename, temp_target, (char *)0); 719 | vacate(implicit); 720 | exit(-1); 721 | } else { 722 | struct job *job = malloc(sizeof *job); 723 | if (!job) 724 | exit(-1); 725 | 726 | close(target_fd); 727 | close(dep_fd); 728 | dep_fd = old_dep_fd; 729 | 730 | job->pid = pid; 731 | job->lock_fd = lock_fd; 732 | job->target = orig_target; 733 | job->temp_depfile = strdup(temp_depfile); 734 | job->temp_target = strdup(temp_target_base); 735 | job->implicit = implicit; 736 | 737 | insert_job(job); 738 | } 739 | } 740 | 741 | static int 742 | try_procure() 743 | { 744 | if (implicit_jobs > 0) { 745 | implicit_jobs--; 746 | return 1; 747 | } else { 748 | if (poolrd_fd < 0) 749 | return 0; 750 | 751 | fcntl(poolrd_fd, F_SETFL, O_NONBLOCK); 752 | 753 | char buf[1]; 754 | return read(poolrd_fd, &buf, 1) > 0; 755 | } 756 | } 757 | 758 | static int 759 | procure() 760 | { 761 | if (implicit_jobs > 0) { 762 | implicit_jobs--; 763 | return 1; 764 | } else { 765 | fcntl(poolrd_fd, F_SETFL, 0); // clear O_NONBLOCK 766 | 767 | char buf[1]; 768 | return read(poolrd_fd, &buf, 1) > 0; 769 | } 770 | } 771 | 772 | void 773 | create_pool() 774 | { 775 | poolrd_fd = envfd("REDO_RD_FD"); 776 | poolwr_fd = envfd("REDO_WR_FD"); 777 | if (poolrd_fd < 0 || poolwr_fd < 0) { 778 | int jobs = envfd("JOBS"); 779 | if (jobs > 1) { 780 | int i, fds[2]; 781 | pipe(fds); 782 | poolrd_fd = fds[0]; 783 | poolwr_fd = fds[1]; 784 | 785 | for (i = 0; i < jobs-1; i++) 786 | vacate(0); 787 | 788 | setenvfd("REDO_RD_FD", poolrd_fd); 789 | setenvfd("REDO_WR_FD", poolwr_fd); 790 | } else { 791 | poolrd_fd = -1; 792 | poolwr_fd = -1; 793 | } 794 | } 795 | } 796 | 797 | static void 798 | redo_ifchange(int targetc, char *targetv[]) 799 | { 800 | pid_t pid; 801 | int status; 802 | struct job *job; 803 | 804 | int targeti = 0; 805 | 806 | // XXX 807 | char skip[targetc]; 808 | 809 | create_pool(); 810 | 811 | // check all targets whether needing rebuild 812 | for (targeti = 0; targeti < targetc; targeti++) 813 | skip[targeti] = check_deps(targetv[targeti]); 814 | 815 | targeti = 0; 816 | while (1) { 817 | int procured = 0; 818 | if (targeti < targetc) { 819 | char *target = targetv[targeti]; 820 | 821 | if (skip[targeti]) { 822 | targeti++; 823 | continue; 824 | } 825 | 826 | int implicit = implicit_jobs > 0; 827 | if (try_procure()) { 828 | procured = 1; 829 | targeti++; 830 | run_script(target, implicit); 831 | } 832 | } 833 | 834 | pid = waitpid(-1, &status, procured ? WNOHANG : 0); 835 | 836 | if (pid == 0) 837 | continue; // nohang 838 | 839 | if (pid < 0) { 840 | if (errno == ECHILD && targeti < targetc) 841 | continue; // no child yet??? 842 | else 843 | break; // no child left 844 | } 845 | 846 | if (WIFEXITED(status)) 847 | status = WEXITSTATUS(status); 848 | 849 | job = find_job(pid); 850 | 851 | if (!job) { 852 | exit(-1); 853 | } 854 | remove_job(job); 855 | 856 | if (job->target) { 857 | if (status > 0) { 858 | remove(job->temp_depfile); 859 | remove(job->temp_target); 860 | } else { 861 | struct stat st; 862 | char *target = targetchdir(job->target); 863 | char *depfile = targetdep(target); 864 | int dfd; 865 | 866 | dfd = open(job->temp_depfile, 867 | O_WRONLY | O_APPEND); 868 | if (stat(job->temp_target, &st) == 0) { 869 | rename(job->temp_target, target); 870 | write_dep(dfd, target); 871 | } else { 872 | remove(job->temp_target); 873 | redo_ifcreate(dfd, target); 874 | } 875 | close(dfd); 876 | 877 | rename(job->temp_depfile, depfile); 878 | remove(targetlock(target)); 879 | } 880 | } 881 | 882 | close(job->lock_fd); 883 | 884 | vacate(job->implicit); 885 | 886 | if (kflag < 0 && status > 0) { 887 | printf("failed with status %d\n", status); 888 | exit(status); 889 | } 890 | } 891 | } 892 | 893 | static void 894 | record_deps(int targetc, char *targetv[]) 895 | { 896 | int targeti = 0; 897 | int fd; 898 | 899 | dep_fd = envfd("REDO_DEP_FD"); 900 | if (dep_fd < 0) 901 | return; 902 | 903 | fchdir(dir_fd); 904 | 905 | for (targeti = 0; targeti < targetc; targeti++) 906 | write_dep(dep_fd, targetv[targeti]); 907 | } 908 | 909 | int 910 | main(int argc, char *argv[]) 911 | { 912 | char *program; 913 | int opt, i; 914 | 915 | dep_fd = envfd("REDO_DEP_FD"); 916 | 917 | level = envfd("REDO_LEVEL"); 918 | if (level < 0) 919 | level = 0; 920 | 921 | if ((program = strrchr(argv[0], '/'))) 922 | program++; 923 | else 924 | program = argv[0]; 925 | 926 | while ((opt = getopt(argc, argv, "+kxfsj:C:")) != -1) { 927 | switch (opt) { 928 | case 'k': 929 | setenvfd("REDO_KEEP_GOING", 1); 930 | break; 931 | case 'x': 932 | setenvfd("REDO_TRACE", 1); 933 | break; 934 | case 'f': 935 | setenvfd("REDO_FORCE", 1); 936 | break; 937 | case 's': 938 | setenvfd("REDO_STDOUT", 1); 939 | break; 940 | case 'j': 941 | setenv("JOBS", optarg, 1); 942 | break; 943 | case 'C': 944 | if (chdir(optarg) < 0) { 945 | perror("chdir"); 946 | exit(-1); 947 | } 948 | break; 949 | default: 950 | fprintf(stderr, "usage: %s [-kfsx] [-jN] [-Cdir] [TARGETS...]\n", program); 951 | exit(1); 952 | } 953 | } 954 | argc -= optind; 955 | argv += optind; 956 | 957 | fflag = envfd("REDO_FORCE"); 958 | kflag = envfd("REDO_KEEP_GOING"); 959 | xflag = envfd("REDO_TRACE"); 960 | sflag = envfd("REDO_STDOUT"); 961 | 962 | dir_fd = keepdir(); 963 | 964 | if (strcmp(program, "redo") == 0) { 965 | char all[] = "all"; 966 | char *argv_def[] = { all }; 967 | 968 | if (argc == 0) { 969 | argc = 1; 970 | argv = argv_def; 971 | } 972 | 973 | fflag = 1; 974 | redo_ifchange(argc, argv); 975 | procure(); 976 | } else if (strcmp(program, "redo-ifchange") == 0) { 977 | compute_uprel(); 978 | redo_ifchange(argc, argv); 979 | record_deps(argc, argv); 980 | procure(); 981 | } else if (strcmp(program, "redo-ifcreate") == 0) { 982 | for (i = 0; i < argc; i++) 983 | redo_ifcreate(dep_fd, argv[i]); 984 | } else if (strcmp(program, "redo-always") == 0) { 985 | dprintf(dep_fd, "!\n"); 986 | } else if (strcmp(program, "redo-hash") == 0) { 987 | for (i = 0; i < argc; i++) 988 | write_dep(1, argv[i]); 989 | } else { 990 | fprintf(stderr, "not implemented %s\n", program); 991 | exit(-1); 992 | } 993 | 994 | return 0; 995 | } 996 | --------------------------------------------------------------------------------