2 * Another fast dependencies generator for Makefiles, Version 3.0
4 * Copyright (C) 2005 by Vladimir Oleynik <dzo@simtreas.ru>
6 * mmaping file may be originally by Linus Torvalds.
9 * Copyright (C) 2005 Manuel Novoa III <mjn3@codepoet.org>
11 * xmalloc() bb_xstrdup() bb_error_d():
12 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
15 * Copyright (C) 2003 Glenn McGrath
16 * Copyright (C) Vladimir Oleynik <dzo@simtreas.ru>
18 * (c) 2005 Bernhard Fischer:
20 * - move "memory exhausted" into msg_enomem,
21 * - more verbose --help output.
24 * 1) find #define KEY VALUE or #undef KEY from include/config.h
25 * 2) recursive find and scan *.[ch] files, but skips scan of include/config/
26 * 3) find #include "*.h" and KEYs using, if not as #define and #undef
27 * 4) generate dependencies to stdout
28 * pwd/file.o: include/config/key*.h found_include_*.h
29 * path/inc.h: include/config/key*.h found_included_include_*.h
30 * 5) save include/config/key*.h if changed after previous usage
31 * This program does not generate dependencies for #include <...>
34 #define LOCAL_INCLUDE_PATH "include"
35 #define INCLUDE_CONFIG_PATH LOCAL_INCLUDE_PATH"/config"
36 #define INCLUDE_CONFIG_KEYS_PATH LOCAL_INCLUDE_PATH"/config.h"
38 #define bb_mkdep_full_options \
40 "\n\t-I local_include_path include paths, default: \"" LOCAL_INCLUDE_PATH "\"" \
41 "\n\t-d don't generate depend" \
42 "\n\t-w show warning if include files not found" \
43 "\n\t-k include/config default: \"" INCLUDE_CONFIG_PATH "\"" \
44 "\n\t-c include/config.h configs, default: \"" INCLUDE_CONFIG_KEYS_PATH "\"" \
45 "\n\tdirs_to_scan default \".\""
47 #define bb_mkdep_terse_options "Usage: [-I local_include_paths] [-dw] " \
48 "[-k path_for_stored_keys] [dirs]"
52 #include <sys/types.h>
67 /* partial and simplified libbb routine */
68 static void bb_error_d(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
69 static char * bb_asprint(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
70 static char *bb_simplify_path(const char *path);
72 /* stolen from libbb as is */
73 typedef struct llist_s {
78 static const char msg_enomem[] = "memory exhausted";
80 /* inline realization for fast works */
81 static inline void *xmalloc(size_t size)
83 void *p = malloc(size);
86 bb_error_d(msg_enomem);
90 static inline char *bb_xstrdup(const char *s)
95 bb_error_d(msg_enomem);
100 static int dontgenerate_dep; /* flag -d usaged */
101 static int noiwarning; /* flag -w usaged */
102 static llist_t *configs; /* list of -c usaged and them stat() after parsed */
103 static llist_t *Iop; /* list of -I include usaged */
105 static char *pwd; /* current work directory */
106 static size_t replace; /* replace current work derectory to build dir */
108 static const char *kp; /* KEY path, argument of -k usaged */
109 static size_t kp_len;
110 static struct stat st_kp; /* stat(kp) */
112 typedef struct BB_KEYS {
118 const char *src_have_this_key;
119 struct BB_KEYS *next;
122 static bb_key_t *key_top; /* list of found KEYs */
123 static bb_key_t *Ifound; /* list of parsed includes */
126 static void parse_conf_opt(const char *opt, const char *val, size_t key_sz);
127 static void parse_inc(const char *include, const char *fname);
129 static inline bb_key_t *check_key(bb_key_t *k, const char *nk, size_t key_sz)
133 for(cur = k; cur; cur = cur->next) {
134 if(key_sz == cur->key_sz && memcmp(cur->keyname, nk, key_sz) == 0) {
135 cur->checked = cur->stored_path;
142 /* for lexical analyser */
143 static int pagesizem1; /* padding mask = getpagesize() - 1 */
145 /* for speed tricks */
146 static char first_chars[1+UCHAR_MAX]; /* + L_EOF */
147 static char isalnums[1+UCHAR_MAX]; /* + L_EOF */
148 /* trick for fast find "define", "include", "undef" */
149 static const char first_chars_diu[UCHAR_MAX] = {
150 [(int)'d'] = (char)5, /* strlen("define") - 1; */
151 [(int)'i'] = (char)6, /* strlen("include") - 1; */
152 [(int)'u'] = (char)4, /* strlen("undef") - 1; */
155 #define CONFIG_MODE 0
156 #define SOURCES_MODE 1
159 #define yy_error_d(s) bb_error_d("%s:%d hmm, %s", fname, line, s)
162 #define S 0 /* start state */
163 #define STR '"' /* string */
164 #define CHR '\'' /* char */
165 #define REM '/' /* block comment */
166 #define BS '\\' /* back slash */
167 #define POUND '#' /* # */
168 #define D '5' /* #define preprocessor's directive */
169 #define I '6' /* #include preprocessor's directive */
170 #define U '4' /* #undef preprocessor's directive */
171 #define DK 'K' /* #define KEY... (config mode) */
172 #define ANY '*' /* any unparsed chars */
175 #define ID(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
177 #define ISALNUM(c) (ID(c) || (c >= '0' && c <= '9'))
179 #define L_EOF (1+UCHAR_MAX)
181 #define getc0() do { c = (optr >= oend) ? L_EOF : *optr++; } while(0)
183 #define getc1() do { getc0(); if(c == BS) { getc0(); \
184 if(c == '\n') { line++; continue; } \
185 else { optr--; c = BS; } \
188 static char id_s[4096];
189 #define put_id(ic) do { if(id_len == sizeof(id_s)) goto too_long; \
190 id[id_len++] = ic; } while(0)
193 /* stupid C lexical analyser */
194 static void c_lex(const char *fname, long fsize)
200 size_t id_len = 0; /* stupid initialize */
201 unsigned char *optr, *oend;
208 fprintf(stderr, "Warning: %s is empty\n", fname);
211 fd = open(fname, O_RDONLY);
216 mapsize = (fsize+pagesizem1) & ~pagesizem1;
217 map = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
218 if ((long) map == -1)
219 bb_error_d("%s: mmap: %m", fname);
221 optr = (unsigned char *)map;
230 /* [ \t]+ eat first space */
231 while(c == ' ' || c == '\t')
235 while(first_chars[c] == ANY) {
243 munmap(map, mapsize);
252 do getc0(); while(c != '\n' && c != L_EOF);
253 } else if(c == '*') {
254 /* <S>[/][*] goto parse block comments */
257 } else if(c == POUND) {
261 } else if(c == STR || c == CHR) {
269 /* <STR>\" or <CHR>\' */
273 /* <STR,CHR>\\ but is not <STR,CHR>\\\n */
276 if(c == '\n' || c == L_EOF)
277 yy_error_d("unterminated");
281 /* <S>[A-Z_a-z0-9] */
283 /* trick for fast drop id
284 if key with this first char undefined */
285 if(first_chars[c] == 0) {
286 /* skip <S>[A-Z_a-z0-9]+ */
287 do getc1(); while(isalnums[c]);
291 /* <S>[A-Z_a-z0-9]+ */
294 } while(isalnums[c]);
295 check_key(key_top, id, id_len);
300 /* begin preprocessor states */
302 yy_error_d("unexpected EOF");
307 yy_error_d("detected // in preprocessor line");
309 /* <#.*>[/][*] goto parse block comments */
313 yy_error_d("strange preprocessor line");
317 static const char * const preproc[] = {
319 "", "", "", "", "ndef", "efine", "nclude"
321 size_t diu = first_chars_diu[c]; /* strlen and preproc ptr */
331 /* have str begined with c, readed == strlen key and compared */
332 if(diu == id_len && !memcmp(id, preproc[diu], diu)) {
334 id_len = 0; /* common for save */
337 while(isalnums[c]) getc1();
339 } else if(state == I) {
347 yy_error_d("unexpected EOF");
351 /* store "include.h" */
352 parse_inc(id, fname);
355 /* else another (may be wrong) #include ... */
357 } else if(state == D || state == U) {
358 if(mode == SOURCES_MODE) {
359 /* ignore depend with #define or #undef KEY */
360 while(isalnums[c]) getc1();
363 /* save KEY from #"define"|"undef" ... */
369 yy_error_d("expected identifier");
371 parse_conf_opt(id, NULL, id_len);
376 yy_error_d("unexpected function macro");
381 /* state==<DK> #define KEY[ ] (config mode) */
382 size_t opt_len = id_len;
383 char *val = id + opt_len;
387 if(c == L_EOF || c == '\n')
394 /* trim tail spaces */
395 while(--sp >= val && (*sp == ' ' || *sp == '\t'
396 || *sp == '\f' || *sp == '\v'))
398 parse_conf_opt(id, val, opt_len);
411 yy_error_d("unexpected newline");
413 } else if(c == L_EOF)
414 yy_error_d("unexpected EOF");
426 yy_error_d("phrase too long");
430 /* bb_simplify_path special variant for apsolute pathname */
431 static size_t bb_qa_simplify_path(char *path)
439 if (*s == '/') { /* skip duplicate (or initial) slash */
441 } else if (*s == '.') {
442 if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */
444 } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) {
447 while (*--p != '/'); /* omit previous dir */
456 if ((p == path) || (*p != '/')) { /* not a trailing slash */
457 ++p; /* so keep last character */
464 static void parse_inc(const char *include, const char *fname)
472 if(*include == '/') {
474 ap = bb_xstrdup(include);
480 p = strrchr(fname, '/'); /* fname have absolute pathname */
482 /* find from current directory of source file */
483 ap = bb_asprint("%.*s/%s", w, fname, include);
487 key_sz = bb_qa_simplify_path(ap);
488 cur = check_key(Ifound, ap, key_sz);
490 cur->checked = cur->value;
494 if(access(ap, F_OK) == 0) {
498 } else if(lo == NULL) {
503 /* find from "-I include" specified directories */
505 /* lo->data have absolute pathname */
506 ap = bb_asprint("%s/%s", lo->data, include);
510 cur = xmalloc(sizeof(bb_key_t));
512 cur->key_sz = key_sz;
513 cur->stored_path = ap;
514 cur->value = cur->checked = p_i;
515 if(p_i == NULL && noiwarning)
516 fprintf(stderr, "%s: Warning: #include \"%s\" not found\n", fname, include);
521 static size_t max_rec_sz;
523 static void parse_conf_opt(const char *opt, const char *val, size_t key_sz)
530 cur = check_key(key_top, opt, key_sz);
532 /* present already */
533 cur->checked = NULL; /* store only */
534 if(cur->value == NULL && val == NULL)
536 if(cur->value != NULL && val != NULL && !strcmp(cur->value, val))
539 fprintf(stderr, "Warning: redefined %s\n", k);
544 val_sz = strlen(val) + 1;
545 recordsz = key_sz + val_sz + 1;
546 if(max_rec_sz < recordsz)
547 max_rec_sz = recordsz;
548 cur = xmalloc(sizeof(bb_key_t) + recordsz);
549 k = cur->keyname = memcpy(cur + 1, opt, key_sz);
550 cur->keyname[key_sz] = '\0';
551 cur->key_sz = key_sz;
553 cur->src_have_this_key = NULL;
562 cur->value = p = cur->keyname + key_sz + 1;
563 memcpy(p, val, val_sz);
568 /* trick, save first char KEY for do fast identify id */
569 first_chars[(int)*k] = *k;
571 cur->stored_path = k = bb_asprint("%s/%s.h", kp, k);
572 /* key converting [A-Z_] -> [a-z/] */
573 for(p = k + kp_len + 1; *p; p++) {
574 if(*p >= 'A' && *p <= 'Z')
576 else if(*p == '_' && p[1] > '9') /* do not change A_1 to A/1 */
581 static void store_keys(void)
588 size_t recordsz = max_rec_sz * 2 + 10 * 2 + 16;
589 /* buffer for double "#define KEY VAL\n" */
590 char *record_buf = xmalloc(recordsz);
592 for(cur = key_top; cur; cur = cur->next) {
593 if(cur->src_have_this_key) {
594 /* do generate record */
596 if(cur->value == NULL) {
597 recordsz = sprintf(record_buf, "#undef %s\n", k);
599 const char *val = cur->value;
601 recordsz = sprintf(record_buf, "#define %s\n", k);
603 recordsz = sprintf(record_buf, "#define %s %s\n", k, val);
606 /* size_t -> ssize_t :( */
607 rw_ret = (ssize_t)recordsz;
608 /* check kp/key.h, compare after previous usage */
610 k = cur->stored_path;
613 for(p = k + kp_len + 1; *p; p++) {
614 /* Auto-create directories. */
617 if (access(k, F_OK) != 0 && mkdir(k, 0755) != 0)
618 bb_error_d("mkdir(%s): %m", k);
624 if(st.st_size == (off_t)recordsz) {
627 size_t padded = recordsz;
629 /* 16-byte padding for read(2) and memcmp(3) */
630 padded = (padded+15) & ~15;
631 r_cmp = record_buf + padded;
632 fd = open(k, O_RDONLY);
633 if(fd < 0 || read(fd, r_cmp, recordsz) < rw_ret)
634 bb_error_d("%s: %m", k);
636 cmp_ok = memcmp(record_buf, r_cmp, recordsz) == 0;
640 int fd = open(k, O_WRONLY|O_CREAT|O_TRUNC, 0644);
641 if(fd < 0 || write(fd, record_buf, recordsz) < rw_ret)
642 bb_error_d("%s: %m", k);
649 static int show_dep(int first, bb_key_t *k, const char *name, const char *f)
653 for(cur = k; cur; cur = cur->next) {
658 printf("\n%s:", name);
660 printf("\n%s/%s:", pwd, name);
665 printf(" %s", cur->checked);
667 cur->src_have_this_key = cur->checked;
675 parse_chd(const char *fe, const char *p, size_t dirlen)
680 static char dir_and_entry[4096];
681 size_t fe_sz = strlen(fe) + 1;
683 df_sz = dirlen + fe_sz + 1; /* dir/file\0 */
684 if(df_sz > sizeof(dir_and_entry))
685 bb_error_d("%s: file name too long", fe);
687 /* sprintf(fp, "%s/%s", p, fe); */
688 memcpy(fp, p, dirlen);
690 memcpy(fp + dirlen + 1, fe, fe_sz);
693 fprintf(stderr, "Warning: stat(%s): %m\n", fp);
696 if(S_ISREG(st.st_mode)) {
698 char *e = fp + df_sz - 3;
700 if(*e++ != '.' || (*e != 'c' && *e != 'h')) {
701 /* direntry is regular file, but is not *.[ch] */
704 for(cfl = configs; cfl; cfl = cfl->link) {
705 struct stat *config = (struct stat *)cfl->data;
707 if (st.st_dev == config->st_dev && st.st_ino == config->st_ino) {
708 /* skip already parsed configs.h */
712 /* direntry is *.[ch] regular file and is not configs */
713 c_lex(fp, st.st_size);
714 if(!dontgenerate_dep) {
719 /* /src_dir/path/file.o to path/file.o */
726 first = show_dep(1, Ifound, fp, e);
727 first = show_dep(first, key_top, fp, e);
731 show_dep(-1, key_top, NULL, NULL);
734 } else if(S_ISDIR(st.st_mode)) {
735 if (st.st_dev == st_kp.st_dev && st.st_ino == st_kp.st_ino)
736 return NULL; /* drop scan kp/ directory */
737 /* direntry is directory. buff is returned */
738 return bb_xstrdup(fp);
740 /* hmm, direntry is device! */
744 /* from libbb but inline for fast */
745 static inline llist_t *llist_add_to(llist_t *old_head, char *new_item)
749 new_head = xmalloc(sizeof(llist_t));
750 new_head->data = new_item;
751 new_head->link = old_head;
756 static void scan_dir_find_ch_files(const char *p)
765 dirs = llist_add_to(NULL, bb_simplify_path(p));
766 replace = strlen(dirs->data);
767 /* emulate recursive */
771 dir = opendir(dirs->data);
773 fprintf(stderr, "Warning: opendir(%s): %m\n", dirs->data);
774 dirlen = strlen(dirs->data);
775 while ((de = readdir(dir)) != NULL) {
778 if (de->d_name[0] == '.')
780 found_dir = parse_chd(de->d_name, dirs->data, dirlen);
782 d_add = llist_add_to(d_add, found_dir);
794 static void show_usage(void) __attribute__ ((noreturn));
795 static void show_usage(void)
797 bb_error_d("%s\n%s\n", bb_mkdep_terse_options, bb_mkdep_full_options);
800 int main(int argc, char **argv)
807 /* for bb_simplify_path, this program have not chdir() */
808 /* libbb-like my xgetcwd() */
809 unsigned path_max = 512;
811 s = xmalloc (path_max);
812 while (getcwd (s, path_max) == NULL) {
814 bb_error_d("getcwd: %m");
816 s = xmalloc(path_max *= 2);
821 while ((i = getopt(argc, argv, "I:c:dk:w")) > 0) {
824 s = bb_simplify_path(optarg);
825 Iop = llist_add_to(Iop, s);
828 s = bb_simplify_path(optarg);
829 configs = llist_add_to(configs, s);
832 dontgenerate_dep = 1;
836 bb_error_d("Hmm, why multiple -k?");
837 kp = bb_simplify_path(optarg);
848 kp = bb_simplify_path(INCLUDE_CONFIG_PATH);
849 /* globals initialize */
852 bb_error_d("stat(%s): %m", kp);
853 if(!S_ISDIR(st_kp.st_mode))
854 bb_error_d("%s is not directory", kp);
857 Iop = llist_add_to(Iop, bb_simplify_path(LOCAL_INCLUDE_PATH));
858 if(configs == NULL) {
859 s = bb_simplify_path(INCLUDE_CONFIG_KEYS_PATH);
860 configs = llist_add_to(configs, s);
863 pagesizem1 = getpagesize() - 1;
864 for(i = 0; i < UCHAR_MAX; i++) {
867 /* set unparsed chars for speed up of parser */
868 else if(i != CHR && i != STR && i != POUND && i != REM)
869 first_chars[i] = ANY;
871 first_chars[i] = '-'; /* L_EOF */
874 for(fl = configs; fl; fl = fl->link) {
877 if(stat(fl->data, &st))
878 bb_error_d("stat(%s): %m", fl->data);
879 c_lex(fl->data, st.st_size);
881 /* trick for fast comparing found files with configs */
882 fl->data = xmalloc(sizeof(struct stat));
883 memcpy(fl->data, &st, sizeof(struct stat));
891 scan_dir_find_ch_files(*argv++);
893 scan_dir_find_ch_files(".");
899 /* partial and simplified libbb routine */
900 static void bb_error_d(const char *s, ...)
905 vfprintf(stderr, s, p);
911 static char *bb_asprint(const char *format, ...)
918 r = vasprintf(&out, format, p);
922 bb_error_d("bb_asprint: %m");
926 /* partial libbb routine as is */
928 static char *bb_simplify_path(const char *path)
933 start = bb_xstrdup(path);
935 /* is not libbb, but this program have not chdir() */
936 start = bb_asprint("%s/%s", pwd, path);
942 if (*s == '/') { /* skip duplicate (or initial) slash */
944 } else if (*s == '.') {
945 if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */
947 } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) {
950 while (*--p != '/'); /* omit previous dir */
959 if ((p == start) || (*p != '/')) { /* not a trailing slash */
960 ++p; /* so keep last character */