diff --git a/.gitignore b/.gitignore index d5091fb..1568433 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ *.o .*.sw* /samu +out/ +.vs/ +.vscode/ +.cache/ +build/ +CMakeUserPresets.json +CMakePresets.json +CMakeSettings.json +*.user \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dff7dc5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.16) +project(samurai C) + +set(SOURCE + build.c + deps.c + env.c + graph.c + htab.c + log.c + parse.c + samu.c + scan.c + tool.c + tree.c + util.c +) + +if (WIN32) + list(APPEND SOURCE os-win32.c samu.manifest) +elseif(UNIX) + list(APPEND SOURCE os-posix.c) +endif() + +add_executable(samu ${SOURCE}) + +if (UNIX) + target_link_libraries(samu PRIVATE rt) +endif() diff --git a/arg.h b/arg.h index e1d0903..b1cf783 100644 --- a/arg.h +++ b/arg.h @@ -14,7 +14,7 @@ extern const char *argv0; switch (*opt_) #define ARGEND \ - } \ + } \ } #define EARGF(x) \ diff --git a/build.c b/build.c index 53dbc60..e4ed233 100644 --- a/build.c +++ b/build.c @@ -1,16 +1,11 @@ -#define _POSIX_C_SOURCE 200809L #include #include #include -#include #include -#include #include #include #include -#include -#include -#include +#include #include "build.h" #include "deps.h" #include "env.h" @@ -24,8 +19,6 @@ struct job { struct edge *edge; struct buffer buf; size_t next; - pid_t pid; - int fd; bool failed; }; @@ -33,7 +26,7 @@ struct buildoptions buildopts = {.maxfail = 1}; static struct edge *work; static size_t nstarted, nfinished, ntotal; static bool consoleused; -static struct timespec starttime; +static struct ostimespec starttime; void buildreset(void) @@ -122,7 +115,6 @@ buildadd(struct node *n) struct edge *e; struct node *newest; size_t i; - bool generator, restat; e = n->gen; if (!e) { @@ -160,15 +152,19 @@ buildadd(struct node *n) ++e->nblock; } /* all outputs are dirty if any are older than the newest input */ - generator = edgevar(e, "generator", true); - restat = edgevar(e, "restat", true); + struct string *generator = edgevar(e, "generator", true); + struct string *restat = edgevar(e, "restat", true); for (i = 0; i < e->nout && !(e->flags & FLAG_DIRTY_OUT); ++i) { n = e->out[i]; - if (isdirty(n, newest, generator, restat)) { + if (isdirty(n, newest, (bool)generator, (bool)restat)) { n->dirty = true; e->flags |= FLAG_DIRTY_OUT; } } + if (generator) + free(generator); + if (restat) + free(restat); if (e->flags & FLAG_DIRTY) { for (i = 0; i < e->nout; ++i) { n = e->out[i]; @@ -198,7 +194,7 @@ formatstatus(char *buf, size_t len) const char *fmt; size_t ret = 0; int n; - struct timespec endtime; + struct ostimespec endtime; for (fmt = buildopts.statusfmt; *fmt; ++fmt) { if (*fmt != '%' || *++fmt == '%') { @@ -230,14 +226,14 @@ formatstatus(char *buf, size_t len) n = snprintf(buf, len, "%3zu%%", 100 * nfinished / ntotal); break; case 'o': - if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { + if (osclock_gettime_monotonic(&endtime) != 0) { warn("clock_gettime:"); break; } n = snprintf(buf, len, "%.1f", nfinished / ((endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec))); break; case 'e': - if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { + if (osclock_gettime_monotonic(&endtime) != 0) { warn("clock_gettime:"); break; } @@ -245,7 +241,7 @@ formatstatus(char *buf, size_t len) break; default: fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *fmt); - continue; /* unreachable, but avoids warning */ + continue; /* unreachable, but avoids warning */ } if (n < 0) fatal("snprintf:"); @@ -272,18 +268,15 @@ printstatus(struct edge *e, struct string *cmd) formatstatus(status, sizeof(status)); fputs(status, stdout); puts(description->s); + free(description); } static int -jobstart(struct job *j, struct edge *e) +jobstart(struct osjob_ctx *osctx, struct osjob *oj, struct job *j, struct edge *e) { - extern char **environ; size_t i; struct node *n; struct string *rspfile, *content; - int fd[2]; - posix_spawn_file_actions_t actions; - char *argv[] = {"/bin/sh", "-c", NULL, NULL}; ++nstarted; for (i = 0; i < e->nout; ++i) { @@ -299,62 +292,24 @@ jobstart(struct job *j, struct edge *e) if (writefile(rspfile->s, content) < 0) goto err0; } - - if (pipe(fd) < 0) { - warn("pipe:"); - goto err1; - } j->edge = e; + if (j->cmd) + free(j->cmd); j->cmd = edgevar(e, "command", true); - j->fd = fd[0]; - argv[2] = j->cmd->s; if (!consoleused) printstatus(e, j->cmd); - if ((errno = posix_spawn_file_actions_init(&actions))) { - warn("posix_spawn_file_actions_init:"); - goto err2; - } - if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) { - warn("posix_spawn_file_actions_addclose:"); - goto err3; - } - if (e->pool != &consolepool) { - if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) { - warn("posix_spawn_file_actions_addopen:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) { - warn("posix_spawn_file_actions_adddup2:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) { - warn("posix_spawn_file_actions_adddup2:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) { - warn("posix_spawn_file_actions_addclose:"); - goto err3; - } - } - if ((errno = posix_spawn(&j->pid, argv[0], &actions, NULL, argv, environ))) { - warn("posix_spawn %s:", j->cmd->s); - goto err3; + bool use_console = e->pool == &consolepool; + if (osjob_create(osctx, oj, j->cmd, use_console) < 0) { + goto err1; } - posix_spawn_file_actions_destroy(&actions); - close(fd[1]); + j->failed = false; if (e->pool == &consolepool) consoleused = true; - - return j->fd; - -err3: - posix_spawn_file_actions_destroy(&actions); -err2: - close(fd[0]); - close(fd[1]); + oj->valid = true; + return 0; err1: if (rspfile && !buildopts.keeprsp) remove(rspfile->s); @@ -414,17 +369,18 @@ edgedone(struct edge *e) struct node *n; size_t i; struct string *rspfile; - bool restat; int64_t old; - restat = edgevar(e, "restat", true); + struct string *restat = edgevar(e, "restat", true); for (i = 0; i < e->nout; ++i) { n = e->out[i]; old = n->mtime; nodestat(n); n->logmtime = n->mtime == MTIME_MISSING ? 0 : n->mtime; - nodedone(n, restat && shouldprune(e, n, old)); + nodedone(n, (bool)restat && shouldprune(e, n, old)); } + if (restat) + free(restat); rspfile = edgevar(e, "rspfile", false); if (rspfile && !buildopts.keeprsp) remove(rspfile->s); @@ -438,30 +394,15 @@ edgedone(struct edge *e) } static void -jobdone(struct job *j) +jobdone(struct osjob_ctx *osctx, struct job *j, struct osjob *oj) { - int status; struct edge *e, *new; struct pool *p; ++nfinished; - if (waitpid(j->pid, &status, 0) < 0) { - warn("waitpid %d:", j->pid); - j->failed = true; - } else if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - warn("job failed with status %d: %s", WEXITSTATUS(status), j->cmd->s); - j->failed = true; - } - } else if (WIFSIGNALED(status)) { - warn("job terminated due to signal %d: %s", WTERMSIG(status), j->cmd->s); - j->failed = true; - } else { - /* cannot happen according to POSIX */ - warn("job status unknown: %s", j->cmd->s); + if (osjob_done(osctx, oj, j->cmd) < 0) { j->failed = true; } - close(j->fd); if (j->buf.len && (!consoleused || j->failed)) fwrite(j->buf.data, 1, j->buf.len, stdout); j->buf.len = 0; @@ -487,7 +428,7 @@ jobdone(struct job *j) /* returns whether a job still has work to do. if not, sets j->failed */ static bool -jobwork(struct job *j) +jobwork(struct osjob_ctx *osctx, struct job *j, struct osjob *ojob) { char *newdata; size_t newcap; @@ -503,20 +444,20 @@ jobwork(struct job *j) j->buf.cap = newcap; j->buf.data = newdata; } - n = read(j->fd, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); - if (n > 0) { - j->buf.len += n; + ssize_t result = osjob_work(osctx, ojob, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); + if (result > 0) { + j->buf.len += result; return true; - } - if (n == 0) + } else if (result == 0) { goto done; - warn("read:"); - -kill: - kill(j->pid, SIGTERM); - j->failed = true; + } else { + warn("read:"); + kill: + osjob_close(osctx, ojob); + j->failed = true; + } done: - jobdone(j); + jobdone(osctx, j, ojob); return false; } @@ -543,16 +484,18 @@ void build(void) { struct job *jobs = NULL; - struct pollfd *fds = NULL; + struct osjob *osjobs = NULL; + struct osjob_ctx *osctx = osjob_ctx_create(); size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0; struct edge *e; if (ntotal == 0) { warn("nothing to do"); + osjob_ctx_close(osctx); return; } - clock_gettime(CLOCK_MONOTONIC, &starttime); + osclock_gettime_monotonic(&starttime); formatstatus(NULL, 0); nstarted = 0; @@ -579,18 +522,14 @@ build(void) if (jobslen > buildopts.maxjobs) jobslen = buildopts.maxjobs; jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0])); - fds = xreallocarray(fds, jobslen, sizeof(fds[0])); + osjobs = xreallocarray(osjobs, jobslen, sizeof(osjobs[0])); for (i = next; i < jobslen; ++i) { - jobs[i].buf.data = NULL; - jobs[i].buf.len = 0; - jobs[i].buf.cap = 0; + jobs[i] = (struct job){0}; jobs[i].next = i + 1; - fds[i].fd = -1; - fds[i].events = POLLIN; + osjobs[i] = (struct osjob){0}; } } - fds[next].fd = jobstart(&jobs[next], e); - if (fds[next].fd < 0) { + if (jobstart(osctx, &osjobs[next], &jobs[next], e) < 0) { warn("job failed to start"); ++numfail; } else { @@ -600,23 +539,28 @@ build(void) } if (numjobs == 0) break; - if (poll(fds, jobslen, 5000) < 0) - fatal("poll:"); + if (osjob_wait(osctx, osjobs, jobslen, 5000) < 0) + fatal("osjob_wait:"); for (i = 0; i < jobslen; ++i) { - if (!fds[i].revents || jobwork(&jobs[i])) + if (!osjobs[i].valid || !osjobs[i].has_data || jobwork(osctx, &jobs[i], &osjobs[i])) continue; --numjobs; jobs[i].next = next; - fds[i].fd = -1; + osjobs[i].valid = false; next = i; if (jobs[i].failed) ++numfail; } } - for (i = 0; i < jobslen; ++i) + for (i = 0; i < jobslen; ++i) { free(jobs[i].buf.data); - free(jobs); - free(fds); + free(jobs[i].cmd); + } + if (jobs) + free(jobs); + if (osjobs) + free(osjobs); + osjob_ctx_close(osctx); if (numfail > 0) { if (numfail < buildopts.maxfail) fatal("cannot make progress due to previous errors"); @@ -625,5 +569,5 @@ build(void) else fatal("subcommand failed"); } - ntotal = 0; /* reset in case we just rebuilt the manifest */ + ntotal = 0; /* reset in case we just rebuilt the manifest */ } diff --git a/build.h b/build.h index 47d6ea3..05e951a 100644 --- a/build.h +++ b/build.h @@ -1,8 +1,11 @@ +#include +#include + struct node; struct buildoptions { size_t maxjobs, maxfail; - _Bool verbose, explain, keepdepfile, keeprsp, dryrun; + bool verbose, explain, keepdepfile, keeprsp, dryrun; const char *statusfmt; double maxload; }; diff --git a/deps.c b/deps.c index 94c3f7b..b6f2d09 100644 --- a/deps.c +++ b/deps.c @@ -162,7 +162,8 @@ depsinit(const char *builddir) goto rewrite; } if (sz > cap) { - do cap *= 2; + do + cap *= 2; while (sz > cap); free(buf); buf = xmalloc(cap); @@ -189,7 +190,14 @@ depsinit(const char *builddir) entry = &entries[id]; entry->mtime = (int64_t)buf[2] << 32 | buf[1]; e = entry->node->gen; - if (!e || !edgevar(e, "deps", true)) + bool has_deps; + { + struct string *deps = edgevar(e, "deps", true); + has_deps = (bool)deps; + if (deps) + free(deps); + } + if (!e || !has_deps) continue; sz /= 4; free(entry->deps.node); @@ -276,10 +284,14 @@ depsinit(const char *builddir) } free(oldentries); fflush(depsfile); + fclose(depsfile); if (ferror(depsfile)) fatal("deps log write failed"); if (rename(depstmppath, depspath) < 0) fatal("deps log rename:"); + depsfile = fopen(depspath, "a"); + if (!depsfile) + fatal("deps log reopen:"); if (builddir) { free(depstmppath); free(depspath); @@ -333,9 +345,14 @@ depsparse(const char *name, bool allowmissing) for (; n > 2; n -= 2) bufadd(&buf, '\\'); switch (c) { - case '#': break; - case '\n': c = ' '; continue; - default: bufadd(&buf, '\\'); continue; + case '#': + break; + case '\n': + c = ' '; + continue; + default: + bufadd(&buf, '\\'); + continue; } break; case '$': @@ -366,7 +383,8 @@ depsparse(const char *name, bool allowmissing) } if (c == '\n') { sawcolon = false; - do c = getc(f); + do + c = getc(f); while (c == '\n'); } if (c == EOF) @@ -408,10 +426,14 @@ depsparse(const char *name, bool allowmissing) warn("depfile read '%s':", name); goto err; } + if (out) + free(out); fclose(f); return &deps; err: + if (out) + free(out); fclose(f); return NULL; } @@ -429,6 +451,7 @@ depsload(struct edge *e) n = e->out[0]; deptype = edgevar(e, "deps", true); if (deptype) { + free(deptype); if (n->id != -1 && n->mtime <= entries[n->id].mtime) deps = &entries[n->id].deps; else if (buildopts.explain) @@ -460,21 +483,34 @@ depsrecord(struct edge *e) bool update; deptype = edgevar(e, "deps", true); - if (!deptype || deptype->n == 0) + if (!deptype) { return; + } + if (deptype->n == 0) { + free(deptype); + return; + } if (strcmp(deptype->s, "gcc") != 0) { warn("unsuported deps type: %s", deptype->s); + free(deptype); return; } + free(deptype); depfile = edgevar(e, "depfile", false); - if (!depfile || depfile->n == 0) { + if (!depfile) { + warn("deps but no depfile"); + return; + } + if (depfile->n == 0) { warn("deps but no depfile"); + free(depfile); return; } out = e->out[0]; deps = depsparse(depfile->s, true); if (!buildopts.keepdepfile) remove(depfile->s); + free(depfile); if (!deps) return; update = false; diff --git a/env.c b/env.c index baffaf9..09f4e11 100644 --- a/env.c +++ b/env.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "env.h" #include "graph.h" @@ -211,6 +212,7 @@ ruleaddvar(struct rule *r, char *var, struct evalstring *val) struct string * edgevar(struct edge *e, char *var, bool escape) { + assert(e && "Attempt to read variable of null edge"); static void *const cycle = (void *)&cycle; struct evalstring *str, *p; struct treenode *n; @@ -234,6 +236,7 @@ edgevar(struct edge *e, char *var, bool escape) n->value = cycle; len = 0; for (p = str; p; p = p->next) { + // leaks memory here. p->str not cleaned up in all cases if (p->var) p->str = edgevar(e, p->var, escape); if (p->str) diff --git a/graph.c b/graph.c index 16573c3..b16d44a 100644 --- a/graph.c +++ b/graph.c @@ -86,6 +86,12 @@ nodestat(struct node *n) n->mtime = osmtime(n->path->s); } +#ifdef _WIN32 +const char _esc = '"'; +#else +const char _esc = '\''; +#endif + struct string * nodepath(struct node *n, bool escape) { @@ -101,22 +107,22 @@ nodepath(struct node *n, bool escape) for (s = n->path->s; *s; ++s) { if (!isalnum(*(unsigned char *)s) && !strchr("_+-./", *s)) escape = true; - if (*s == '\'') + if (*s == _esc) ++nquote; } if (escape) { n->shellpath = mkstr(n->path->n + 2 + 3 * nquote); d = n->shellpath->s; - *d++ = '\''; + *d++ = _esc; for (s = n->path->s; *s; ++s) { *d++ = *s; - if (*s == '\'') { + if (*s == _esc) { *d++ = '\\'; - *d++ = '\''; - *d++ = '\''; + *d++ = _esc; + *d++ = _esc; } } - *d++ = '\''; + *d++ = _esc; } else { n->shellpath = n->path; } @@ -175,6 +181,7 @@ edgehash(struct edge *e) } else { e->hash = murmurhash64a(cmd->s, cmd->n); } + free(cmd); } static struct edge * diff --git a/graph.h b/graph.h index 5ea808a..7e9538c 100644 --- a/graph.h +++ b/graph.h @@ -1,4 +1,6 @@ -#include /* for uint64_t */ +#include +#include +#include /* for uint64_t */ /* set in the tv_nsec field of a node's mtime */ enum { @@ -26,7 +28,7 @@ struct node { int32_t id; /* does the node need to be rebuilt */ - _Bool dirty; + bool dirty; }; /* build rule, i.e., edge between inputs and outputs */ @@ -53,13 +55,13 @@ struct edge { size_t nprune; enum { - FLAG_WORK = 1 << 0, /* scheduled for build */ - FLAG_HASH = 1 << 1, /* calculated the command hash */ - FLAG_DIRTY_IN = 1 << 3, /* dirty input */ - FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ - FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, - FLAG_CYCLE = 1 << 5, /* used for cycle detection */ - FLAG_DEPS = 1 << 6, /* dependencies loaded */ + FLAG_WORK = 1 << 0, /* scheduled for build */ + FLAG_HASH = 1 << 1, /* calculated the command hash */ + FLAG_DIRTY_IN = 1 << 3, /* dirty input */ + FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ + FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, + FLAG_CYCLE = 1 << 5, /* used for cycle detection */ + FLAG_DEPS = 1 << 6, /* dependencies loaded */ } flags; /* used to coordinate ready work in build() */ diff --git a/htab.h b/htab.h index 20e1aeb..b6b0b25 100644 --- a/htab.h +++ b/htab.h @@ -1,4 +1,4 @@ -#include /* for uint64_t */ +#include /* for uint64_t */ struct hashtablekey { uint64_t hash; diff --git a/log.c b/log.c index 47144c2..c082861 100644 --- a/log.c +++ b/log.c @@ -6,6 +6,7 @@ #include "graph.h" #include "log.h" #include "util.h" +#include "os.h" static FILE *logfile; static const char *logname = ".ninja_log"; @@ -74,11 +75,11 @@ loginit(const char *builddir) ++nline; p = buf.data; buf.len = 0; - if (!nextfield(&p)) /* start time */ + if (!nextfield(&p)) /* start time */ continue; - if (!nextfield(&p)) /* end time */ + if (!nextfield(&p)) /* end time */ continue; - s = nextfield(&p); /* mtime (used for restat) */ + s = nextfield(&p); /* mtime (used for restat) */ if (!s) continue; mtime = strtoll(s, &s, 10); @@ -86,7 +87,7 @@ loginit(const char *builddir) warn("corrupt build log: invalid mtime"); continue; } - s = nextfield(&p); /* output path */ + s = nextfield(&p); /* output path */ if (!s) continue; n = nodeget(s, 0); @@ -95,7 +96,7 @@ loginit(const char *builddir) if (n->logmtime == MTIME_MISSING) ++nentry; n->logmtime = mtime; - s = nextfield(&p); /* command hash */ + s = nextfield(&p); /* command hash */ if (!s) continue; n->hash = strtoull(s, &s, 16); @@ -136,10 +137,14 @@ loginit(const char *builddir) } } fflush(logfile); + fclose(logfile); if (ferror(logfile)) fatal("build log write failed"); if (rename(logtmppath, logpath) < 0) fatal("build log rename:"); + logfile = fopen(logpath, "a"); + if (!logfile) + fatal("build log reopen:"); if (builddir) { free(logpath); free(logtmppath); diff --git a/os-posix.c b/os-posix.c index 4c2a7f0..88bc968 100644 --- a/os-posix.c +++ b/os-posix.c @@ -1,13 +1,24 @@ +#include #define _POSIX_C_SOURCE 200809L + #include #include #include -#include -#include +#include +#include + #include "graph.h" #include "os.h" #include "util.h" +#include +#include +#include +#include +#include +#include +#include + void osgetcwd(char *buf, size_t len) { @@ -83,3 +94,163 @@ since it has not been updated to support POSIX.1-2008: #endif } } + +int +osclock_gettime_monotonic(struct ostimespec *time) +{ + struct timespec t; + if (clock_gettime(CLOCK_MONOTONIC, &t) < 0) { + return -1; + } + time->tv_sec = t.tv_sec; + time->tv_nsec = t.tv_nsec; + return 0; +} + +////////////// Jobs + +struct osjob_ctx { + struct pollfd *pfds; + size_t pfds_len; +}; + +struct osjob_ctx * +osjob_ctx_create() +{ + struct osjob_ctx *result = xmalloc(sizeof(struct osjob_ctx)); + memset(result, 0, sizeof(*result)); + return result; +} + +void +osjob_ctx_close(struct osjob_ctx *ctx) +{ + if (ctx->pfds) + free(ctx->pfds); + free(ctx); +} + +int +osjob_close(struct osjob_ctx *ctx, struct osjob *ojob) +{ + + close(ojob->fd); + kill(ojob->pid, SIGTERM); + memset(ojob, 0, sizeof(*ojob)); + return 0; +} + +int +osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd) +{ + int status; + if (waitpid(ojob->pid, &status, 0) < 0) { + warn("waitpid %d:", ojob->pid); + goto err; + } else if (WIFSIGNALED(status)) { + warn("job terminated due to signal %d: %s", WTERMSIG(status), cmd->s); + goto err; + } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + warn("job failed with status %d: %s", WEXITSTATUS(status), cmd->s); + goto err; + } + return osjob_close(ctx, ojob); +err: + osjob_close(ctx, ojob); + return -1; +} + +int +osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobslen, int timeout) +{ + if (ctx->pfds_len < jobslen) { + ctx->pfds = xreallocarray(ctx->pfds, jobslen, sizeof(ctx->pfds[0])); + ctx->pfds_len = jobslen; + } + nfds_t count = 0; + for (size_t i = 0; i < jobslen; ++i) { + if (ojobs[i].valid) { + ctx->pfds[i].events = POLLIN; + ctx->pfds[i].fd = ojobs[i].fd; + ctx->pfds[i].revents = 0; + count++; + } + } + if (poll(ctx->pfds, count, timeout) < 0) { + fatal("poll:"); + } + struct osjob *curr = ojobs; + for (nfds_t i = 0; i < count; ++i) { + while (!curr->valid) + curr++; + curr->has_data = ctx->pfds[i].revents; + curr++; + } + return 0; +} + +ssize_t +osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen) +{ + assert(ojob->has_data); + return read(ojob->fd, buf, buflen); +} + +int +osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console) +{ + extern char **environ; + int fd[2]; + posix_spawn_file_actions_t actions; + char *argv[] = {"/bin/sh", "-c", cmd->s, NULL}; + + if (pipe(fd) < 0) { + warn("pipe:"); + return -1; + } + + created->has_data = false; + created->fd = fd[0]; + + if ((errno = posix_spawn_file_actions_init(&actions))) { + warn("posix_spawn_file_actions_init:"); + goto err2; + } + if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) { + warn("posix_spawn_file_actions_addclose:"); + goto err3; + } + if (!console) { + if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) { + warn("posix_spawn_file_actions_addopen:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) { + warn("posix_spawn_file_actions_adddup2:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) { + warn("posix_spawn_file_actions_adddup2:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) { + warn("posix_spawn_file_actions_addclose:"); + goto err3; + } + } + if ((errno = posix_spawn(&created->pid, argv[0], &actions, NULL, argv, environ))) { + warn("posix_spawn %s:", cmd->s); + goto err3; + } + posix_spawn_file_actions_destroy(&actions); + close(fd[1]); + + return 0; + +err3: + posix_spawn_file_actions_destroy(&actions); +err2: + close(fd[0]); + close(fd[1]); + return -1; +} diff --git a/os-win32.c b/os-win32.c new file mode 100644 index 0000000..ca4b23a --- /dev/null +++ b/os-win32.c @@ -0,0 +1,338 @@ +#include +#include + +#include "os.h" +#include "graph.h" +#include "util.h" + +void +osgetcwd(char *buf, size_t len) +{ + if (!GetCurrentDirectoryA(len, buf)) { + fatal("GetCurrentDirectory:"); + } +} + +void +oschdir(const char *dir) +{ + if (!SetCurrentDirectoryA(dir)) { + fatal("SetCurrentDirectory %s:", dir); + } +} + +int +osmkdirs(struct string *_path, bool parent) +{ + char *path = _path->s; + if (!parent) { + if (!CreateDirectoryA(path, NULL)) { + DWORD err = GetLastError(); + if (err != ERROR_ALREADY_EXISTS) { + warn("mkdirs %s:", path); + return -1; + } + } + return 0; + } + char folder[MAX_PATH]; + char *end; + ZeroMemory(folder, sizeof(folder)); + + end = strchr(path, L'\\'); + + while (end != NULL) { + strncpy(folder, path, end - path + 1); + if (!CreateDirectoryA(folder, NULL)) { + DWORD err = GetLastError(); + if (err != ERROR_ALREADY_EXISTS) { + warn("mkdirs %s:", folder); + return -1; + } + } + end = strchr(++end, L'\\'); + } + return 0; +} + +// taken fron ninja-build +static int64_t +TimeStampFromFileTime(const FILETIME *filetime) +{ + // FILETIME is in 100-nanosecond increments since the Windows epoch. + // We don't much care about epoch correctness but we do want the + // resulting value to fit in a 64-bit integer. + int64_t mtime = ((int64_t)filetime->dwHighDateTime << 32) | + ((int64_t)filetime->dwLowDateTime); + // 1600 epoch -> 2000 epoch (subtract 400 years). + return (int64_t)mtime - 12622770400LL * (1000000000LL / 100LL); +} + +// taken fron ninja-build +int64_t +osmtime(const char *name) +{ + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesExA(name, GetFileExInfoStandard, &attrs)) { + DWORD win_err = GetLastError(); + if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { + // ok + } else { + warn("GetFileTime:"); + } + return MTIME_MISSING; + } + return TimeStampFromFileTime(&attrs.ftLastWriteTime); +} + +int +osclock_gettime_monotonic(struct ostimespec *ts) +{ + static LARGE_INTEGER frequency = {0}; + static BOOL initialized = FALSE; + static BOOL qpf_available = FALSE; + if (!initialized) { + qpf_available = QueryPerformanceFrequency(&frequency); + initialized = TRUE; + } + if (!qpf_available) { + SetLastError(ERROR_NOT_CAPABLE); + warn("QueryPerformanceFrequency:"); + return -1; + } + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + warn("QueryPerformanceCounter:"); + return -1; + } + uint64_t ticks = counter.QuadPart; + uint64_t ticks_per_sec = frequency.QuadPart; + + ts->tv_sec = ticks / ticks_per_sec; + uint64_t remaining_ticks = ticks % ticks_per_sec; + ts->tv_nsec = (remaining_ticks * 1000000000ULL) / ticks_per_sec; + + return 0; +} + +///////////////////////////// JOBS + +static HANDLE +win_create_nul() +{ + SECURITY_ATTRIBUTES sa = {sizeof(sa)}; + sa.bInheritHandle = TRUE; + // Must be inheritable so subprocesses can dup to children. + HANDLE nul = CreateFileA("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + &sa, OPEN_EXISTING, 0, NULL); + if (nul == INVALID_HANDLE_VALUE) + fatal("couldn't open nul:"); + return nul; +} + +struct osjob_ctx { + HANDLE iocp; +}; + +struct osjob_ctx * +osjob_ctx_create() +{ + struct osjob_ctx *result = xmalloc(sizeof(*result)); + result->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (result->iocp == INVALID_HANDLE_VALUE) { + fatal("CreateIoCompletionPort:"); + } + return result; +} + +void +osjob_ctx_close(struct osjob_ctx *osctx) +{ + CloseHandle(osctx->iocp); + free(osctx); +} + +static int +win_start_read(struct osjob_ctx *osctx, struct osjob *job) +{ + DWORD readAmount; + BOOL read = ReadFile(job->output, job->buff, sizeof(job->buff), &readAmount, &job->overlapped); + if (read && !readAmount) { + job->to_read = 0; + job->has_data = true; + } else if (!read && GetLastError() != ERROR_IO_PENDING) { + warn("StartPipeRead:"); + osjob_close(osctx, job); + return -1; + } + return 0; +} + +int +osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, bool console) +{ + // Generate a unique pipe name + static volatile long counter = 0; + char pipeName[MAX_PATH]; + snprintf(pipeName, sizeof(pipeName), "\\\\.\\pipe\\samu-%ld-%lu", + InterlockedIncrement(&counter), GetCurrentProcessId()); + + SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; + + // Create overlapped read handle (server side) + HANDLE stdoutRead = CreateNamedPipeA( + pipeName, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, // Number of instances + 4096, // Out buffer size + 4096, // In buffer size + 0, // Timeout + &sa); + if (stdoutRead == INVALID_HANDLE_VALUE) { + warn("CreateNamedPipe:"); + return -1; + } + + // Create overlapped write handle (client side) + HANDLE stdoutWrite = CreateFileA( + pipeName, + GENERIC_WRITE, + 0, // No sharing + &sa, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + if (stdoutWrite == INVALID_HANDLE_VALUE) { + CloseHandle(stdoutRead); + warn("CreateFile(pipe):"); + return -1; + } + + HANDLE nul = win_create_nul(); + + STARTUPINFOA si = {sizeof(si)}; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = stdoutWrite; + si.hStdError = stdoutWrite; + si.hStdInput = nul; + + PROCESS_INFORMATION pi; + BOOL inheritHandles = TRUE; + DWORD flags = 0; + + if (!CreateProcessA(NULL, cmd->s, NULL, NULL, inheritHandles, flags, NULL, NULL, &si, &pi)) { + CloseHandle(stdoutRead); + CloseHandle(stdoutWrite); + CloseHandle(nul); + warn("CreateProcess:"); + return -1; + } + + CloseHandle(stdoutWrite); + CloseHandle(nul); + CloseHandle(pi.hThread); + + created->output = stdoutRead; + created->hProcess = pi.hProcess; + created->valid = true; + created->has_data = false; + memset(&created->overlapped, 0, sizeof(OVERLAPPED)); + + // Associate with IOCP + if (!CreateIoCompletionPort(created->output, osctx->iocp, (ULONG_PTR)created, 0)) { + warn("CreateIoCompletionPort:"); + osjob_close(osctx, created); + return -1; + } + + if (win_start_read(osctx, created) < 0) { + return -1; + } + + return 0; +} + +int +osjob_wait(struct osjob_ctx *osctx, struct osjob ojobs[], size_t jobs_count, int timeout) +{ + (void)osctx; + OVERLAPPED_ENTRY entries[64]; + ULONG num_entries = 0; + const DWORD timeout_ms = timeout == -1 ? INFINITE : (DWORD)timeout; + + for (size_t i = 0; i < jobs_count; ++i) { + struct osjob *job = ojobs + i; + if (job->valid && job->has_data) { + return 0; // some jobs are already buffered + } + } + + if (!GetQueuedCompletionStatusEx(osctx->iocp, entries, 64, &num_entries, timeout_ms, FALSE)) { + return GetLastError() == WAIT_TIMEOUT ? 0 : -1; + } + + for (ULONG i = 0; i < num_entries; i++) { + struct osjob *job = (struct osjob *)entries[i].lpCompletionKey; + const DWORD bytes = entries[i].dwNumberOfBytesTransferred; + job->to_read = bytes; + job->has_data = true; + } + return 0; +} + +ssize_t +osjob_work(struct osjob_ctx *osctx, struct osjob *ojob, void *buf, size_t buflen) +{ + assert(ojob->has_data); + if (ojob->to_read == 0) { // EOF/process exit + return 0; + } else { // Data available + size_t read = buflen < ojob->to_read ? buflen : ojob->to_read; + memcpy(buf, ojob->buff, read); + ojob->to_read -= read; + if (!ojob->to_read) { + if (win_start_read(osctx, ojob) < 0) { + return -1; + } + ojob->has_data = false; + } else { + memmove(ojob->buff, ojob->buff + read, ojob->to_read); + // move buffer -> will get polled again as if wait() + } + return read; + } +} + +int +osjob_done(struct osjob_ctx *osctx, struct osjob *ojob, struct string *cmd) +{ + if (WaitForSingleObject(ojob->hProcess, INFINITE) == WAIT_FAILED) { + warn("wait process:"); + goto err; + } + DWORD exit_code; + if (!GetExitCodeProcess(ojob->hProcess, &exit_code)) { + warn("WaitForSingleObject:"); + goto err; + } + if (exit_code != 0) { + warn("job failed with status %llu: %s", exit_code, cmd->s); + goto err; + } + return osjob_close(osctx, ojob); +err: + osjob_close(osctx, ojob); + return -1; +} + +int +osjob_close(struct osjob_ctx *osctx, struct osjob *ojob) +{ + (void)osctx; + CloseHandle(ojob->hProcess); + CloseHandle(ojob->output); + memset(ojob, 0, sizeof(*ojob)); + return 0; +} diff --git a/os.h b/os.h index 91cae05..0255838 100644 --- a/os.h +++ b/os.h @@ -1,9 +1,68 @@ +#include +#include +#include +#include + +struct ostimespec { + int64_t tv_sec; + int64_t tv_nsec; +}; + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +typedef void *HANDLE; +typedef long long ssize_t; + +#define setvbuf(stream, buff, mode, len) (void)0 + +struct osjob { + bool has_data; + bool valid; + + OVERLAPPED overlapped; + HANDLE output; + HANDLE hProcess; + + DWORD to_read; + + char buff[4096]; +}; + +#else +#include "poll.h" +struct osjob { + bool has_data; + bool valid; + + int pid; + int fd; +}; +#endif + +struct buffer; struct string; void osgetcwd(char *, size_t); /* changes the working directory to the given path */ void oschdir(const char *); /* creates all the parent directories of the given path */ -int osmkdirs(struct string *, _Bool); +int osmkdirs(struct string *, bool); /* queries the mtime of a file in nanoseconds since the UNIX epoch */ int64_t osmtime(const char *); +/* get current monotonic time */ +int osclock_gettime_monotonic(struct ostimespec *); + +// os-specific job functions + +struct osjob_ctx; + +struct osjob_ctx *osjob_ctx_create(); +void osjob_ctx_close(struct osjob_ctx *ctx); +int osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console); +int osjob_wait(struct osjob_ctx *ctx, struct osjob ojobs[], size_t jobs_count, int timeout); +ssize_t osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen); +int osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd); +int osjob_close(struct osjob_ctx *ctx, struct osjob *ojob); diff --git a/parse.c b/parse.c index c4cf0de..990d569 100644 --- a/parse.c +++ b/parse.c @@ -132,8 +132,10 @@ parseedge(struct scanner *s, struct environment *env) npaths = 0; val = edgevar(e, "pool", true); - if (val) + if (val) { e->pool = poolget(val->s); + free(val); + } } static void diff --git a/samu.c b/samu.c index 05f5597..bead33f 100644 --- a/samu.c +++ b/samu.c @@ -109,7 +109,8 @@ parseenvargs(char *env) } argv[argc] = NULL; - ARGBEGIN { + ARGBEGIN + { case 'j': jobsflag(EARGF(usage())); break; @@ -121,7 +122,8 @@ parseenvargs(char *env) break; default: fatal("invalid option in SAMUFLAGS"); - } ARGEND + } + ARGEND free(env); } @@ -148,7 +150,8 @@ main(int argc, char *argv[]) argv0 = progname(argv[0], "samu"); parseenvargs(getenv("SAMUFLAGS")); - ARGBEGIN { + ARGBEGIN + { case '-': arg = EARGF(usage()); if (strcmp(arg, "version") == 0) { @@ -197,13 +200,16 @@ main(int argc, char *argv[]) break; default: usage(); - } ARGEND + } + ARGEND argdone: if (!buildopts.maxjobs) { #ifdef _SC_NPROCESSORS_ONLN long nproc = sysconf(_SC_NPROCESSORS_ONLN); switch (nproc) { - case -1: case 0: case 1: + case -1: + case 0: + case 1: buildopts.maxjobs = 2; break; case 2: diff --git a/samu.manifest b/samu.manifest new file mode 100644 index 0000000..be79bf0 --- /dev/null +++ b/samu.manifest @@ -0,0 +1,9 @@ + + + + + UTF-8 + true + + + \ No newline at end of file diff --git a/scan.c b/scan.c index 92cabd4..2167dd0 100644 --- a/scan.c +++ b/scan.c @@ -117,7 +117,8 @@ comment(struct scanner *s) { if (s->chr != '#') return false; - do next(s); + do + next(s); while (!newline(s)); return true; } @@ -144,12 +145,12 @@ scankeyword(struct scanner *s, char **var) const char *name; int value; } keywords[] = { - {"build", BUILD}, - {"default", DEFAULT}, - {"include", INCLUDE}, - {"pool", POOL}, - {"rule", RULE}, - {"subninja", SUBNINJA}, + {"build", BUILD}, + {"default", DEFAULT}, + {"include", INCLUDE}, + {"pool", POOL}, + {"rule", RULE}, + {"subninja", SUBNINJA}, }; int low = 0, high = LEN(keywords) - 1, mid, cmp; diff --git a/tool.c b/tool.c index 092bf46..d51bb50 100644 --- a/tool.c +++ b/tool.c @@ -72,7 +72,8 @@ clean(int argc, char *argv[]) struct node *n; struct rule *r; - ARGBEGIN { + ARGBEGIN + { case 'g': cleangen = true; break; @@ -82,7 +83,8 @@ clean(int argc, char *argv[]) default: fprintf(stderr, "usage: %s ... -t clean [-gr] [targets...]\n", argv0); return 2; - } ARGEND + } + ARGEND if (cleanrule) { if (!argc) @@ -200,14 +202,16 @@ compdb(int argc, char *argv[]) int i; size_t off; - ARGBEGIN { + ARGBEGIN + { case 'x': expandrsp = true; break; default: fprintf(stderr, "usage: %s ... -t compdb [-x] [rules...]\n", argv0); return 2; - } ARGEND + } + ARGEND osgetcwd(dir, sizeof(dir)); @@ -436,12 +440,12 @@ targets(int argc, char *argv[]) } static const struct tool tools[] = { - {"clean", clean}, - {"commands", commands}, - {"compdb", compdb}, - {"graph", graph}, - {"query", query}, - {"targets", targets}, + {"clean", clean}, + {"commands", commands}, + {"compdb", compdb}, + {"graph", graph}, + {"query", query}, + {"targets", targets}, }; const struct tool * diff --git a/util.c b/util.c index 8ee3ddd..6ff2d34 100644 --- a/util.c +++ b/util.c @@ -152,6 +152,13 @@ delevalstr(void *ptr) void canonpath(struct string *path) { + +#ifdef _WIN32 +#define _samu_path_sep '\\' +#else +#define _samu_path_sep '/' +#endif + char *component[60]; int n; char *s, *d, *end; @@ -161,22 +168,23 @@ canonpath(struct string *path) s = d = path->s; end = path->s + path->n; n = 0; - if (*s == '/') { + if (*s == _samu_path_sep) { ++s; ++d; } while (s < end) { switch (s[0]) { - case '/': + case _samu_path_sep: ++s; continue; case '.': switch (s[1]) { - case '\0': case '/': + case '\0': + case _samu_path_sep: s += 2; continue; case '.': - if (s[2] != '/' && s[2] != '\0') + if (s[2] != _samu_path_sep && s[2] != '\0') break; if (n > 0) { d = component[--n]; @@ -192,7 +200,7 @@ canonpath(struct string *path) if (n == LEN(component)) fatal("path has too many components: %s", path->s); component[n++] = d; - while (*s != '/' && *s != '\0') + while (*s != _samu_path_sep && *s != '\0') *d++ = *s++; *d++ = *s++; } diff --git a/util.h b/util.h index 5da348c..285445e 100644 --- a/util.h +++ b/util.h @@ -18,7 +18,14 @@ struct evalstring { #define LEN(a) (sizeof(a) / sizeof((a)[0])) void warn(const char *, ...); -void fatal(const char *, ...); + +#ifdef _MSC_VER +__declspec(noreturn) +#elif defined(__GNUC__) +__attribute__((noreturn)) +#endif +void +fatal(const char *, ...); void *xmalloc(size_t); void *xreallocarray(void *, size_t, size_t);