summaryrefslogtreecommitdiff
path: root/tools/musl-make.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/musl-make.c')
-rw-r--r--tools/musl-make.c2622
1 files changed, 2622 insertions, 0 deletions
diff --git a/tools/musl-make.c b/tools/musl-make.c
new file mode 100644
index 0000000..33cc117
--- /dev/null
+++ b/tools/musl-make.c
@@ -0,0 +1,2622 @@
+/*
+ * This file is part of musl-mod: modified version of Musl libc library.
+ * musl-mod in general and this file specifically are available under
+ * MIT license. See COPYRIGHT file.
+ *
+ * Limited make to build musl. Implements just enough to handle Makefile.
+ */
+
+#include "common.h"
+
+enum {
+ path_max_len = 4096,
+ buf_size = 1024 * 1024,
+ buf_static_size = 512,
+ hash_table_size = 16 * 1024,
+ pat_rules_limit = 16,
+ find_body_max_depth = 2,
+ file_stack_size = 8
+};
+
+const char *debug_prx = ">>> ";
+
+struct pos {
+ char *fname;
+ int line;
+};
+
+struct line {
+ struct str str;
+ struct pos pos;
+};
+
+struct buf {
+ char *start;
+ char *end;
+ char *cur;
+};
+
+struct strbuf {
+ struct str *buf;
+ int len;
+ int size;
+};
+
+struct cmdbuf {
+ struct line *buf;
+ int len;
+ int size;
+};
+
+struct rule_body {
+ struct strbuf prereqs;
+ int normal_prereq_count;
+ struct pos pos;
+
+ struct cmdbuf *recipes;
+};
+
+struct full_rule {
+ struct str *target;
+ struct rule_body *rest;
+};
+
+struct rule_details {
+ struct rule_body *body;
+ struct strbuf targets;
+ int patrule;
+ struct pos *pos;
+};
+
+enum {
+ vs_makefile,
+ vs_cmdargs
+};
+
+struct vt_cell {
+ struct str vname;
+ struct str value;
+ char scope;
+};
+
+struct vtable {
+ struct vt_cell *buf;
+ int cells_occupied;
+};
+
+struct rule_cell {
+ struct str *target;
+ struct rule_body *body;
+};
+
+struct rtable {
+ struct rule_cell *buf;
+ int cells_occupied;
+};
+
+struct file {
+ char *name;
+ char *buf;
+ int line;
+};
+
+struct file_stack {
+ struct file buf[file_stack_size];
+ int idx;
+};
+
+struct world {
+ struct file_stack fstack;
+ struct str target;
+ char **envp;
+
+ int debug_mode;
+
+ struct full_rule pat_rules[pat_rules_limit];
+ int pat_rule_num;
+
+ struct vtable vtable;
+ struct rtable rtable;
+};
+
+struct eval_aux {
+ int level;
+ struct pos *pos;
+ struct full_rule *rule;
+
+ struct buf *mainbuf;
+ struct buf *auxbuf;
+ struct world *world;
+};
+
+static void errorp(struct pos *pos, char *str, ...)
+{
+ va_list vl;
+
+ eprintf("%s:%d: ", pos->fname, pos->line);
+
+ va_start(vl, str);
+ evprintf(str, vl);
+
+ print_strz(&stderr, "\n");
+ va_end(vl);
+ exit(1);
+}
+
+static void strdup(struct str *dest, struct str *src, int with_zero)
+{
+ int slen = src->end - src->start;
+ int len = slen + (with_zero ? 1 : 0);
+ dest->start = malloc(len);
+ dest->end = dest->start + slen;
+
+ memcpy(dest->start, src->start, slen);
+ if (with_zero)
+ dest->start[slen] = 0;
+}
+
+#define HASHTABLE_INIT(type) \
+static void type ## _init(struct type *t) \
+{ \
+ t->buf = malloc(hash_table_size * sizeof(*t->buf)); \
+ t->cells_occupied = 0; \
+ memset(t->buf, 0, hash_table_size * sizeof(*t->buf)); \
+}
+
+HASHTABLE_INIT(vtable)
+HASHTABLE_INIT(rtable)
+
+/* FNV-1a */
+static unsigned int calc_hash(struct str *str)
+{
+ char *tmp;
+ unsigned int h = 2166136261;
+ for (tmp = str->start; tmp != str->end; ++tmp) {
+ h = h ^ *tmp;
+ h *= 16777619;
+ }
+ return h;
+}
+
+static struct rule_cell *rtable_getcell(struct rtable *t, struct str *key)
+{
+ unsigned hash = calc_hash(key);
+ int i = hash & (hash_table_size - 1);
+ for (;;) {
+ if (t->buf[i].target == NULL)
+ break;
+ else if (streq(key, t->buf[i].target))
+ break;
+ i = (i + 1) & (hash_table_size - 1);
+ }
+ return t->buf + i;
+}
+
+static struct vt_cell *vtable_getcell(struct vtable *t, struct str *key)
+{
+ unsigned hash = calc_hash(key);
+ int i = hash & (hash_table_size - 1);
+ for (;;) {
+ if (t->buf[i].vname.start == NULL)
+ break;
+ else if (streq(key, &t->buf[i].vname))
+ break;
+ i = (i + 1) & (hash_table_size - 1);
+ }
+ return t->buf + i;
+}
+
+static void vtable_update(struct vtable *vt, struct str *key,
+ int alloc_name, struct str *value, char scope)
+{
+ struct vt_cell *cell = vtable_getcell(vt, key);
+ if (cell->vname.start == NULL) {
+ ++vt->cells_occupied;
+ if ((double)vt->cells_occupied / hash_table_size >= 0.7)
+ error("vtable: threshold reached");
+
+ if (alloc_name)
+ strdup(&cell->vname, key, 0);
+ else
+ cell->vname = *key;
+ }
+ cell->value = *value;
+ cell->scope = scope;
+}
+
+static struct str *vtable_find(struct vtable *vt, struct str *key)
+{
+ struct vt_cell *tmp = vtable_getcell(vt, key);
+ if (tmp->vname.start == NULL)
+ return NULL;
+ return &tmp->value;
+}
+
+static void rtable_add(struct rtable *rt, struct str *target,
+ int alloc_name, struct rule_body *rule)
+{
+ struct rule_cell *cell = rtable_getcell(rt, target);
+ if (cell->target != NULL)
+ error("%S: rule redefinition", target);
+ if ((double)rt->cells_occupied / hash_table_size >= 0.7)
+ error("rtable: threshold reached");
+ if (alloc_name) {
+ cell->target = malloc(sizeof(*cell->target));
+ strdup(cell->target, target, 0);
+ } else {
+ cell->target = target;
+ }
+ cell->body = rule;
+ ++rt->cells_occupied;
+}
+
+static int rtable_find(struct rtable *rt, struct str *key,
+ struct rule_body **val)
+{
+ struct rule_cell *cell = rtable_getcell(rt, key);
+ if (cell->target == NULL)
+ return 0;
+ *val = cell->body;
+ return 1;
+}
+
+static char *strchr(char *s, char c)
+{
+ for (; *s; ++s) {
+ if (*s == c)
+ return s;
+ }
+ return NULL;
+}
+
+static char *mmap_file(char *fname)
+{
+ int fd = open(fname, O_RDONLY, 0);
+ struct stat st;
+ char *tmp;
+
+ if (fd == -1)
+ goto syserr;
+ if (fstat(fd, &st) == -1)
+ goto syserr;
+
+ if (st.size == 0) {
+ tmp = NULL;
+ goto end;
+ }
+
+ tmp = mmap(NULL, st.size + 1, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ if (tmp == (void*)-1)
+ goto syserr;
+
+end:
+ if (close(fd) == -1)
+ goto syserr;
+ return tmp;
+syserr:
+ syscall_error(fname);
+ return NULL; /* unreachable */
+}
+
+static int readline(struct file_stack *stack, struct line *line)
+{
+ char *tmp;
+ char *marker = NULL;
+ struct file *file;
+
+ if (stack->idx == 0)
+ return 0;
+ file = stack->buf + stack->idx - 1;
+ if (file->buf == NULL) {
+ file->buf = mmap_file(file->name);
+ if (file->buf == NULL) {
+ --stack->idx;
+ return readline(stack, line);
+ }
+ }
+
+ tmp = file->buf;
+
+ line->pos = (struct pos) { file->name, file->line };
+
+ for (; *tmp && *tmp != '\n'; ++tmp) {
+ if (*tmp == '#' && marker == NULL)
+ marker = tmp;
+ else if (tmp[0] == '\\' && tmp[1] == '\n') {
+ tmp[0] = tmp[1] = ' ';
+ ++tmp;
+ ++file->line;
+ }
+ }
+
+ if (*tmp == 0) {
+ if (tmp == file->buf) {
+ --stack->idx;
+ return readline(stack, line);
+ } else {
+ error("last line missing '\\n'");
+ }
+ }
+
+ *tmp = 0;
+ line->str.start = file->buf;
+ line->str.end = marker ? marker : tmp;
+
+ file->buf = tmp + 1;
+ ++file->line;
+
+ return 1;
+}
+
+/* Does not cross file end. We do not have to check if idx == 0, because
+ * idx is always >0, if readline did not return "end of input".
+ */
+static int nextline_starts_with_tab(struct file_stack *fstack)
+{
+ return *fstack->buf[fstack->idx-1].buf == '\t';
+}
+
+#define buf_static_init(buf, local_buf, size) \
+do { \
+ buf.start = local_buf; \
+ buf.end = buf.start + size; \
+ buf.cur = buf.start; \
+} while (0)
+
+static void buf_init(struct buf *buf)
+{
+ buf->start = mem_alloc(buf_size);
+ buf->end = buf->start + buf_size;
+ buf->cur = buf->start;
+}
+
+static void __buf_putchar(struct buf *buf, char c, int inc)
+{
+ if (buf->cur == buf->end)
+ error("buf_putchar: buffer resizing is not supported");
+ if (c == '\t')
+ c = ' ';
+ if (c == ' ' && buf->cur > buf->start && buf->cur[-1] == ' ')
+ return;
+ *buf->cur = c;
+ if (inc)
+ ++buf->cur;
+}
+
+static void buf_putchar(struct buf *buf, char c)
+{
+ __buf_putchar(buf, c, 1);
+}
+
+static void buf_put_silent_zero(struct buf *buf)
+{
+ __buf_putchar(buf, 0, 0);
+}
+
+static void buf_puts(struct buf *buf, struct str *str)
+{
+ char *tmp;
+ for (tmp = str->start; tmp != str->end; ++tmp)
+ buf_putchar(buf, *tmp);
+}
+
+static void buf_putsz(struct buf *buf, char *s)
+{
+ for (; *s; ++s)
+ buf_putchar(buf, *s);
+}
+
+static int strempty(struct str *str)
+{
+ return str->start == str->end;
+}
+
+static int strlen(struct str *str)
+{
+ return str->end - str->start;
+}
+
+static char *find_cparent(struct str *str, struct pos *pos)
+{
+ char *tmp;
+ int level = 0;
+ for (tmp = str->start + 1; tmp != str->end; ++tmp) {
+ if (*tmp == '(')
+ ++level;
+ if (*tmp == ')') {
+ if (level == 0)
+ return tmp;
+ else
+ --level;
+ }
+ }
+ errorp(pos, "missing ')'");
+ return NULL; /* unreachable */
+}
+
+enum {
+ id_variable,
+ id_subref,
+
+ id_prereqs,
+ id_first_prereq,
+ id_target,
+
+ id_func_class_start,
+ id_func_wildcard,
+ id_func_subst,
+ id_func_patsubst,
+ id_func_filter,
+ id_func_filter_out,
+
+ id_func_basename,
+ id_func_dir,
+
+ id_func_shell,
+ id_func_addprefix,
+ id_func_addsuffix,
+ id_func_sort,
+ id_func_class_end
+};
+
+struct eprofile {
+ char type;
+ struct str *body;
+ struct pos *pos;
+
+ struct str from;
+ struct str to;
+};
+
+static int is_space(char c)
+{
+ return c == ' ' || c == '\t';
+}
+
+static int valid_var_char(char c)
+{
+ return (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ ( c >= '0' && c <= '9') ||
+ c == '_' || c == '/' ||
+ c == '-' || c == '.';
+}
+
+static char *word_end(struct str *str)
+{
+ char *tmp;
+ for (tmp = str->start; tmp != str->end; ++tmp) {
+ if (is_space(*tmp))
+ return tmp;
+ if (!valid_var_char(*tmp))
+ return *tmp == ':' ? tmp : NULL;
+ }
+ return tmp;
+}
+
+#define ENTRY(fname) { CSTR(#fname), id_func_ ## fname }
+
+static struct { struct str fname; char type; } func_table[] = {
+ ENTRY(wildcard),
+ ENTRY(subst),
+ ENTRY(patsubst),
+ ENTRY(filter),
+ { CSTR("filter-out"), id_func_filter_out },
+ ENTRY(basename),
+ ENTRY(dir),
+ ENTRY(sort),
+ ENTRY(shell),
+ ENTRY(addprefix),
+ ENTRY(addsuffix)
+};
+#undef ENTRY
+
+static int is_special_var(struct eprofile *prof, struct str *word)
+{
+ if (streq(word, &CSTR("^"))) {
+ prof->type = id_prereqs;
+ return 1;
+ } else if (streq(word, &CSTR("<"))) {
+ prof->type = id_first_prereq;
+ return 1;
+ } else if (streq(word, &CSTR("@"))) {
+ prof->type = id_target;
+ return 1;
+ }
+ return 0;
+}
+
+static void check_var_form(struct str *word, struct str *all,
+ struct pos *pos)
+{
+ if (word->end == all->end)
+ return;
+ errorp(pos, "(%S): variable name must be a single word", all);
+}
+
+static void parse_subref(struct eprofile *prof, struct str *vname,
+ struct str *all)
+{
+ char *tmp;
+ prof->type = id_subref;
+
+ all->start = vname->end + 1;
+ prof->from.start = all->start;
+ for (tmp = all->start; tmp != all->end && *tmp != '='; ++tmp)
+ ;
+ if (tmp == all->end)
+ errorp(prof->pos, "%s: no '=' is subref expr", vname);
+ prof->from.end = tmp;
+ prof->to.start = tmp + 1;
+ prof->to.end = all->end;
+
+ *all = *vname;
+ prof->body = all;
+}
+
+static void parse_profile(struct eprofile *prof, struct str *str,
+ struct full_rule *rule)
+{
+ int i;
+ char *wend;
+ struct str first_word;
+
+ if (rule != NULL && is_special_var(prof, str))
+ return;
+
+ wend = word_end(str);
+ if (wend == NULL) {
+ errorp(prof->pos,
+ "invalid symbol during first word parsing");
+ }
+ first_word = (struct str) { str->start, wend };
+
+ if (wend != str->end && *wend == ':') {
+ parse_subref(prof, &first_word, str);
+ return;
+ }
+
+ for (i = 0; i < sizeof(func_table) / sizeof(func_table[0]); ++i) {
+ if (streq(&first_word, &func_table[i].fname)) {
+ prof->type = func_table[i].type;
+ str->start = first_word.end;
+ prof->body = str;
+ return;
+ }
+ }
+ prof->type = id_variable;
+ check_var_form(&first_word, str, prof->pos);
+ prof->body = str;
+}
+
+static void __eval(struct eval_aux *aux, struct str *str);
+
+static void swap_buffers(struct eval_aux *aux)
+{
+ struct buf *tmp = aux->mainbuf;
+ aux->mainbuf = aux->auxbuf;
+ aux->auxbuf = tmp;
+}
+
+static void eval_wrapper(struct eval_aux *aux, struct str *str,
+ int swap, int zero_terminate)
+{
+ struct str tmp = *str;
+ ++aux->level;
+ if (swap)
+ swap_buffers(aux);
+ __eval(aux, &tmp);
+ if (zero_terminate)
+ buf_put_silent_zero(aux->mainbuf);
+ if (swap)
+ swap_buffers(aux);
+ --aux->level;
+}
+
+static int adjust_vname(struct buf *buf, struct full_rule *rule,
+ struct str *vname)
+{
+ if (rule == NULL)
+ return 0;
+ buf_puts(buf, rule->target);
+ buf_putchar(buf, '#');
+ buf_puts(buf, vname);
+ return 1;
+}
+
+static void expand_variable(struct eval_aux *aux, struct eprofile *prof)
+{
+ char localbuf[buf_static_size];
+ struct buf buf;
+ struct str *varval = NULL;
+
+ buf_static_init(buf, localbuf, buf_static_size);
+ if (adjust_vname(&buf, aux->rule, prof->body)) {
+ struct str name = (struct str) { buf.start, buf.cur };
+ varval = vtable_find(&aux->world->vtable, &name);
+ }
+
+ if (varval == NULL)
+ varval = vtable_find(&aux->world->vtable, prof->body);
+
+ if (varval == NULL)
+ return;
+ eval_wrapper(aux, varval, 0, 0);
+}
+
+static void expand_first_prereq(struct eval_aux *aux,
+ struct eprofile *prof)
+{
+ if (aux->rule->rest->prereqs.len == 0)
+ return;
+ buf_puts(aux->mainbuf, &aux->rule->rest->prereqs.buf[0]);
+}
+
+static void expand_prereqs(struct eval_aux *aux,
+ struct eprofile *prof)
+{
+ int i;
+ struct strbuf *prereqs = &aux->rule->rest->prereqs;
+ for (i = 0; i < prereqs->len; ++i) {
+ buf_puts(aux->mainbuf, &prereqs->buf[i]);
+ if (i + 1 != prereqs->len)
+ buf_putchar(aux->mainbuf, ' ');
+ }
+}
+
+static void trim_front(struct str *str);
+static void trim_back(struct str *str);
+static void trim(struct str *str);
+
+static int extract_word(struct str *str, struct str *word)
+{
+ char *tmp;
+
+ trim_front(str);
+ if (strempty(str))
+ return 0;
+
+ tmp = str->start;
+ word->start = tmp;
+ for (; tmp != str->end && !is_space(*tmp); ++tmp)
+ ;
+ word->end = tmp;
+
+ str->start = tmp;
+ return 1;
+}
+
+struct matched {
+ int front_remains;
+ int have_wildcard;
+ struct str wildcard;
+};
+
+static char *contains_char(struct str *str, char c);
+
+static int matches_wildcard(struct matched *m, struct str *pat,
+ struct str *str, char *wildcard)
+{
+ int ret;
+ int diff = wildcard - pat->start;
+ ret = memeq(str->start, pat->start, diff);
+ if (ret == 0)
+ return 0;
+ if (m)
+ m->wildcard.start = str->start + diff;
+ diff = pat->end - (wildcard + 1);
+ ret = memeq(str->end - diff, pat->end - diff, diff);
+ if (ret == 0)
+ return 0;
+ if (m) {
+ m->wildcard.end = str->end - diff;
+ m->have_wildcard = 1;
+ }
+ return 1;
+}
+
+static int match_str(struct str *pat, struct str *str,
+ struct matched *m)
+{
+ char *tmp;
+
+ if (m) {
+ m->front_remains = 0;
+ m->have_wildcard = 0;
+ }
+
+ tmp = contains_char(pat, '%');
+ if (tmp != NULL) {
+ if(!matches_wildcard(m, pat, str, tmp))
+ return 0;
+ } else {
+ int ret;
+ int diff = strlen(str) - strlen(pat);
+ if (diff < 0)
+ return 0;
+ ret = memeq(str->start + diff, pat->start, strlen(pat));
+ if (ret == 0)
+ return 0;
+ if (m)
+ m->front_remains = diff;
+ }
+ return 1;
+}
+
+static int pmatches(struct eprofile *prof, struct str *value,
+ struct matched *m)
+{
+ return match_str(&prof->from, value, m);
+}
+
+static void modify(struct eval_aux *aux, struct eprofile *prof,
+ struct str *value, struct matched *m)
+{
+ int i;
+ char *tmp;
+ struct str *template = &prof->to;
+ for (i = 0; i < m->front_remains; ++i)
+ buf_putchar(aux->mainbuf, value->start[i]);
+ for (tmp = template->start; tmp != template->end; ++tmp) {
+ if (*tmp == '%' && m->have_wildcard)
+ buf_puts(aux->mainbuf, &m->wildcard);
+ else
+ buf_putchar(aux->mainbuf, *tmp);
+ }
+}
+
+static void subref(struct eval_aux *aux, struct eprofile *prof,
+ struct str *value)
+{
+ int first = 1;
+ for (;; first = 0) {
+ struct str word;
+ struct matched matched;
+
+ if (!extract_word(value, &word))
+ break;
+
+ if (!first)
+ buf_putchar(aux->mainbuf, ' ');
+
+ if (pmatches(prof, &word, &matched))
+ modify(aux, prof, &word, &matched);
+ else
+ buf_puts(aux->mainbuf, &word);
+ }
+}
+
+static void expand_subref(struct eval_aux *aux, struct eprofile *prof)
+{
+ char *savedpos = aux->auxbuf->cur;
+ char *tmp;
+ struct str value;
+ prof->type = id_variable;
+
+ swap_buffers(aux);
+ expand_variable(aux, prof);
+ swap_buffers(aux);
+ value = (struct str) { savedpos, aux->auxbuf->cur };
+
+ tmp = aux->auxbuf->cur;
+ eval_wrapper(aux, &prof->from, 1, 0);
+ prof->from = (struct str) { tmp, aux->auxbuf->cur };
+
+ tmp = aux->auxbuf->cur;
+ eval_wrapper(aux, &prof->to, 1, 0);
+ prof->to = (struct str) { tmp, aux->auxbuf->cur };
+
+ subref(aux, prof, &value);
+
+ aux->auxbuf->cur = savedpos;
+}
+
+static void expand_target(struct eval_aux *aux)
+{
+ eval_wrapper(aux, aux->rule->target, 0, 0);
+}
+
+static int extract_farg(struct str *src, struct str *arg)
+{
+ int level = 0;
+ char *tmp;
+
+ arg->start = src->start;
+ for (tmp = src->start; tmp != src->end; ++tmp) {
+ if (*tmp == '(')
+ ++level;
+ else if (*tmp == ')')
+ --level;
+ else if (*tmp == ',' && level == 0)
+ break;
+
+ }
+ arg->end = tmp;
+ if (tmp == src->end)
+ return 0;
+ src->start = tmp + 1;
+ return 1;
+}
+
+static void basename_word(struct str *res, struct str *word)
+{
+ char *tmp = word->end - 1;
+ char *dot = NULL;
+ for (; tmp >= word->start; --tmp) {
+ if (*tmp == '.' && dot == NULL) {
+ dot = tmp;
+ break;
+ }
+ if (*tmp == '/')
+ break;
+ }
+ *res = (struct str) {
+ word->start,
+ dot ? dot : word->end
+ };
+}
+
+static void dir_word(struct str *res, struct str *word)
+{
+ char *tmp = word->end - 1;
+ char *slash = NULL;
+
+ for (; tmp >= word->start; --tmp) {
+ if (*tmp == '/') {
+ slash = tmp;
+ break;
+ }
+ }
+ if (slash == NULL)
+ *res = CSTR("./");
+ else
+ *res = (struct str) { word->start, slash + 1 };
+}
+
+static void extract_args(struct str *args, struct str *str, int argnum,
+ char *func, struct pos *pos)
+{
+ int i;
+ for (i = 0;; ++i) {
+ int ret = extract_farg(str, args + i);
+ if (i == argnum - 1) {
+ if (ret == 1)
+ goto argcount_err;
+ else {
+ trim(args + i);
+ return;
+ }
+ }
+ if (ret == 0)
+ goto argcount_err;
+ trim(args + i);
+ }
+argcount_err:
+ errorp(pos, "%s expects %d argument%s", func, argnum,
+ argnum > 1 ? "s" : "");
+}
+
+enum {
+ mode_basename,
+ mode_dir,
+};
+
+static void expand_basename_or_dir(struct eval_aux *aux, int mode,
+ struct eprofile *prof)
+{
+ struct str arg;
+ int first = 1;
+
+ char *func = mode == mode_basename ? "basename" : "dir";
+ extract_args(&arg, prof->body, 1, func, aux->pos);
+
+ for (;; first = 0) {
+ struct str word, res;
+
+ if(!extract_word(&arg, &word))
+ break;
+
+ if (mode == mode_basename)
+ basename_word(&res, &word);
+ else
+ dir_word(&res, &word);
+ if (!first)
+ buf_putchar(aux->mainbuf, ' ');
+ buf_puts(aux->mainbuf, &res);
+ }
+}
+
+static int arg_is_single_word(struct str *str)
+{
+ struct str word;
+
+ if (!extract_word(str, &word))
+ return 0;
+ trim_front(str);
+
+ if (!strempty(str))
+ return 0;
+ *str = word;
+ return 1;
+}
+
+enum {
+ mode_prefix,
+ mode_suffix
+};
+
+static void expand_add(struct eval_aux *aux, int mode,
+ struct eprofile *prof)
+{
+ struct str args[2];
+ int first = 1;
+
+ char *func = mode == mode_prefix ? "addprefix" : "addsuffix";
+ extract_args(args, prof->body, 2, func, aux->pos);
+
+ if (!arg_is_single_word(args))
+ errorp(aux->pos, "%s: accepts a single prefix", func);
+
+ for (;; first = 0) {
+ struct str word;
+
+ if (!extract_word(args + 1, &word))
+ break;
+
+ if (!first)
+ buf_putchar(aux->mainbuf, ' ');
+ if (mode == mode_prefix) {
+ buf_puts(aux->mainbuf, args);
+ buf_puts(aux->mainbuf, &word);
+ } else {
+ buf_puts(aux->mainbuf, &word);
+ buf_puts(aux->mainbuf, args);
+ }
+ }
+ return;
+}
+
+enum {
+ mode_filter,
+ mode_filter_out
+};
+
+static int passes_filter(struct str *str, struct strbuf *pats, int mode)
+{
+ int ret_when_matches = mode == mode_filter ? 1 : 0;
+ int i;
+ for (i = 0; i < pats->len; ++i) {
+ if (match_str(pats->buf + i, str, NULL))
+ return ret_when_matches;
+ }
+ return !ret_when_matches;
+}
+
+static void strbuf_init(struct strbuf *buf);
+static void strbuf_push(struct strbuf *buf, struct str *str);
+
+static void collect_words(struct strbuf *buf, struct str *words)
+{
+ strbuf_init(buf);
+
+ for (;;) {
+ struct str word;
+ int ret = extract_word(words, &word);
+ if (ret == 0)
+ break;
+ strbuf_push(buf, &word);
+ }
+}
+
+static void filter_words(struct str *words, struct strbuf *pats,
+ struct eval_aux *aux, int mode)
+{
+ int first = 1;
+ for (;;) {
+ struct str word;
+ int ret = extract_word(words, &word);
+ if (ret == 0)
+ break;
+ if (!passes_filter(&word, pats, mode))
+ continue;
+ if (!first)
+ buf_putchar(aux->mainbuf, ' ');
+ buf_puts(aux->mainbuf, &word);
+ first = 0;
+ }
+}
+
+static void expand_filter(struct eval_aux *aux, int mode,
+ struct eprofile *prof)
+{
+ struct str args[2];
+ struct strbuf pats;
+
+ char *func = mode == mode_filter ? "filter" : "filter-out";
+ extract_args(args, prof->body, 2, func, aux->pos);
+
+ heap_offset_save();
+ collect_words(&pats, args);
+ filter_words(args + 1, &pats, aux, mode);
+ heap_offset_restore();
+}
+
+static void merge(struct str *to, struct str *from, int n)
+{
+ int mid = n/2;
+ int i, j, k;
+
+ for (k = 0, i = 0, j = mid; i < mid && j < n; ++k) {
+ if (strge(from + j, from + i)) {
+ to[k] = from[i];
+ ++i;
+ } else {
+ to[k] = from[j];
+ ++j;
+ }
+ }
+ if (i < mid) {
+ for (; i < mid; ++i, ++k)
+ to[k] = from[i];
+ } else {
+ for (; j < n; ++j, ++k)
+ to[k] = from[j];
+ }
+}
+
+static void mergesort(struct str *array, struct str *tmp,
+ int n)
+{
+ if (n < 2)
+ return;
+ mergesort(tmp, array, n/2);
+ mergesort(tmp + n/2, array + n/2, n - n/2);
+ merge(array, tmp, n);
+}
+
+static void sort(struct strbuf *words)
+{
+ struct str *tmp = malloc(sizeof(*tmp) * words->len);
+ int i;
+ for (i = 0; i < words->len; ++i)
+ tmp[i] = words->buf[i];
+
+ mergesort(words->buf, tmp, words->len);
+}
+
+static void uniq(struct strbuf *words)
+{
+ int i, j;
+ struct str *array = words->buf;
+ for (i = 0, j = 0; i < words->len; ++i) {
+ if (j > 0 && streq(&array[i], &array[j-1]))
+ continue;
+ array[j] = array[i];
+ ++j;
+ }
+ words->len = j;
+}
+
+static void expand_sort(struct eval_aux *aux, struct eprofile *prof)
+{
+ struct strbuf words;
+ struct str arg;
+ int i;
+
+ heap_offset_save();
+ extract_args(&arg, prof->body, 1, "sort", aux->pos);
+ collect_words(&words, &arg);
+
+ sort(&words);
+ uniq(&words);
+
+ for (i = 0; i < words.len; ++i) {
+ if (i > 0)
+ buf_putchar(aux->mainbuf, ' ');
+ buf_puts(aux->mainbuf, &words.buf[i]);
+ }
+
+ heap_offset_restore();
+}
+
+static void save_shell_stdout(int fd, struct buf *buf)
+{
+ char tmpbuf[buf_static_size];
+ struct str tmp = { buf->cur };
+ int skip_spaces = 1;
+ for (;;) {
+ int i;
+ int ret = read(fd, tmpbuf, buf_static_size);
+ if (ret == -1)
+ syscall_error("shell output read");
+ if (ret == 0)
+ break;
+ for (i = 0; i < ret; ++i) {
+ char c = tmpbuf[i];
+ if (tmpbuf[i] == '\n')
+ c = ' ';
+
+ if (is_space(c)) {
+ if (skip_spaces)
+ continue;
+ } else
+ skip_spaces = 1;
+ buf_putchar(buf, c);
+ }
+ }
+ tmp.end = buf->cur;
+ trim_back(&tmp);
+ buf->cur = tmp.end;
+ close(fd);
+}
+static void check_status(int status, char *prx);
+
+static void expand_shell(struct eval_aux *aux, struct eprofile *prof)
+{
+ struct str arg;
+ extract_args(&arg, prof->body, 1, "shell", aux->pos);
+ int pipefds[2];
+ int status, pid;
+
+ trim_front(&arg);
+ if (pipe(pipefds) == -1)
+ syscall_error("pipe");
+ pid = fork();
+ if (pid == -1)
+ syscall_error("fork");
+ if (pid == 0) {
+ char *argv[] = { "/bin/dash", "-c", arg.start, NULL };
+ if (dup2(pipefds[1], 1) == -1)
+ syscall_error("dup2");
+ close(pipefds[1]);
+ close(pipefds[0]);
+ execve(argv[0], argv, aux->world->envp);
+ exit(255);
+ }
+ close(pipefds[1]);
+ save_shell_stdout(pipefds[0], aux->mainbuf);
+ wait4(pid, &status, 0, NULL);
+ check_status(status, "shell: ");
+}
+
+static void subst(struct buf *buf, struct str *from, struct str *to,
+ struct str *word)
+{
+ int i = 0;
+ int fromlen = strlen(from);
+ int n = strlen(word);
+ while (i <= n - fromlen) {
+ if (memeq(word->start + i, from->start, fromlen)) {
+ buf_puts(buf, to);
+ i += fromlen;
+ } else {
+ buf_putchar(buf, word->start[i]);
+ ++i;
+ }
+ }
+ for (; i < n; ++i)
+ buf_putchar(buf, word->start[i]);
+}
+
+static void expand_subst(struct eval_aux *aux, struct eprofile *prof)
+{
+ struct str args[3];
+ int first = 1;
+
+ extract_args(args, prof->body, 3, "subst", aux->pos);
+
+ if (!arg_is_single_word(args))
+ goto single_word_err;
+ if (!arg_is_single_word(args + 1))
+ goto single_word_err;
+
+ for (;; first = 0) {
+ struct str word;
+ int ret = extract_word(args + 2, &word);
+ if (ret == 0)
+ break;
+ if (!first)
+ buf_putchar(aux->mainbuf, ' ');
+ subst(aux->mainbuf, args, args + 1, &word);
+ }
+ return;
+single_word_err:
+ errorp(aux->pos, "subst: from/to must to single words");
+}
+
+static int matches(struct str *pat, struct str *str,
+ struct str *wildcard);
+
+static void patsubst(struct buf *buf, struct str *from,
+ struct str *to, struct str *words)
+{
+ int first = 1;
+ for (;; first = 0) {
+ struct str word, wildcard;
+
+ if (extract_word(words, &word) == 0)
+ break;
+ if (!first)
+ buf_putchar(buf, ' ');
+ if (matches(from, &word, &wildcard)) {
+ char *tmp;
+ for (tmp = to->start; tmp != to->end; ++tmp) {
+ if (*tmp == '%')
+ buf_puts(buf, &wildcard);
+ else
+ buf_putchar(buf, *tmp);
+ }
+ } else {
+ buf_puts(buf, &word);
+ }
+ }
+}
+
+static void expand_patsubst(struct eval_aux *aux, struct eprofile *prof)
+{
+ struct str args[3];
+ extract_args(args, prof->body, 3, "patsubst", aux->pos);
+
+ if (!arg_is_single_word(args))
+ goto single_word_err;
+ if (!arg_is_single_word(args + 1))
+ goto single_word_err;
+ if (!contains_char(args, '%'))
+ goto no_wildcard_err;
+ if (!contains_char(args + 1, '%'))
+ goto no_wildcard_err;
+
+ patsubst(aux->mainbuf, args, args + 1, args + 2);
+ return;
+
+single_word_err:
+ errorp(aux->pos, "patsubst: from/to must be single words");
+no_wildcard_err:
+ errorp(aux->pos, "patsubst: from/to must contain '%'");
+}
+
+enum {
+ max_limbs = 64
+};
+
+struct pathbuf {
+ struct str limbuf[max_limbs];
+ int limbs;
+
+ char buf[path_max_len];
+ char *cur;
+
+ int add_space;
+ struct pos *pos;
+
+ struct buf *dest;
+};
+
+static void __pbuf_putchar(struct pathbuf *pbuf, char c, int inc)
+{
+ if (pbuf->cur == pbuf->buf + path_max_len)
+ error("pathbuf: no more space");
+ *pbuf->cur = c;
+ if (inc)
+ ++pbuf->cur;
+}
+
+static void pbuf_putchar(struct pathbuf *pbuf, char c)
+{
+ __pbuf_putchar(pbuf, c, 1);
+}
+
+static void pbuf_put_silent_zero(struct pathbuf *pbuf)
+{
+ __pbuf_putchar(pbuf, 0, 0);
+}
+
+static void add_slash(struct pathbuf *pbuf)
+{
+ if (pbuf->cur - pbuf->buf > 1 || pbuf->cur[-1] == '.')
+ pbuf_putchar(pbuf, '/');
+}
+
+static void pbuf_putlimb(struct pathbuf *pbuf, struct str *limb)
+{
+ char *tmp;
+ add_slash(pbuf);
+ for (tmp = limb->start; tmp != limb->end; ++tmp)
+ pbuf_putchar(pbuf, *tmp);
+}
+
+static void pbuf_putlimbz(struct pathbuf *pbuf, char *limb)
+{
+ add_slash(pbuf);
+ for (; *limb; ++limb)
+ pbuf_putchar(pbuf, *limb);
+}
+
+static void skip_slashes(struct str *path)
+{
+ char *tmp = path->start;
+ for (; tmp != path->end && *tmp == '/'; ++tmp)
+ ;
+ path->start = tmp;
+}
+
+static void init_beginning(struct pathbuf *pbuf, struct str *path)
+{
+ if (path->start[0] == '/') {
+ skip_slashes(path);
+ pbuf_putchar(pbuf, '/');
+ } else {
+ pbuf_putchar(pbuf, '.');
+ }
+}
+
+static struct str *get_word(struct pathbuf *pbuf)
+{
+ if (pbuf->limbs == max_limbs)
+ error("pathbuf: too deep nesting");
+ return pbuf->limbuf + pbuf->limbs;
+}
+
+static int eat_limb(struct pathbuf *pbuf, struct str *path)
+{
+ struct str *dest;
+ char *tmp;
+ if (strempty(path))
+ return 0;
+ dest = get_word(pbuf);
+ dest->start = path->start;
+ for (tmp = path->start; tmp != path->end && *tmp != '/'; ++tmp)
+ ;
+ dest->end = tmp;
+ path->start = tmp;
+ skip_slashes(path);
+ ++pbuf->limbs;
+ return 1;
+}
+
+static void parse_path(struct pathbuf *pbuf, struct str *path)
+{
+ init_beginning(pbuf, path);
+ while (eat_limb(pbuf, path))
+ ;
+}
+
+static int is_pattern(struct str *str)
+{
+ char *tmp = str->start;
+ for (; tmp != str->end; ++tmp) {
+ if (*tmp == '*' || *tmp == '?')
+ return 1;
+ }
+ return 0;
+}
+
+static int is_special(char *dent)
+{
+ return dent[0] == '.' &&
+ (dent[1] == 0 || (dent[1] == '.' && dent[2] == 0));
+}
+
+static int __match(char *pat, char *str, struct pos *pos)
+{
+ for (; *pat; ++pat) {
+ if (*pat == '*') {
+ char *sc;
+ for (sc = str; ; ++sc) {
+ if (__match(pat + 1, sc, pos))
+ return 1;
+ if (*sc == 0)
+ return 0;
+ }
+ } else if (*pat == '?') {
+ if (*str)
+ ++str;
+ else
+ return 0;
+ } else if (*pat == '[') {
+ int ret = 0;
+ for (++pat; *pat && *pat != ']'; ++pat) {
+ if (*pat == *str)
+ ret = 1;
+ }
+ if (*pat == 0)
+ errorp(pos, "wildcard missing ']'");
+ if (ret == 0)
+ return 0;
+ ++str;
+ } else {
+ if (*pat != *str)
+ return 0;
+ ++str;
+ }
+ }
+ return *str == 0;
+}
+
+static int match_pattern(struct str *pat, char *s, struct pos *pos)
+{
+ char c = *pat->end;
+ int ret;
+
+ *pat->end = 0;
+
+ ret = __match(pat->start, s, pos);
+
+ *pat->end = c;
+ return ret;
+}
+
+static void follow_path(struct pathbuf *pbuf, int start);
+
+static void iterate(struct pathbuf *pbuf, int idx)
+{
+ struct dir dir;
+
+ heap_offset_save();
+
+ pbuf_put_silent_zero(pbuf);
+ if (opendir(&dir, pbuf->buf) == 0)
+ goto end;
+
+ char *cur = pbuf->cur;
+ for (;;) {
+ struct dirent *dent = readdir(&dir);
+ pbuf->cur = cur;
+
+ if (dent == NULL)
+ break;
+ if (is_special(dent->name))
+ continue;
+
+ int res = match_pattern(pbuf->limbuf + idx, dent->name,
+ pbuf->pos);
+ if (res) {
+ pbuf_putlimbz(pbuf, dent->name);
+ follow_path(pbuf, idx + 1);
+ }
+ }
+ closedir(&dir);
+end:
+ heap_offset_restore();
+}
+
+static void commit(struct buf *dest, char *path)
+{
+ char *tmp = path[0] == '/' ? path : path + 2;
+ buf_putsz(dest, tmp);
+}
+
+static int exists(char *path);
+
+static void follow_path(struct pathbuf *pbuf, int start)
+{
+ int i;
+ for (i = start; i < pbuf->limbs; ++i) {
+ if (is_pattern(pbuf->limbuf + i)) {
+ iterate(pbuf, i);
+ return;
+ } else {
+ pbuf_putlimb(pbuf, pbuf->limbuf + i);
+ }
+ }
+ pbuf_put_silent_zero(pbuf);
+ if (exists(pbuf->buf)) {
+ if (pbuf->add_space)
+ buf_putchar(pbuf->dest, ' ');
+ commit(pbuf->dest, pbuf->buf);
+ pbuf->add_space = 1;
+ }
+}
+
+static int wexpand(struct buf *buf, struct str *path, int add_space,
+ struct pos *pos)
+{
+ static struct pathbuf pbuf;
+ pbuf.limbs = 0;
+ pbuf.cur = pbuf.buf;
+ pbuf.add_space = add_space;
+ pbuf.dest = buf;
+ pbuf.pos = pos;
+
+ parse_path(&pbuf, path);
+ follow_path(&pbuf, 0);
+ return pbuf.add_space;
+}
+
+static void expand_wildcard(struct eval_aux *aux, struct eprofile *prof)
+{
+ struct str arg;
+ int first = 1;
+
+ extract_args(&arg, prof->body, 1, "wildcard", aux->pos);
+ for (;;) {
+ struct str word;
+ if (extract_word(&arg, &word) == 0)
+ break;
+ int ret = wexpand(aux->mainbuf, &word, !first,
+ aux->pos);
+ if (ret && first)
+ first = 0;
+ }
+}
+
+static void expand_func(struct eval_aux *aux, struct eprofile *prof)
+{
+ char *savedpos = aux->auxbuf->cur;
+ eval_wrapper(aux, prof->body, 1, 1);
+ prof->body = &(struct str) { savedpos, aux->auxbuf->cur };
+
+ switch(prof->type) {
+ case id_func_basename:
+ expand_basename_or_dir(aux, mode_basename, prof);
+ break;
+ case id_func_dir:
+ expand_basename_or_dir(aux, mode_dir, prof);
+ break;
+ case id_func_addprefix:
+ expand_add(aux, mode_prefix, prof);
+ break;
+ case id_func_addsuffix:
+ expand_add(aux, mode_suffix, prof);
+ break;
+ case id_func_filter:
+ expand_filter(aux, mode_filter, prof);
+ break;
+ case id_func_filter_out:
+ expand_filter(aux, mode_filter_out, prof);
+ break;
+ case id_func_sort:
+ expand_sort(aux, prof);
+ break;
+ case id_func_shell:
+ expand_shell(aux, prof);
+ break;
+ case id_func_subst:
+ expand_subst(aux, prof);
+ break;
+ case id_func_patsubst:
+ expand_patsubst(aux, prof);
+ break;
+ case id_func_wildcard:
+ expand_wildcard(aux, prof);
+ break;
+ default:
+ error("func id=%d not implemented yet", prof->type);
+ }
+ aux->auxbuf->cur = savedpos;
+}
+
+static void __expand(struct eval_aux *aux, struct eprofile *prof)
+{
+ switch(prof->type) {
+ case id_variable:
+ expand_variable(aux, prof);
+ break;
+ case id_subref:
+ expand_subref(aux, prof);
+ break;
+ case id_target:
+ expand_target(aux);
+ break;
+ case id_first_prereq:
+ expand_first_prereq(aux, prof);
+ break;
+ case id_prereqs:
+ expand_prereqs(aux, prof);
+ break;
+ default:
+ expand_func(aux, prof);
+ break;
+ }
+}
+
+static void expand(struct eval_aux *aux, struct str *str)
+{
+ struct eprofile profile;
+ struct str estr;
+
+ if (strempty(str))
+ errorp(aux->pos, "stray '$'");
+
+ if (*str->start == '(') {
+ char *end = find_cparent(str, aux->pos);
+
+ ++str->start;
+ estr = (struct str) { str->start, end };
+ str->start = end + 1;
+ } else {
+ estr = (struct str) { str->start, str->start + 1 };
+ ++str->start;
+ }
+ profile.pos = aux->pos;
+ parse_profile(&profile, &estr, aux->rule);
+ __expand(aux, &profile);
+}
+
+static int stop_sym(char c)
+{
+ return c == '=' || c == '+' || c == ':';
+}
+
+static void __eval(struct eval_aux *aux, struct str *str)
+{
+ while (str->start != str->end) {
+ if (stop_sym(*str->start) && aux->level == 0 &&
+ aux->rule == NULL)
+ {
+ return;
+ } else if (*str->start == '$') {
+ ++str->start;
+ expand(aux, str);
+ } else {
+ buf_putchar(aux->mainbuf, *str->start);
+ ++str->start;
+ }
+ }
+}
+
+static struct buf *evict_buf(struct buf *buf)
+{
+ static struct buf tmp;
+ tmp = *buf;
+ tmp.end = tmp.cur;
+ buf->start = buf->cur;
+ return &tmp;
+}
+
+static struct buf *eval(struct world *world, struct line *line,
+ struct full_rule *rule)
+{
+ static char localbuf2[buf_size];
+ static struct buf buffers[2] = {
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL }
+ };
+ struct eval_aux aux;
+
+ if (buffers[0].start == NULL)
+ buf_init(buffers);
+ else
+ buffers[0].cur = buffers[0].start;
+ buf_static_init(buffers[1], localbuf2, buf_size);
+
+ aux = (struct eval_aux) {
+ 0,
+ &line->pos,
+ rule,
+ buffers, buffers + 1,
+ world
+ };
+ __eval(&aux, &line->str);
+ buf_putchar(buffers, ' ');
+
+ return rule == NULL ? evict_buf(buffers) : buffers;
+}
+
+static void trim_front(struct str *str)
+{
+ for (; str->start != str->end; ++str->start) {
+ if (!is_space(*str->start))
+ break;
+ }
+}
+
+static void trim_back(struct str *str)
+{
+ for (; str->start != str->end; --str->end) {
+ if (!is_space(str->end[-1]))
+ break;
+ }
+}
+
+static void trim(struct str *str)
+{
+ trim_front(str);
+ trim_back(str);
+}
+
+static int valid_var_name(struct str *vname)
+{
+ char *tmp;
+ for (tmp = vname->start; tmp != vname->end; ++tmp) {
+ if (!valid_var_char(*tmp))
+ return 0;
+ }
+ return 1;
+}
+
+static void str_combine(struct str *res, struct str *s1, struct str *s2)
+{
+ int len1, len2, nlen;
+
+ if (s1 == NULL) {
+ *res = *s2;
+ return;
+ }
+ len1 = strlen(s1);
+ len2 = strlen(s2);
+ nlen = len1 + len2 + 1;
+
+ res->start = malloc(nlen);
+ res->end = res->start + nlen;
+
+ memcpy(res->start, s1->start, len1);
+ res->start[len1] = ' ';
+ memcpy(res->start + len1 + 1, s2->start, len2);
+}
+
+enum {
+ svmode_set,
+ svmode_append
+};
+
+static void make_full_vname(struct buf *dest, struct str *prx,
+ struct str *vname)
+{
+ dest->cur = dest->start;
+ buf_puts(dest, prx);
+ buf_putchar(dest, '#');
+ buf_puts(dest, vname);
+}
+
+struct variable {
+ struct str name;
+ struct strbuf *goals;
+ struct str *value;
+ char scope;
+
+ struct pos *pos;
+ struct vtable *vt;
+};
+
+static int check_policy(struct variable *var, struct str *name)
+{
+ struct vt_cell *cell = vtable_getcell(var->vt, name);
+
+ return !(cell->scope == vs_cmdargs && var->scope == vs_makefile);
+}
+
+static void set_single_var(struct variable *var, struct str *goal, int mode)
+{
+ static char localbuf[buf_static_size];
+ struct buf buf;
+ struct str fullname;
+ /* TODO: Why do we need to alloc for goal specific assignments */
+ int alloc = goal ? 1 : 0;
+
+ buf_static_init(buf, localbuf, buf_static_size);
+
+ if (goal != NULL) {
+ make_full_vname(&buf, goal, &var->name);
+ fullname = (struct str) { buf.start, buf.cur };
+ } else {
+ fullname = var->name;
+ }
+
+ if (mode == svmode_set) {
+ if (check_policy(var, &fullname) == 0)
+ return;
+ /* Double work, but I do not think we should even bother */
+ vtable_update(var->vt, &fullname, alloc, var->value, var->scope);
+ } else {
+ struct str newval;
+ struct str *oldval = vtable_find(var->vt, &fullname);
+ if (oldval == NULL)
+ oldval = vtable_find(var->vt, &var->name);
+ str_combine(&newval, oldval, var->value);
+ vtable_update(var->vt, &fullname, alloc, &newval, var->scope);
+ }
+}
+
+static void setvar(struct variable *var, int mode)
+{
+ trim(var->value);
+
+ if (var->goals != NULL) {
+ int i;
+ for (i = 0; i < var->goals->len; ++i)
+ set_single_var(var, &var->goals->buf[i], mode);
+ } else {
+ set_single_var(var, NULL, mode);
+ }
+}
+
+static int is_followed(struct line *line, char c)
+{
+ return strlen(&line->str) >= 2 && line->str.start[1] == c;
+}
+
+static void follows(struct line *line, char c)
+{
+ if (is_followed(line, c))
+ return;
+ errorp(&line->pos, "expected %c after %c", c, line->str.start[0]);
+}
+
+static void parse_assignment(struct world *world, struct strbuf *goals,
+ struct buf *buf, struct line *line, char scope)
+{
+ struct variable var = {
+ { buf->start, buf->cur },
+ goals,
+ &line->str,
+ scope,
+
+ &line->pos,
+ &world->vtable
+ };
+
+ trim(&var.name);
+ if (!valid_var_name(&var.name)) {
+ errorp(&line->pos, "[%S]: invalid variable name",
+ &var.name);
+ }
+ if (var.value->start[0] == '+') {
+ follows(line, '=');
+ var.value->start += 2;
+ setvar(&var, svmode_append);
+ } else {
+ ++var.value->start;
+ setvar(&var, svmode_set);
+ }
+}
+
+#define SBUF_INIT(type) \
+static void type ## _init(struct type *buf) \
+{ \
+ buf->size = 1; \
+ buf->len = 0; \
+ buf->buf = malloc(sizeof(*buf->buf) * buf->size); \
+}
+
+SBUF_INIT(strbuf)
+SBUF_INIT(cmdbuf)
+
+static void strbuf_push(struct strbuf *buf, struct str *str)
+{
+ if (buf->len == buf->size) {
+ buf->size *= 2;
+ realloc(buf->buf, sizeof(*buf->buf) * buf->size);
+ }
+ buf->buf[buf->len] = *str;
+ ++buf->len;
+}
+
+static void cmdbuf_push(struct cmdbuf *buf, struct str *str,
+ struct pos *pos)
+{
+ if (buf->len == buf->size) {
+ buf->size *= 2;
+ realloc(buf->buf, sizeof(*buf->buf) * buf->size);
+ }
+ buf->buf[buf->len].str = *str;
+ buf->buf[buf->len].pos = *pos;
+ ++buf->len;
+}
+
+static int valid_target_char(char c)
+{
+ return valid_var_char(c) || c == '%';
+}
+
+static void save_word(struct strbuf *buf, struct str *str,
+ struct pos *pos)
+{
+ struct str word;
+ word.start = str->start;
+
+ /* No check for empty string here, because str is part of eval
+ * result. It won't be overwritten in this case. And at the end
+ * of eval we add ' ' for this function.
+ */
+ for (;; ++str->start) {
+ if (is_space(*str->start) || *str->start == ':')
+ break;
+ if (!valid_target_char(*str->start))
+ goto err;
+ }
+ word.end = str->start;
+ *str->start = 0;
+ ++str->start;
+
+ strbuf_push(buf, &word);
+ return;
+err:
+ errorp(pos, "%c: invalid target/prereq name symbol", *str->start);
+}
+
+static void parse_targets(struct strbuf *targets, struct buf *buf,
+ struct pos *pos)
+{
+ struct str str = { buf->start, buf->cur };
+ strbuf_init(targets);
+
+ for (;;) {
+ trim_front(&str);
+ if (strempty(&str))
+ break;
+ save_word(targets, &str, pos);
+ }
+ if (targets->len == 0)
+ errorp(pos, "zero targets before ':'");
+}
+
+static void parse_prereqs(struct rule_body *rule, struct buf *buf,
+ struct pos *pos)
+{
+ struct str str = { buf->start, buf->cur };
+ strbuf_init(&rule->prereqs);
+ int i = 0;
+ int normal = -1;
+
+ for (;;) {
+ trim_front(&str);
+ if (strempty(&str))
+ break;
+ if (*str.start == '|' && normal == -1) {
+ normal = i;
+ ++str.start;
+ trim_front(&str);
+ }
+ save_word(&rule->prereqs, &str, pos);
+ ++i;
+ }
+ rule->normal_prereq_count = normal == -1 ? i : normal;
+}
+
+static struct cmdbuf *parse_recipes(struct file_stack *fstack)
+{
+ struct cmdbuf *tmp = malloc(sizeof(*tmp));
+ cmdbuf_init(tmp);
+
+ while (nextline_starts_with_tab(fstack)) {
+ struct line line;
+ readline(fstack, &line);
+
+ trim(&line.str);
+ cmdbuf_push(tmp, &line.str, &line.pos);
+ }
+ return tmp;
+}
+
+static void save_first_target(struct world *world,
+ struct rule_details *rule)
+{
+ if (!strempty(&world->target) || rule->patrule ||
+ streq(&rule->targets.buf[0], &CSTR(".PRECIOUS")))
+ {
+ return;
+ }
+ world->target = *rule->targets.buf;
+}
+
+static char *contains_char(struct str *str, char c)
+{
+ char *tmp;
+ for (tmp = str->start; tmp != str->end; ++tmp) {
+ if (*tmp == c)
+ return tmp;
+ }
+ return NULL;
+}
+
+static int is_patrule(struct strbuf *targets, struct pos *pos)
+{
+ if (contains_char(&targets->buf[0], '%') == NULL)
+ return 0;
+ if (targets->len == 1)
+ return 1;
+ errorp(pos, "multiple targets not supported in patturn rules");
+ return -1; /* unreachable */
+}
+
+static void add_patrule(struct world *world, struct rule_details *rule)
+{
+ if (world->pat_rule_num == pat_rules_limit)
+ errorp(rule->pos, "pattern rules limit reached");
+ world->pat_rules[world->pat_rule_num] = (struct full_rule) {
+ &rule->targets.buf[0],
+ rule->body
+ };
+ ++world->pat_rule_num;
+}
+
+static void handle_normal(struct world *world, struct rule_details *rule,
+ struct buf *buf)
+{
+ int i;
+ parse_prereqs(rule->body, buf, rule->pos);
+ rule->body->recipes = parse_recipes(&world->fstack);
+
+ if (rule->patrule) {
+ add_patrule(world, rule);
+ return;
+ }
+
+ for (i = 0; i < rule->targets.len; ++i) {
+ if (streq(&rule->targets.buf[i], &CSTR(".PRECIOUS")))
+ continue;
+ rtable_add(&world->rtable, &rule->targets.buf[i], 0,
+ rule->body);
+ }
+}
+
+char *target_spec_in_patrules =
+ "target-specific var in pattern rules are not supported";
+
+static void handle_target_spec(struct world *world, struct buf *buf,
+ struct line *line, struct rule_details *rule)
+{
+ if (rule->patrule)
+ errorp(&line->pos, target_spec_in_patrules);
+ if (*line->str.start == ':')
+ errorp(&line->pos, "expected target-specific var");
+
+ parse_assignment(world, &rule->targets, buf, line, vs_makefile);
+}
+
+static void parse_rule(struct world *world, struct buf *buf,
+ struct line *line)
+{
+ struct rule_details rule;
+
+ ++line->str.start;
+ rule.body = malloc(sizeof(*rule.body));
+ rule.pos = &line->pos;
+
+ parse_targets(&rule.targets, buf, &line->pos);
+ rule.patrule = is_patrule(&rule.targets, &line->pos);
+
+ buf = eval(world, line, NULL);
+ if (strempty(&line->str))
+ handle_normal(world, &rule, buf);
+ else
+ handle_target_spec(world, buf, line, &rule);
+
+ save_first_target(world, &rule);
+}
+
+static void fstack_add(struct file_stack *stack, char *file,
+ struct pos *pos)
+{
+ if (stack->idx == file_stack_size) {
+ struct pos tpos = (struct pos) { "cmdarg", 0 };
+ errorp(pos ? pos : &tpos, "include nesting is too deep");
+ }
+ stack->buf[stack->idx] = (struct file) { file, NULL, 1 };
+ ++stack->idx;
+}
+
+static int is_include_directive(struct str *line)
+{
+ struct str linec;
+ if (line->end - line->start < 8)
+ return 0;
+ linec = (struct str) { line->start, line->start + 8 };
+ return streq(&CSTR("include "), &linec);
+}
+
+static void handle_include(struct world *world, struct line *line)
+{
+ line->str.start += 8;
+ *line->str.end = 0;
+ fstack_add(&world->fstack, line->str.start, &line->pos);
+}
+
+static int parse_toplev(struct world *world)
+{
+ struct line line;
+ struct buf *buf;
+
+ if (!readline(&world->fstack, &line))
+ return 0;
+ buf = eval(world, &line, NULL);
+
+ if (strempty(&line.str)) {
+ struct line tmp = { { buf->start, buf->cur }, line.pos };
+ trim(&tmp.str);
+ if (is_include_directive(&tmp.str)) {
+ handle_include(world, &tmp);
+ return 1;
+ }
+ if (!strempty(&tmp.str))
+ errorp(&tmp.pos, "missing separator");
+ } else if (*line.str.start == ':') {
+ parse_rule(world, buf, &line);
+ } else {
+ parse_assignment(world, NULL, buf, &line, vs_makefile);
+ }
+ return 1;
+}
+
+static void parse(struct world *world)
+{
+ while (parse_toplev(world))
+ ;
+}
+
+static int is_assignment(char *arg, char **equal_sign)
+{
+ char *tmp = strchr(arg, '=');
+ if (tmp == NULL)
+ return 0;
+ *equal_sign = tmp;
+ return 1;
+}
+
+static void parse_cmdarg_assignment(struct world *world, char *arg,
+ char *equal_sign)
+{
+ struct buf buf = { arg, equal_sign, equal_sign };
+ char *end = equal_sign + strzlen(equal_sign);
+ struct line line = { { equal_sign, end }, { "cmdarg", 0 } };
+
+ parse_assignment(world, NULL, &buf, &line, vs_cmdargs);
+}
+
+static void world_init(struct world *world, char **envp)
+{
+ world->fstack.idx = 0;
+ world->envp = envp;
+ world->pat_rule_num = 0;
+
+ vtable_init(&world->vtable);
+ rtable_init(&world->rtable);
+
+ for (; *envp; ++envp) {
+ char *equal_sign;
+ is_assignment(*envp, &equal_sign);
+ parse_cmdarg_assignment(world, *envp, equal_sign);
+ }
+}
+
+static int timegt(struct time *t1, struct time *t2)
+{
+ if (t1->sec > t2->sec)
+ return 1;
+ if (t1->sec == t2->sec)
+ return t1->usec > t2->usec;
+ return 0;
+}
+
+static void check_status(int status, char *prx)
+{
+ if (WIFEXITED(status)) {
+ int ret = WEXITSTATUS(status);
+ if (ret != 0) {
+ error("%s: program exited with %d status",
+ prx, ret);
+ }
+ } else if(WIFSIGNALED(status)) {
+ error("%s: program received a signal %d",
+ WTERMSIG(status), prx);
+ } else {
+ error("%s: unknown program status", prx);
+ }
+}
+
+static void exec_recipe(struct world *world, struct full_rule *rule,
+ int i)
+{
+ char *argv[] = { "/bin/sh", "-c", NULL, NULL };
+ int ret;
+ int status;
+ struct line line = rule->rest->recipes->buf[i];
+
+ struct buf *buf = eval(world, &line, rule);
+ buf_put_silent_zero(buf);
+
+ argv[2] = buf->start;
+ flush(&stderr);
+ ret = fork();
+
+ if (ret == -1)
+ syscall_error("fork");
+ if (ret == 0) {
+ struct str cmd = { buf->start, buf->cur };
+ trim(&cmd);
+ printf("%S\n", &cmd);
+ execve("/bin/sh", argv, world->envp);
+
+ perror("execve");
+ flush(&stderr);
+ exit(1);
+ }
+ wait4(ret, &status, 0, 0);
+ check_status(status, "");
+}
+
+static void exec_recipes(struct world *world, struct full_rule *rule)
+{
+ int i;
+ for (i = 0; i < rule->rest->recipes->len; ++i)
+ exec_recipe(world, rule, i);
+}
+
+static int exists(char *path)
+{
+ return access(path, F_OK) == 0;
+}
+
+static int matches(struct str *pat, struct str *str,
+ struct str *wildcard)
+{
+ char *tmp = pat->start;
+ char *stmp = str->start;
+ int rem;
+ for (;*tmp != '%'; ++tmp, ++stmp) {
+ if (*tmp != *stmp)
+ return 0;
+ }
+ ++tmp;
+
+ rem = pat->end - tmp;
+ if (rem > str->end - stmp)
+ return 0;
+ wildcard->start = stmp;
+ wildcard->end = str->end - rem;
+ return memeq(tmp, wildcard->end, rem);
+}
+
+static void heapbuf_init(struct buf *buf)
+{
+ buf->start = malloc(1);
+ buf->cur = buf->start;
+ buf->end = buf->start + 1;
+}
+
+static void heapbuf_putchar(struct buf *buf, char c)
+{
+ if (buf->cur == buf->end) {
+ ++buf->end;
+ realloc(buf->start, buf->end - buf->start);
+ }
+ *buf->cur = c;
+ ++buf->cur;
+}
+
+static void heapbuf_puts(struct buf *buf, struct str *str)
+{
+ char *tmp;
+ for (tmp = str->start; tmp != str->end; ++tmp)
+ heapbuf_putchar(buf, *tmp);
+}
+
+static void subst_pat(struct str *dest, struct str *pat,
+ struct str *wildcard)
+{
+ struct buf buf;
+ char *tmp;
+ heapbuf_init(&buf);
+ for (tmp = pat->start; tmp != pat->end; ++tmp) {
+ if (*tmp != '%')
+ heapbuf_putchar(&buf, *tmp);
+ else
+ heapbuf_puts(&buf, wildcard);
+ }
+ dest->start = buf.start;
+ dest->end = buf.cur;
+ heapbuf_putchar(&buf, 0);
+}
+
+static struct rule_body *construct_prereqs(struct full_rule *patrule,
+ struct str *wildcard)
+{
+ struct rule_body *newbody = malloc(sizeof(*newbody));
+ struct strbuf *prereqs = &newbody->prereqs;
+ int i;
+
+ newbody->normal_prereq_count = patrule->rest->normal_prereq_count;
+ newbody->recipes = patrule->rest->recipes;
+
+ prereqs->len = patrule->rest->prereqs.len;
+ prereqs->buf = malloc(sizeof(*prereqs->buf) * prereqs->len);
+ for (i = 0; i < prereqs->len; ++i) {
+ subst_pat(&prereqs->buf[i], &patrule->rest->prereqs.buf[i],
+ wildcard);
+ }
+ return newbody;
+}
+
+static void ccarena_grow(struct arena *a, int size)
+{
+ error("ccarena: no more space");
+}
+
+void prereqdup(struct arena *a, struct str *dest, struct str *src)
+{
+ int len = strlen(src);
+ dest->start = amalloc(a, len + 1);
+ dest->end = dest->start + len;
+ memcpy(dest->start, src->start, len);
+ *dest->end = 0;
+}
+
+static struct rule_body *create_copy(struct rule_body *rule_body)
+{
+ static struct arena ccarena;
+ struct rule_body *tmp;
+ struct strbuf *prereqs;
+ int i;
+ if (ccarena.start == NULL) {
+ ccarena.start = ccarena.cur = mem_alloc(buf_size);
+ ccarena.end = ccarena.start + buf_size;
+ ccarena.grow = ccarena_grow;
+ }
+ tmp = amalloc(&ccarena, sizeof(*tmp));
+ tmp->normal_prereq_count = rule_body->normal_prereq_count;
+ tmp->recipes = rule_body->recipes;
+ prereqs = &tmp->prereqs;
+ prereqs->len = rule_body->prereqs.len;
+ prereqs->buf =
+ amalloc(&ccarena, sizeof(*prereqs->buf) * prereqs->len);
+
+ for (i = 0; i < prereqs->len; ++i) {
+ prereqdup(&ccarena, &prereqs->buf[i],
+ &rule_body->prereqs.buf[i]);
+ }
+ /*printf(">>> Cur: %d\n", ccarena.cur - ccarena.start);*/
+ return tmp;
+}
+
+static struct rule_body *find_body(struct world *world, struct str *target,
+ int depth);
+
+static struct rule_body *suitable_patrule(struct world *world, int idx,
+ struct str *target, int depth)
+{
+ struct rule_body *tmp = NULL;
+ struct rule_body *tmp_copy;
+ struct str wildcard;
+ int i;
+
+ heap_offset_save();
+
+ if (!matches(world->pat_rules[idx].target, target, &wildcard))
+ goto failure;
+
+ tmp = construct_prereqs(world->pat_rules + idx, &wildcard);
+
+ for (i = 0; i < tmp->prereqs.len; ++i) {
+ struct str *prereq = &tmp->prereqs.buf[i];
+ struct rule_body *pbody;
+ if (exists(prereq->start))
+ continue;
+ pbody = find_body(world, prereq, depth);
+ if (pbody == NULL)
+ goto failure;
+ }
+ tmp_copy = create_copy(tmp);
+ /*printf(">>> Saving: %S\n", target);*/
+ rtable_add(&world->rtable, target, 1, tmp_copy);
+ heap_offset_remove();
+ return tmp_copy;
+failure:
+ heap_offset_restore();
+ return NULL;
+}
+
+static struct rule_body *find_body(struct world *world,
+ struct str *target, int depth)
+{
+ int i;
+ struct rule_body *tmp;
+ /*printf("FB: %S\n", target);*/
+ if (rtable_find(&world->rtable, target, &tmp) == 1)
+ return tmp;
+
+ if (depth >= find_body_max_depth)
+ return NULL;
+
+ for (i = 0; i < world->pat_rule_num; ++i) {
+ tmp = suitable_patrule(world, i, target, depth + 1);
+ if (tmp != NULL)
+ break;
+ }
+ if (tmp == NULL)
+ rtable_add(&world->rtable, target, 1, NULL);
+ return tmp;
+}
+
+struct target_info {
+ struct time mtime;
+ int exists;
+};
+
+static int get_mtime(struct str *str, struct time *time)
+{
+ int ret;
+ struct stat st;
+
+ ret = stat(str->start, &st);
+ /*printf("Time: %s %d\n", str->start, ret);*/
+ if (ret == -1) {
+ if (errno == ENOENT) {
+ return 0;
+ }
+ syscall_error(str->start);
+ }
+ *time = st.mtime;
+ return 1;
+}
+
+static void tinfo_get(struct target_info *info, struct str *str)
+{
+ info->exists = get_mtime(str, &info->mtime);
+}
+
+static int exec_target(struct world *world, struct str *target,
+ struct target_info *info);
+
+struct pstatus {
+ struct time time;
+ struct str *prereq;
+};
+
+static int exec_prereqs(struct world *world, struct full_rule *rule,
+ struct pstatus *pstatus, struct target_info *info)
+{
+ int i;
+ int flag = 0;
+ struct target_info pinfo;
+
+ *pstatus = (struct pstatus) { { 0, 0 }, NULL };
+
+ for (i = 0; i < rule->rest->prereqs.len; ++i) {
+ int ret, is_oo;
+ struct str *prereq = &rule->rest->prereqs.buf[i];
+ tinfo_get(&pinfo, prereq);
+ ret = exec_target(world, prereq, &pinfo);
+ is_oo = i >= rule->rest->normal_prereq_count;
+ if (ret) {
+ if (flag == 0)
+ pstatus->prereq = prereq;
+ flag = 1;
+ }
+ if (flag == 0 && !is_oo && timegt(&pinfo.mtime, &pstatus->time)) {
+ pstatus->time = pinfo.mtime;
+ pstatus->prereq = prereq;
+ }
+ }
+ return flag;
+}
+
+static void print_reason(int debug, struct str *target, int flag,
+ struct target_info *info, struct pstatus *ptime)
+{
+
+ if (debug == 0)
+ return;
+ if (!info->exists) {
+ printf("%s%S did not exist\n", debug_prx, target);
+ } else if (flag) {
+ printf("%s%S(%U.%U) by %S\n", debug_prx, target,
+ info->mtime.sec, info->mtime.usec,
+ ptime->prereq);
+ } else {
+ printf("%s%S(%U.%U) <- %S(%U.%U)\n", debug_prx, target,
+ info->mtime.sec, info->mtime.usec,
+ ptime->prereq, ptime->time.sec,
+ ptime->time.usec);
+ }
+}
+
+static void print_new_time(int debug, struct str *target)
+{
+ struct time time;
+ if (debug == 0)
+ return;
+ if (get_mtime(target, &time)) {
+ printf("%sUpdated time %S: %U.%U\n", debug_prx, target,
+ time.sec, time.usec);
+ } else {
+ printf("%s%S was not created\n", debug_prx, target);
+ }
+}
+
+static int exec_target(struct world *world, struct str *target,
+ struct target_info *info)
+{
+ struct rule_body *prule = find_body(world, target, 0);
+ struct full_rule rule = { target, prule };
+ int flag = 0;
+ struct pstatus pstatus;
+ int prereq_later;
+
+ /*printf("EXEC: %S\n", target);*/
+ if (prule == NULL) {
+ if (info->exists) {
+ return 0;
+ } else {
+ error("%S: don't know what to do with the target",
+ target);
+ }
+ }
+ flag = exec_prereqs(world, &rule, &pstatus, info);
+
+ prereq_later = timegt(&pstatus.time, &info->mtime);
+ if (flag || !info->exists || prereq_later) {
+ print_reason(world->debug_mode, target, flag, info,
+ &pstatus);
+ exec_recipes(world, &rule);
+ print_new_time(world->debug_mode, target);
+ return 1;
+ }
+ return 0;
+}
+
+static void exec(struct world *world)
+{
+ struct target_info info;
+ if (strempty(&world->target)) {
+ printf("nothing to be done\n");
+ hexit(0);
+ }
+ tinfo_get(&info, &world->target);
+ exec_target(world, &world->target, &info);
+}
+
+static void parse_cmdargs(struct world *world, char **argv)
+{
+ char *makefile = "Makefile";
+ char *target = NULL;
+ char *equal_sign;
+
+ ++argv;
+ for (; argv[0]; ++argv) {
+ if (argv[0][0] == '-') {
+ if (argv[0][1] == 'f' && argv[0][2] == 0) {
+ if (argv[1] == NULL)
+ error("-f: missing file name");
+ makefile = argv[1];
+ ++argv;
+ } else if (argv[0][1] == 'C' && argv[0][2] == 0) {
+ if (argv[1] == NULL)
+ error("-C: missing dir");
+ if (chdir(argv[1]) == -1)
+ syscall_error(argv[1]);
+ ++argv;
+ } else if (argv[0][1] == 'd' && argv[0][2] == 0) {
+ world->debug_mode = 1;
+ } else {
+ error("%s: unknown flag", argv[0]);
+ }
+ } else if (is_assignment(argv[0], &equal_sign)) {
+ parse_cmdarg_assignment(world, argv[0],
+ equal_sign);
+ } else {
+ if (target)
+ error("%s: target already provided");
+ target = argv[0];
+ }
+ }
+ world->target = target ? STR(target) : CSTR("");
+ fstack_add(&world->fstack, makefile, NULL);
+}
+
+static void print_program_time(int debug)
+{
+ struct rusage rusage;
+ if (!debug)
+ return;
+ if (getrusage(RUSAGE_SELF, &rusage) == -1)
+ syscall_error("rusage");
+ printf("%s: user time: %U.%3U, sys time: %U.%3U\n", progname,
+ rusage.utime.sec, rusage.utime.msec / 1000,
+ rusage.stime.sec, rusage.stime.msec / 1000);
+}
+
+int main(int argc, char **argv, char **envp)
+{
+ struct world world;
+
+ world_init(&world, envp);
+ parse_cmdargs(&world, argv);
+
+ parse(&world);
+
+ exec(&world);
+ print_program_time(world.debug_mode);
+ return 0;
+}