2 * Another fast dependencies generator for Makefiles, Version 2.2
4 * Copyright (C) 2005 by Vladimir Oleynik <dzo@simtreas.ru>
5 * mmaping file may be originally by Linus Torvalds.
7 * (c) 2005 Bernhard Fischer:
9 * - move "memory exhausted" into msg_enomem,
10 * - more verbose --help output.
13 * 1) find #define KEY VALUE or #undef KEY from include/config.h
14 * 2) save include/config/key*.h if changed after previous usage
15 * 3) recursive scan from "./" *.[ch] files, but skips scan of include/config/
16 * 4) find #include "*.h" and KEYs using, if not as #define and #undef
17 * 5) generate dependencies to stdout
18 * path/file.o: include/config/key*.h found_include_*.h
19 * path/inc.h: include/config/key*.h found_included_include_*.h
20 * This program does not generate dependencies for #include <...>
23 #define LOCAL_INCLUDE_PATH "include"
24 #define INCLUDE_CONFIG_PATH LOCAL_INCLUDE_PATH"/config"
25 #define INCLUDE_CONFIG_KEYS_PATH LOCAL_INCLUDE_PATH"/config.h"
27 #define bb_mkdep_full_options \
29 "\n\t-I local_include_path include paths, default: \"" LOCAL_INCLUDE_PATH "\"" \
30 "\n\t-d don't generate depend" \
31 "\n\t-w show warning if include files not found" \
32 "\n\t-k include/config default: \"" INCLUDE_CONFIG_PATH "\"" \
33 "\n\t-c include/config.h configs, default: \"" INCLUDE_CONFIG_KEYS_PATH "\"" \
34 "\n\tdirs_to_scan default \".\""
36 #define bb_mkdep_terse_options "Usage: [-I local_include_paths] [-dw] " \
37 "[-k path_for_stored_keys] [dirs]"
42 #include <sys/types.h>
56 typedef struct BB_KEYS {
64 static bb_key_t *check_key(bb_key_t *k, const char *nk);
65 static bb_key_t *make_new_key(bb_key_t *k, const char *nk);
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)));
71 /* stolen from libbb as is */
72 typedef struct llist_s {
76 static void *xrealloc(void *p, size_t size);
77 static void *xmalloc(size_t size);
78 static char *bb_xstrdup(const char *s);
79 static char *bb_simplify_path(const char *path);
81 static const char msg_enomem[] = "memory exhausted";
83 /* for lexical analyser */
84 static bb_key_t *key_top;
85 static llist_t *configs;
89 #define SOURCES_MODE 1
91 static void parse_inc(const char *include, const char *fname);
92 static void parse_conf_opt(char *opt, const char *val, size_t rsz);
94 /* for speed tricks */
95 static char first_chars[257]; /* + L_EOF */
96 static char isalnums[257]; /* + L_EOF */
97 /* trick for fast find "define", "include", "undef" */
98 static char first_chars_diu[256];
100 static int pagesizem1;
101 static size_t mema_id = 128; /* first allocated for id */
106 #define yy_error_d(s) bb_error_d("%s:%d hmm, %s", fname, line, s)
109 #define S 0 /* start state */
110 #define STR '"' /* string */
111 #define CHR '\'' /* char */
112 #define REM '/' /* block comment */
113 #define BS '\\' /* back slash */
114 #define POUND '#' /* # */
115 #define I 'i' /* #include preprocessor's directive */
116 #define D 'd' /* #define preprocessor's directive */
117 #define U 'u' /* #undef preprocessor's directive */
118 #define LI 'I' /* #include "... */
119 #define DK 'K' /* #define KEY... (config mode) */
120 #define DV 'V' /* #define KEY "VALUE or #define KEY 'VALUE */
121 #define NLC 'n' /* \ and \n */
122 #define ANY '*' /* any unparsed chars */
126 #define ID(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
128 #define ISALNUM(c) (ID(c) || (c >= '0' && c <= '9'))
130 #define getc1() do { c = (optr >= oend) ? L_EOF : *optr++; } while(0)
131 #define ungetc1() optr--
133 #define put_id(c) do { if(id_len == local_mema_id) \
134 id = xrealloc(id, local_mema_id += 16); \
135 id[id_len++] = c; } while(0)
139 /* stupid C lexical analyser */
140 static void c_lex(const char *fname, long fsize)
142 int c = L_EOF; /* stupid initialize */
143 int prev_state = L_EOF;
148 size_t local_mema_id = mema_id;
149 size_t id_len = 0; /* stupid initialize */
151 unsigned char *optr, *oend;
152 unsigned char *start = NULL; /* stupid initialize */
159 fprintf(stderr, "Warning: %s is empty\n", fname);
162 fd = open(fname, O_RDONLY);
167 mapsize = (fsize+pagesizem1) & ~pagesizem1;
168 map = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
169 if ((long) map == -1)
170 bb_error_d("%s: mmap: %m", fname);
172 optr = (unsigned char *)map;
179 if(state == LI || state == DV) {
180 /* store "include.h" or config mode #define KEY "|'..."|' */
183 parse_inc(id, fname);
185 /* #define KEY "[VAL]" */
186 put_id(c); /* #define KEY "VALUE"<- */
188 parse_conf_opt(id, val, (optr - start));
192 if(prev_state != state) {
197 /* [ \t]+ eat first space */
198 while(c == ' ' || c == '\t')
204 /* \\\n eat continued */
214 while(first_chars[c] == ANY) {
223 mema_id = local_mema_id;
224 munmap(map, mapsize);
230 getc1(); /* eat <S>/ */
233 do getc1(); while(c != '\n' && c != L_EOF);
234 } else if(c == '*') {
239 } else if(c == POUND) {
243 } else if(c == STR || c == CHR) {
249 /* <S>[A-Z_a-z0-9] */
251 /* trick for fast drop id
252 if key with this first char undefined */
253 if(first_chars[c] == 0) {
254 /* skip <S>[A-Z_a-z0-9]+ */
255 do getc1(); while(isalnums[c]);
259 /* <S>[A-Z_a-z0-9]+ */
262 } while(isalnums[c]);
264 check_key(key_top, id);
279 yy_error_d("unexpected newline");
281 } else if(c == L_EOF)
282 yy_error_d("unexpected EOF");
295 if(state == STR || state == CHR) {
297 /* <STR,CHR>\n|<<EOF>> */
298 if(c == '\n' || c == L_EOF)
299 yy_error_d("unterminated");
303 if(c != BS && c != '\n' && c != state) {
304 /* another usage \ in str or char */
306 yy_error_d("unexpected EOF");
311 /* <STR,CHR>\\[\\\n] or <STR>\\\" or <CHR>\\\' */
317 } else if(c == state) {
318 /* <STR>\" or <CHR>\' */
329 /* begin preprocessor states */
331 yy_error_d("unexpected EOF");
336 yy_error_d("detected // in preprocessor line");
344 yy_error_d("strange preprocessor line");
348 static const char * const preproc[] = {
351 /* 5 */ /* 6 */ /* 7 */
352 "undef", "define", "include",
354 size_t diu = first_chars_diu[c]; /* strlen and preproc ptr */
355 const unsigned char *p = optr - 1;
357 while(isalnums[c]) getc1();
358 /* have str begined with c, readed == strlen key and compared */
359 if(diu != S && diu == (optr-p-1) && !memcmp(p, preproc[diu], diu)) {
361 id_len = 0; /* common for save */
375 /* another (may be wrong) #include ... */
381 if(state == D || state == U) {
382 if(mode == SOURCES_MODE) {
383 /* ignore depend with #define or #undef KEY */
388 /* save KEY from #"define"|"undef" ... */
394 yy_error_d("expected identifier");
397 parse_conf_opt(id, NULL, (optr - start));
408 /* #define KEY[ ] (config mode) */
410 if(c == STR || c == CHR) {
411 /* define KEY "... or define KEY '... */
431 static void show_usage(void) __attribute__ ((noreturn));
432 static void show_usage(void)
434 bb_error_d("%s\n%s\n", bb_mkdep_terse_options, bb_mkdep_full_options);
437 static const char *kp;
438 static size_t kp_len;
440 static bb_key_t *Ifound;
441 static int noiwarning;
443 static bb_key_t *check_key(bb_key_t *k, const char *nk)
447 for(cur = k; cur; cur = cur->next) {
448 if(strcmp(cur->keyname, nk) == 0) {
456 static bb_key_t *make_new_key(bb_key_t *k, const char *nk)
461 nk_size = strlen(nk) + 1;
462 cur = xmalloc(sizeof(bb_key_t) + nk_size);
463 cur->keyname = memcpy(cur + 1, nk, nk_size);
469 static inline char *store_include_fullpath(char *p_i, bb_key_t *li)
473 if(access(p_i, F_OK) == 0) {
474 ok = li->stored_path = bb_simplify_path(p_i);
483 static void parse_inc(const char *include, const char *fname)
489 li = check_key(Ifound, include);
492 Ifound = li = make_new_key(Ifound, include);
494 if(include[0] != '/') {
499 p_i = strrchr(fname, '/');
507 p_i = bb_asprint("%.*s/%s", w, p, include);
508 if(store_include_fullpath(p_i, li))
510 for(lo = Iop; lo; lo = lo->link) {
511 p_i = bb_asprint("%s/%s", lo->data, include);
512 if(store_include_fullpath(p_i, li))
516 /* absolute include pathname */
517 if(access(include, F_OK) == 0) {
518 li->stored_path = bb_xstrdup(include);
523 li->stored_path = NULL;
525 fprintf(stderr, "%s: Warning: #include \"%s\" not found in specified paths\n", fname, include);
528 static void parse_conf_opt(char *opt, const char *val, size_t recordsz)
535 static char *record_buf;
540 cur = check_key(key_top, opt);
542 /* present already */
543 cur->checked = 0; /* store only */
544 if(cur->value == NULL && val == NULL)
546 if(cur->value != NULL && val != NULL && strcmp(cur->value, val) == 0)
548 fprintf(stderr, "Warning: redefined %s\n", opt);
550 key_top = cur = make_new_key(key_top, opt);
552 /* do generate record */
553 recordsz += 2; /* \n\0 */
554 if(recordsz > r_sz) {
555 record_buf = xrealloc(record_buf, r_sz=recordsz);
556 r_cmp = xrealloc(r_cmp, recordsz);
559 /* may be short count " " */
563 recordsz = sprintf(s, "#define %s\n", opt);
565 cur->value = bb_xstrdup(val);
566 recordsz = sprintf(s, "#define %s %s\n", opt, val);
570 recordsz = sprintf(s, "#undef %s\n", opt);
572 /* size_t -> ssize_t :( */
573 rw_ret = (ssize_t)recordsz;
574 /* trick, save first char KEY for do fast identify id */
575 first_chars[(int)*opt] = *opt;
577 /* key converting [A-Z_] -> [a-z/] */
578 for(p = opt; *p; p++) {
579 if(*p >= 'A' && *p <= 'Z')
581 else if(*p == '_' && p[1] > '9') /* do not change A_1 to A/1 */
584 /* check kp/key.h if present after previous usage */
585 cur->stored_path = opt = bb_asprint("%s/%s.h", kp, opt);
589 /* Auto-create directories. */
592 if (access(opt, F_OK) != 0 && mkdir(opt, 0755) != 0)
593 bb_error_d("mkdir(%s): %m", opt);
599 if(st.st_size == (off_t)recordsz) {
600 fd = open(opt, O_RDONLY);
601 if(fd < 0 || read(fd, r_cmp, recordsz) < rw_ret)
602 bb_error_d("%s: %m", opt);
604 cmp_ok = memcmp(s, r_cmp, recordsz) == 0;
608 fd = open(opt, O_WRONLY|O_CREAT|O_TRUNC, 0644);
609 if(fd < 0 || write(fd, s, recordsz) < rw_ret)
610 bb_error_d("%s: %m", opt);
615 static int show_dep(int first, bb_key_t *k, const char *name)
619 for(cur = k; cur; cur = cur->next) {
620 if(cur->checked && cur->stored_path) {
622 printf("\n%s:", name);
627 printf(" %s", cur->stored_path);
634 static struct stat st_kp;
635 static int dontgenerate_dep;
638 parse_chd(const char *fe, const char *p, size_t dirlen)
643 static char *dir_and_entry;
644 static size_t dir_and_entry_sz;
649 df_sz = dirlen + strlen(fe) + 2; /* dir/file\0 */
650 if(df_sz > dir_and_entry_sz)
651 dir_and_entry = xrealloc(dir_and_entry, dir_and_entry_sz = df_sz);
653 sprintf(fp, "%s/%s", p, fe);
656 fprintf(stderr, "Warning: stat(%s): %m", fp);
659 if(S_ISREG(st.st_mode)) {
661 char *e = fp + df_sz - 3;
663 if(*e++ != '.' || (*e != 'c' && *e != 'h')) {
664 /* direntry is regular file, but is not *.[ch] */
667 for(cfl = configs; cfl; cfl = cfl->link) {
668 struct stat *config = (struct stat *)cfl->data;
670 if (st.st_dev == config->st_dev && st.st_ino == config->st_ino) {
671 /* skip already parsed configs.h */
675 /* direntry is *.[ch] regular file and is not configs */
676 if(!dontgenerate_dep) {
679 c_lex(fp, st.st_size);
680 fp = bb_simplify_path(fp);
683 e = strrchr(fp, '.') + 1;
686 first = show_dep(1, Ifound, fp);
687 first = show_dep(first, key_top, fp);
693 } else if(S_ISDIR(st.st_mode)) {
694 if (st.st_dev == st_kp.st_dev && st.st_ino == st_kp.st_ino)
695 return NULL; /* drop scan kp/ directory */
696 /* direntry is directory. buff is returned, begin of zero allocate */
697 dir_and_entry = NULL;
698 dir_and_entry_sz = 0;
701 /* hmm, direntry is device! */
705 /* from libbb but inline for fast */
706 static inline llist_t *llist_add_to(llist_t *old_head, char *new_item)
710 new_head = xmalloc(sizeof(llist_t));
711 new_head->data = new_item;
712 new_head->link = old_head;
717 static void scan_dir_find_ch_files(char *p)
726 dirs = llist_add_to(NULL, p);
727 /* emulate recursive */
731 dir = opendir(dirs->data);
733 fprintf(stderr, "Warning: opendir(%s): %m", dirs->data);
734 dirlen = strlen(dirs->data);
735 while ((de = readdir(dir)) != NULL) {
736 char *found_dir = parse_chd(de->d_name, dirs->data, dirlen);
739 d_add = llist_add_to(d_add, found_dir);
754 int main(int argc, char **argv)
761 /* for bb_simplify_path */
762 /* libbb xgetcwd(), this program have not chdir() */
763 unsigned path_max = 512;
765 s = xmalloc (path_max);
767 while (getcwd (s, path_max) == NULL) {
769 bb_error_d("getcwd: %m");
770 s = xrealloc (s, path_max += PATH_INCR);
775 while ((i = getopt(argc, argv, "I:c:dk:w")) > 0) {
778 Iop = llist_add_to(Iop, optarg);
781 s = bb_simplify_path(optarg);
782 configs = llist_add_to(configs, s);
785 dontgenerate_dep = 1;
789 bb_error_d("Hmm, why multiple -k?");
790 kp = bb_simplify_path(optarg);
801 kp = bb_simplify_path(INCLUDE_CONFIG_PATH);
802 /* globals initialize */
805 bb_error_d("stat(%s): %m", kp);
806 if(!S_ISDIR(st_kp.st_mode))
807 bb_error_d("%s is not directory", kp);
810 Iop = llist_add_to(Iop, LOCAL_INCLUDE_PATH);
811 if(configs == NULL) {
812 s = bb_simplify_path(INCLUDE_CONFIG_KEYS_PATH);
813 configs = llist_add_to(configs, s);
816 pagesizem1 = getpagesize() - 1;
817 id_s = xmalloc(mema_id);
818 for(i = 0; i < 256; i++) {
821 /* set unparsed chars for speed up of parser */
822 else if(i != CHR && i != STR && i != POUND && i != REM && i != BS)
823 first_chars[i] = ANY;
825 first_chars[i] = '-'; /* L_EOF */
826 /* trick for fast find "define", "include", "undef" */
827 first_chars_diu[(int)'d'] = (char)6; /* strlen("define"); */
828 first_chars_diu[(int)'i'] = (char)7; /* strlen("include"); */
829 first_chars_diu[(int)'u'] = (char)5; /* strlen("undef"); */
832 for(fl = configs; fl; fl = fl->link) {
835 if(stat(fl->data, &st))
836 bb_error_d("stat(%s): %m", fl->data);
837 c_lex(fl->data, st.st_size);
838 /* trick for fast comparing found files with configs */
839 fl->data = xrealloc(fl->data, sizeof(struct stat));
840 memcpy(fl->data, &st, sizeof(struct stat));
848 scan_dir_find_ch_files(*argv++);
850 scan_dir_find_ch_files(".");
855 /* partial and simplified libbb routine */
856 static void bb_error_d(const char *s, ...)
861 vfprintf(stderr, s, p);
867 static char *bb_asprint(const char *format, ...)
874 r = vasprintf(&out, format, p);
878 bb_error_d("bb_asprint: %m");
882 /* partial libbb routine as is */
883 static void *xmalloc(size_t size)
885 void *p = malloc(size);
888 bb_error_d(msg_enomem);
892 static void *xrealloc(void *p, size_t size) {
893 p = realloc(p, size);
895 bb_error_d(msg_enomem);
899 static char *bb_xstrdup(const char *s)
904 bb_error_d(msg_enomem);
908 static char *bb_simplify_path(const char *path)
913 start = bb_xstrdup(path);
915 /* is not libbb, but this program have not chdir() */
916 start = bb_asprint("%s/%s", pwd, path);
922 if (*s == '/') { /* skip duplicate (or initial) slash */
924 } else if (*s == '.') {
925 if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */
927 } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) {
930 while (*--p != '/'); /* omit previous dir */
939 if ((p == start) || (*p != '/')) { /* not a trailing slash */
940 ++p; /* so keep last character */