master xplshn/aruu / cmd / dev / ninqu / ninja.c
  1#include "ninqu.h"
  2
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6
  7/* ninjas own tokenizer treats '$', ':' and ' ' as special outside
  8 * of a rules variables, so any path used in a build statement
  9 * (outputs, inputs, deps) needs those three escaped with a leading
 10 * '$'. this is a different escaping pass from shell quoting below,
 11 * it runs on ninjas file syntax, not on the command it configures */
 12static char *
 13ninja_path_escape(const char *s)
 14{
 15  size_t i, j = 0;
 16  size_t len = strlen(s);
 17  char  *out = emalloc(len * 2 + 1);
 18
 19  for (i = 0; i < len; i++) {
 20    if (s[i] == '$' || s[i] == ':' || s[i] == ' ')
 21      out[j++] = '$';
 22    out[j++] = s[i];
 23  }
 24  out[j] = '\0';
 25  return out;
 26}
 27
 28/* ninqus argv is already fully expanded ($(VAR) substitution
 29 * happened at parse time), so the ninja rules command is a plain
 30 * literal string, never $in/$out. write one argv word as a single
 31 * shell-quoted token: close/escape/reopen for an embedded quote,
 32 * and double any '$' so ninjas own preprocessing pass on the
 33 * "command" value hands the shell back exactly one '$' */
 34static void
 35fprint_shell_word_plain(FILE *f, const char *s)
 36{
 37  fputc('\'', f);
 38  for (; *s; s++) {
 39    if (*s == '\'')
 40      fputs("'\\''", f);
 41    else if (*s == '$')
 42      fputs("$$", f);
 43    else
 44      fputc(*s, f);
 45  }
 46  fputc('\'', f);
 47}
 48
 49/* ninja reads a "command = ..." value to the end of the physical
 50 * line: a literal newline byte terminates the statement, and $\n is
 51 * a continuation that strips the newline rather than preserving it,
 52 * so neither lets a real newline survive inside the value. POSIX
 53 * single quotes cannot help either, there is no escape processing
 54 * inside them, so a backslash-n written between quotes stays two
 55 * literal characters, not a newline, once the shell reads it back.
 56 *
 57 * the fix is to not carry the newline in quoted text at all: emit
 58 * a command substitution that runs the word through `printf '%b'`,
 59 * whose %b directive is POSIX-specified to expand backslash escapes
 60 * (including \n) in its argument at runtime. the argument to printf
 61 * is itself single-quoted using the same rules as the plain case,
 62 * plus doubling any backslash so it survives as data rather than
 63 * being read as an escape by the quoting layer itself, and the `$(`
 64 * that opens the substitution needs its own `$` doubled so ninjas
 65 * pass just picks it out unmodified for the shell to see */
 66static void
 67fprint_shell_word(FILE *f, const char *s)
 68{
 69  const char *p;
 70  int         has_nl = 0;
 71
 72  for (p = s; *p; p++) {
 73    if (*p == '\n') {
 74      has_nl = 1;
 75      break;
 76    }
 77  }
 78
 79  if (!has_nl) {
 80    fprint_shell_word_plain(f, s);
 81    return;
 82  }
 83
 84  /* wrapped in double quotes so the shell treats the substitutions
 85   * result as one word instead of splitting it on whitespace, which
 86   * is exactly what an unquoted $(...) would do to a multi-line
 87   * result */
 88  fputs("\"$$(printf '%b' '", f);
 89  for (; *s; s++) {
 90    if (*s == '\'')
 91      fputs("'\\''", f);
 92    else if (*s == '$')
 93      fputs("$$", f);
 94    else if (*s == '\\')
 95      fputs("\\\\", f);
 96    else if (*s == '\n')
 97      fputs("\\n", f);
 98    else
 99      fputc(*s, f);
100  }
101  fputs("')\"", f);
102}
103
104/* an instance with no (out) has no on-disk path of its own (a
105 * (phony) rule, or a pure grouping rule). ninja still needs a
106 * target name to hang a build statement off, so one is made up
107 * from the rule name and instance index. it is never a real file,
108 * so ninja will always find it missing and re-run it, the same
109 * "always stale" behaviour inst_stale() gives phony instances in
110 * the internal engine. the name is a single flat path component
111 * (no '/'), so ninja never mkdir -p's a directory for it: ninja
112 * only creates the parent directory of a build outputs path, and a
113 * name with no slash has no parent beyond the working directory */
114static const char *
115inst_target(struct Inst *inst, int idx, char *buf, size_t bufsz)
116{
117  if (inst->out[0])
118    return inst->out;
119  snprintf(buf, bufsz, "__ninqu_phony__%s_%d", rules[inst->rule_idx].name, idx);
120  return buf;
121}
122
123/* does this instance look like "cc ... -c ... -o foo.o"? if so its
124 * a real compile step and ninja can track its header dependencies
125 * precisely via a gcc-style depfile instead of only the coarse
126 * order-only edges dep_inst gives every instance. matched by
127 * basename so whatever CC the manifest actually resolved (gcc,
128 * clang, a cross compiler under some path) is what gets the extra
129 * flags, nothing here hardcodes a specific compiler binary; the
130 * check is on -c and a .o output, not on the compiler name meaning
131 * anything beyond "probably understands -MMD -MF" */
132static int
133is_cc_compile(struct Inst *inst)
134{
135  static const char *const compilers[] = {"cc", "gcc", "clang", "g++", "c++", NULL};
136  const char              *prog, *slash;
137  size_t                   len;
138  int                      k, has_c;
139
140  if (inst->phony || !inst->out[0] || inst->cmd.is_pipe || inst->cmd.argv.n < 2)
141    return 0;
142
143  len = strlen(inst->out);
144  if (len < 2 || strcmp(inst->out + len - 2, ".o") != 0)
145    return 0;
146
147  prog  = inst->cmd.argv.v[0];
148  slash = strrchr(prog, '/');
149  if (slash)
150    prog = slash + 1;
151
152  for (k = 0; compilers[k]; k++)
153    if (strcmp(prog, compilers[k]) == 0)
154      break;
155  if (!compilers[k])
156    return 0;
157
158  for (k = 1, has_c = 0; k < inst->cmd.argv.n; k++)
159    if (strcmp(inst->cmd.argv.v[k], "-c") == 0)
160      has_c = 1;
161
162  return has_c;
163}
164
165/* the command a rule runs: an optional workdir cd, the argv or
166 * piped stages shell-quoted word by word, and an optional stdout
167 * redirect into (out). mirrors spawn_inst()/spawn_pipe() exactly,
168 * just written out as text instead of forked and execd. a compile
169 * step (is_cc_compile()) gets "-MMD -MF <out>.d" spliced in right
170 * after the compiler name, paired with the depfile/deps lines
171 * emit_ninja() writes into that instances rule block */
172static void
173emit_command(FILE *f, struct Inst *inst)
174{
175  int is_cc = is_cc_compile(inst);
176  int i;
177
178  if (inst->workdir[0]) {
179    fputs("cd ", f);
180    fprint_shell_word(f, inst->workdir);
181    fputs(" && ", f);
182  }
183
184  if (inst->cmd.is_pipe) {
185    for (i = 0; i < inst->cmd.nstages; i++) {
186      int k;
187      if (i > 0)
188        fputs(" | ", f);
189      for (k = 0; k < inst->cmd.stages[i]->n; k++) {
190        if (k > 0)
191          fputc(' ', f);
192        fprint_shell_word(f, inst->cmd.stages[i]->v[k]);
193      }
194    }
195  } else {
196    for (i = 0; i < inst->cmd.argv.n; i++) {
197      if (i > 0)
198        fputc(' ', f);
199      fprint_shell_word(f, inst->cmd.argv.v[i]);
200      if (i == 0 && is_cc) {
201        fputs(" -MMD -MF ", f);
202        fprint_shell_word(f, inst->out);
203        fputs(".d", f);
204      }
205    }
206  }
207
208  if (inst->redirect) {
209    fputs(" > ", f);
210    fprint_shell_word(f, inst->out);
211  }
212}
213
214/* write every instance currently in insts[] (the same closure the
215 * internal engine would have built, wanted targets plus their
216 * transitive deps) out as a build.ninja. one rule per instance
217 * (ninqu already resolved everything to a literal command line, so
218 * there is no shared rule template to factor out), one build
219 * statement per instance. dep_inst edges become order-only ("||"):
220 * inst_stale() never consults dep_inst for staleness either, only
221 * (in)/(stale_extra), so mirroring that as a plain (not order-only)
222 * prerequisite list is what makes ninjas own mtime check agree
223 * with ninqus.
224 *
225 * ninja only knows build outputs, never ninqus rule/group/meta
226 * names, so `ninja LIBUTIL` (what the Makefile forwards $@ as) has
227 * nothing to resolve against on its own. wanted carries the literal
228 * command-line words ninqu itself was invoked with, one phony alias
229 * per word makes that name buildable too. when more than one target
230 * was requested in the same invocation every alias points at the
231 * whole combined closure rather than just its own slice, since
232 * insts[] no longer remembers which instance came from which wanted
233 * name, thats fine for the Makefiles own use (always one target
234 * per invocation), just slightly conservative for a multi-target
235 * `ninqu -G ninja a b` run */
236void
237emit_ninja(const char *path, struct StrList *wanted)
238{
239  FILE *f = fopen(path, "w");
240  int   i, k;
241
242  if (!f)
243    eprintf("emit_ninja: open %s:", path);
244
245  fputs("# autogenerated by `ninqu -G ninja`, do not edit\n", f);
246  fputs("ninja_required_version = 1.3\n\n", f);
247
248  for (i = 0; i < ninsts; i++) {
249    struct Inst *inst = &insts[i];
250    fprintf(f, "rule r%d\n", i);
251    fprintf(
252        f,
253        "  description = %s %s\n",
254        rules[inst->rule_idx].name,
255        inst->out[0] ? inst->out : "(phony)"
256    );
257    if (is_cc_compile(inst)) {
258      char *esc = ninja_path_escape(inst->out);
259      fprintf(f, "  depfile = %s.d\n", esc);
260      fputs("  deps = gcc\n", f);
261      free(esc);
262    }
263    fputs("  command = ", f);
264    emit_command(f, inst);
265    fputs("\n\n", f);
266  }
267
268  for (i = 0; i < ninsts; i++) {
269    struct Inst *inst = &insts[i];
270    char         buf[1024];
271    const char  *out = inst_target(inst, i, buf, sizeof buf);
272    char        *esc = ninja_path_escape(out);
273
274    fprintf(f, "build %s: r%d", esc, i);
275    free(esc);
276
277    for (k = 0; k < inst->in.n; k++) {
278      esc = ninja_path_escape(inst->in.v[k]);
279      fprintf(f, " %s", esc);
280      free(esc);
281    }
282    for (k = 0; k < inst->stale_extra.n; k++) {
283      esc = ninja_path_escape(inst->stale_extra.v[k]);
284      fprintf(f, " %s", esc);
285      free(esc);
286    }
287
288    if (inst->n_dep > 0) {
289      int printed_bar = 0;
290      for (k = 0; k < inst->n_dep; k++) {
291        struct Inst *dep = &insts[inst->dep_inst[k]];
292        char         depbuf[1024];
293        const char  *depout = inst_target(dep, inst->dep_inst[k], depbuf, sizeof depbuf);
294        /* a rule ref that is also a real (dep ...) input already
295         * forces both order and staleness, listing it again as
296         * order-only is pure noise */
297        if (sl_has(&inst->in, depout) || sl_has(&inst->stale_extra, depout))
298          continue;
299        if (!printed_bar) {
300          fputs(" ||", f);
301          printed_bar = 1;
302        }
303        esc = ninja_path_escape(depout);
304        fprintf(f, " %s", esc);
305        free(esc);
306      }
307    }
308    fputc('\n', f);
309  }
310
311  fputs("\ndefault", f);
312  for (i = 0; i < ninsts; i++) {
313    struct Inst *inst = &insts[i];
314    char         buf[1024];
315    const char  *out = inst_target(inst, i, buf, sizeof buf);
316    char        *esc = ninja_path_escape(out);
317    fprintf(f, " %s", esc);
318    free(esc);
319  }
320  fputc('\n', f);
321
322  for (i = 0; i < wanted->n; i++) {
323    const char *name = wanted->v[i];
324    int         dup  = 0;
325
326    for (k = 0; k < ninsts && !dup; k++) {
327      char        buf[1024];
328      const char *out = inst_target(&insts[k], k, buf, sizeof buf);
329      if (strcmp(out, name) == 0)
330        dup = 1;
331    }
332    if (dup)
333      continue;
334
335    {
336      char *esc = ninja_path_escape(name);
337      fprintf(f, "build %s: phony", esc);
338      free(esc);
339    }
340    for (k = 0; k < ninsts; k++) {
341      char        buf[1024];
342      const char *out = inst_target(&insts[k], k, buf, sizeof buf);
343      char       *esc = ninja_path_escape(out);
344      fprintf(f, " %s", esc);
345      free(esc);
346    }
347    fputc('\n', f);
348  }
349
350  if (fshut(f, path))
351    eprintf("emit_ninja: %s:", path);
352  printf("  GEN        %s\n", path);
353}