2 * Another fast dependencies generator for Makefiles, Version 2.0
4 * Copyright (C) 2005 by Vladimir Oleynik <dzo@simtreas.ru>
7 * 1) find #define KEY VALUE or #undef KEY from include/config.h
8 * 2) save include/config/key*.h if changed after previous usage
9 * 3) recursive scan from "./" *.[ch] files, but skips scan of include/config/
10 * 4) find #include "*.h" and KEYs using, if not as #define and #undef
11 * 5) generate dependencies to stdout
12 * path/file.o: include/config/key*.h found_include_*.h
13 * path/inc.h: include/config/key*.h found_included_include_*.h
14 * This programm does not generate dependencies for #include <...>
17 #define LOCAL_INCLUDE_PATH "include"
18 #define INCLUDE_CONFIG_PATH LOCAL_INCLUDE_PATH"/config"
19 #define INCLUDE_CONFIG_KEYS_PATH LOCAL_INCLUDE_PATH"/config.h"
21 #define bb_mkdep_full_options \
23 "\n\t-I local_include_path include paths, default: \"" LOCAL_INCLUDE_PATH "\"" \
24 "\n\t-d don't generate depend" \
25 "\n\t-w show warning if include files not found" \
26 "\n\t-k include/config default: \"" INCLUDE_CONFIG_PATH "\"" \
27 "\n\t-c include/config.h configs, default: \"" INCLUDE_CONFIG_KEYS_PATH "\"" \
28 "\n\tdirs_to_scan default \".\""
30 #define bb_mkdep_terse_options "Usage: [-I local_include_paths] [-dw] " \
31 "[-k path_for_stored_keys] [dirs]"
36 #include <sys/types.h>
50 typedef struct BB_KEYS {
58 static bb_key_t *check_key(bb_key_t *k, const char *nk);
59 static bb_key_t *make_new_key(bb_key_t *k, const char *nk);
61 /* partial and simplified libbb routine */
62 static void bb_error_d(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
63 static char * bb_asprint(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
65 /* stolen from libbb as is */
66 typedef struct llist_s {
70 static void *xrealloc(void *p, size_t size);
71 static void *xmalloc(size_t size);
72 static char *bb_xstrdup(const char *s);
73 static char *bb_simplify_path(const char *path);
75 static const char msg_enomem[] = "memory exhausted";
77 /* for lexical analyser */
78 static bb_key_t *key_top;
79 static llist_t *configs;
83 #define SOURCES_MODE 1
85 static void parse_inc(const char *include, const char *fname);
86 static void parse_conf_opt(char *opt, const char *val, size_t rsz);
88 /* for speed tricks */
89 static char first_chars[257]; /* + L_EOF */
90 /* trick for fast find "define", "include", "undef" */
91 static char first_chars_diu[256];
93 static int pagesizem1;
94 static size_t mema_id = 128; /* first allocated for id */
99 #define yy_error_d(s) bb_error_d("%s:%d hmm, %s", fname, line, s)
102 #define S 0 /* start state */
103 #define STR '"' /* string */
104 #define CHR '\'' /* char */
105 #define REM '/' /* block comment */
106 #define BS '\\' /* back slash */
107 #define POUND '#' /* # */
108 #define I 'i' /* #include preprocessor's directive */
109 #define D 'd' /* #define preprocessor's directive */
110 #define U 'u' /* #undef preprocessor's directive */
111 #define LI 'I' /* #include "... */
112 #define DK 'K' /* #define KEY... (config mode) */
113 #define DV 'V' /* #define KEY "VALUE or #define KEY 'VALUE */
114 #define NLC 'n' /* \ and \n */
115 #define ANY '*' /* any unparsed chars */
119 #define ID(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
121 #define ISALNUM(c) (ID(c) || (c >= '0' && c <= '9'))
123 #define getc1() do { c = (optr >= oend) ? L_EOF : *optr++; } while(0)
124 #define ungetc1() optr--
126 #define put_id(c) do { if(id_len == local_mema_id) \
127 id = xrealloc(id, local_mema_id += 16); \
128 id[id_len++] = c; } while(0)
132 /* stupid C lexical analyser */
133 static void c_lex(const char *fname, long fsize)
135 int c = L_EOF; /* stupid initialize */
136 int prev_state = L_EOF;
141 size_t local_mema_id = mema_id;
142 size_t id_len = 0; /* stupid initialize */
144 unsigned char *optr, *oend;
145 unsigned char *start = NULL; /* stupid initialize */
152 fprintf(stderr, "Warning: %s is empty\n", fname);
155 fd = open(fname, O_RDONLY);
160 mapsize = (fsize+pagesizem1) & ~pagesizem1;
161 map = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
162 if ((long) map == -1)
163 bb_error_d("%s: mmap: %m", fname);
165 optr = (unsigned char *)map;
172 if(state == LI || state == DV) {
173 /* store "include.h" or config mode #define KEY "|'..."|' */
176 parse_inc(id, fname);
178 /* #define KEY "[VAL]" */
179 parse_conf_opt(id, val, (optr - start));
183 if(prev_state != state) {
188 /* [ \t]+ eat first space */
189 while(c == ' ' || c == '\t')
195 /* \\\n eat continued */
205 while(first_chars[c] == ANY) {
214 mema_id = local_mema_id;
215 munmap(map, mapsize);
221 getc1(); /* eat <S>/ */
224 do getc1(); while(c != '\n' && c != L_EOF);
225 } else if(c == '*') {
230 } else if(c == POUND) {
234 } else if(c == STR || c == CHR) {
240 /* <S>[A-Z_a-z0-9] */
242 /* trick for fast drop id
243 if key with this first char undefined */
244 if(first_chars[c] == 0) {
245 /* skip <S>[A-Z_a-z0-9]+ */
246 do getc1(); while(ISALNUM(c));
250 /* <S>[A-Z_a-z0-9]+ */
255 check_key(key_top, id);
270 yy_error_d("unexpected newline");
272 } else if(c == L_EOF)
273 yy_error_d("unexpected EOF");
286 if(state == STR || state == CHR) {
288 /* <STR,CHR>\n|<<EOF>> */
289 if(c == '\n' || c == L_EOF)
290 yy_error_d("unterminated");
294 if(c != BS && c != '\n' && c != state) {
295 /* another usage \ in str or char */
297 yy_error_d("unexpected EOF");
302 /* <STR,CHR>\\[\\\n] or <STR>\\\" or <CHR>\\\' */
308 } else if(c == state) {
309 /* <STR>\" or <CHR>\' */
311 put_id(c); /* #define KEY "VALUE"<- */
322 /* begin preprocessor states */
324 yy_error_d("unexpected EOF");
329 yy_error_d("detected // in preprocessor line");
337 yy_error_d("strange preprocessor line");
340 const unsigned char *p = optr - 1;
342 static const char * const preproc[] = {
345 /* 5 */ /* 6 */ /* 7 */
346 "undef", "define", "include",
349 size_t diu = first_chars_diu[c]; /* strlen and preproc ptr */
355 /* have str begined with c, readed == strlen key and compared */
356 if(diu != S && diu == readed && !memcmp(p, preproc[diu], diu)) {
358 id_len = 0; /* common for save */
372 /* another (may be wrong) #include ... */
378 if(state == D || state == U) {
379 if(mode == SOURCES_MODE) {
380 /* ignore depend with #define or #undef KEY */
385 /* save KEY from #"define"|"undef" ... */
391 yy_error_d("expected identifier");
394 parse_conf_opt(id, NULL, (optr - start));
405 /* #define KEY[ ] (config mode) */
407 if(c == STR || c == CHR) {
408 /* define KEY "... or define KEY '... */
427 static void show_usage(void) __attribute__ ((noreturn));
428 static void show_usage(void)
430 bb_error_d("%s\n%s\n", bb_mkdep_terse_options, bb_mkdep_full_options);
433 static const char *kp;
434 static size_t kp_len;
436 static bb_key_t *Ifound;
437 static int noiwarning;
439 static bb_key_t *check_key(bb_key_t *k, const char *nk)
443 for(cur = k; cur; cur = cur->next) {
444 if(strcmp(cur->keyname, nk) == 0) {
452 static bb_key_t *make_new_key(bb_key_t *k, const char *nk)
457 nk_size = strlen(nk) + 1;
458 cur = xmalloc(sizeof(bb_key_t) + nk_size);
459 cur->keyname = memcpy(cur + 1, nk, nk_size);
465 static inline char *store_include_fullpath(char *p_i, bb_key_t *li)
469 if(access(p_i, F_OK) == 0) {
470 ok = li->stored_path = bb_simplify_path(p_i);
479 static void parse_inc(const char *include, const char *fname)
485 li = check_key(Ifound, include);
488 Ifound = li = make_new_key(Ifound, include);
490 if(include[0] != '/') {
495 p_i = strrchr(fname, '/');
503 p_i = bb_asprint("%.*s/%s", w, p, include);
504 if(store_include_fullpath(p_i, li))
506 for(lo = Iop; lo; lo = lo->link) {
507 p_i = bb_asprint("%s/%s", lo->data, include);
508 if(store_include_fullpath(p_i, li))
512 /* absolute include pathname */
513 if(access(include, F_OK) == 0) {
514 li->stored_path = bb_xstrdup(include);
519 li->stored_path = NULL;
521 fprintf(stderr, "%s: Warning: #include \"%s\" not found in specified paths\n", fname, include);
524 static void parse_conf_opt(char *opt, const char *val, size_t recordsz)
531 static char *record_buf;
536 cur = check_key(key_top, opt);
538 /* present already */
539 cur->checked = 0; /* store only */
540 if(cur->value == NULL && val == NULL)
542 if(cur->value != NULL && val != NULL && strcmp(cur->value, val) == 0)
544 fprintf(stderr, "Warning: redefined %s\n", opt);
546 /* new or redefined key, check old key if present after previous usage */
547 key_top = cur = make_new_key(key_top, opt);
549 recordsz += 2; /* \n\0 */
550 if(recordsz > r_sz) {
551 record_buf = xrealloc(record_buf, r_sz=recordsz);
552 r_cmp = xrealloc(r_cmp, recordsz);
555 /* may be short count " " */
559 recordsz = sprintf(s, "#define %s\n", opt);
561 cur->value = bb_xstrdup(val);
562 recordsz = sprintf(s, "#define %s %s\n", opt, val);
566 recordsz = sprintf(s, "#undef %s\n", opt);
568 /* size_t -> ssize_t :( */
569 rw_ret = (ssize_t)recordsz;
570 /* trick, save first char KEY for do fast identify id */
571 first_chars[(int)*opt] = *opt;
573 /* key converting [A-Z_] -> [a-z/] */
574 for(p = opt; *p; p++) {
575 if(*p >= 'A' && *p <= 'Z')
580 p = bb_asprint("%s/%s.h", kp, opt);
581 cur->stored_path = opt = p;
585 /* Auto-create directories. */
588 if (access(opt, F_OK) != 0 && mkdir(opt, 0755) != 0)
589 bb_error_d("mkdir(%s): %m", opt);
595 if(st.st_size == (off_t)recordsz) {
596 fd = open(opt, O_RDONLY);
597 if(fd < 0 || read(fd, r_cmp, recordsz) < rw_ret)
598 bb_error_d("%s: %m", opt);
600 cmp_ok = memcmp(s, r_cmp, recordsz) == 0;
604 fd = open(opt, O_WRONLY|O_CREAT|O_TRUNC, 0644);
605 if(fd < 0 || write(fd, s, recordsz) < rw_ret)
606 bb_error_d("%s: %m", opt);
611 static int show_dep(int first, bb_key_t *k, const char *name)
615 for(cur = k; cur; cur = cur->next) {
616 if(cur->checked && cur->stored_path) {
618 printf("\n%s:", name);
623 printf(" %s", cur->stored_path);
630 static struct stat st_kp;
631 static int dontgenerate_dep;
634 parse_chd(const char *fe, const char *p, size_t dirlen)
639 static char *dir_and_entry;
640 static size_t dir_and_entry_sz;
645 df_sz = dirlen + strlen(fe) + 2; /* dir/file\0 */
646 if(df_sz > dir_and_entry_sz)
647 dir_and_entry = xrealloc(dir_and_entry, dir_and_entry_sz = df_sz);
649 sprintf(fp, "%s/%s", p, fe);
652 fprintf(stderr, "Warning: stat(%s): %m", fp);
655 if(S_ISREG(st.st_mode)) {
657 char *e = fp + df_sz - 3;
659 if(*e++ != '.' || (*e != 'c' && *e != 'h')) {
660 /* direntry is regular file, but is not *.[ch] */
663 for(cfl = configs; cfl; cfl = cfl->link) {
664 struct stat *config = (struct stat *)cfl->data;
666 if (st.st_dev == config->st_dev && st.st_ino == config->st_ino) {
667 /* skip already parsed configs.h */
671 /* direntry is *.[ch] regular file and is not configs */
672 if(!dontgenerate_dep) {
675 c_lex(fp, st.st_size);
676 fp = bb_simplify_path(fp);
679 e = strrchr(fp, '.') + 1;
682 first = show_dep(1, Ifound, fp);
683 first = show_dep(first, key_top, fp);
689 } else if(S_ISDIR(st.st_mode)) {
690 if (st.st_dev == st_kp.st_dev && st.st_ino == st_kp.st_ino)
691 return NULL; /* drop scan kp/ directory */
692 /* direntry is directory. buff is returned, begin of zero allocate */
693 dir_and_entry = NULL;
694 dir_and_entry_sz = 0;
697 /* hmm, direntry is device! */
701 /* from libbb but inline for fast */
702 static inline llist_t *llist_add_to(llist_t *old_head, char *new_item)
706 new_head = xmalloc(sizeof(llist_t));
707 new_head->data = new_item;
708 new_head->link = old_head;
713 static void scan_dir_find_ch_files(char *p)
722 dirs = llist_add_to(NULL, p);
723 /* emulate recursive */
727 dir = opendir(dirs->data);
729 fprintf(stderr, "Warning: opendir(%s): %m", dirs->data);
730 dirlen = strlen(dirs->data);
731 while ((de = readdir(dir)) != NULL) {
732 char *found_dir = parse_chd(de->d_name, dirs->data, dirlen);
735 d_add = llist_add_to(d_add, found_dir);
750 int main(int argc, char **argv)
757 /* for bb_simplify_path */
758 /* libbb xgetcwd(), this program have not chdir() */
759 unsigned path_max = 512;
761 s = xmalloc (path_max);
763 while (getcwd (s, path_max) == NULL) {
765 bb_error_d("getcwd: %m");
766 s = xrealloc (s, path_max += PATH_INCR);
771 while ((i = getopt(argc, argv, "I:c:dk:w")) > 0) {
774 Iop = llist_add_to(Iop, optarg);
777 s = bb_simplify_path(optarg);
778 configs = llist_add_to(configs, s);
781 dontgenerate_dep = 1;
785 bb_error_d("Hmm, why multiple -k?");
786 kp = bb_simplify_path(optarg);
797 kp = bb_simplify_path(INCLUDE_CONFIG_PATH);
798 /* globals initialize */
801 bb_error_d("stat(%s): %m", kp);
802 if(!S_ISDIR(st_kp.st_mode))
803 bb_error_d("%s is not directory", kp);
806 Iop = llist_add_to(Iop, LOCAL_INCLUDE_PATH);
807 if(configs == NULL) {
808 s = bb_simplify_path(INCLUDE_CONFIG_KEYS_PATH);
809 configs = llist_add_to(configs, s);
812 pagesizem1 = getpagesize() - 1;
813 id_s = xmalloc(mema_id);
814 for(i = 0; i < 256; i++) {
815 /* set unparsed chars for speed up of parser */
816 if(!ISALNUM(i) && i != CHR && i != STR &&
817 i != POUND && i != REM && i != BS)
818 first_chars[i] = ANY;
820 first_chars[i] = '-'; /* L_EOF */
821 /* trick for fast find "define", "include", "undef" */
822 first_chars_diu[(int)'d'] = (char)6; /* strlen("define"); */
823 first_chars_diu[(int)'i'] = (char)7; /* strlen("include"); */
824 first_chars_diu[(int)'u'] = (char)5; /* strlen("undef"); */
827 for(fl = configs; fl; fl = fl->link) {
830 if(stat(fl->data, &st))
831 bb_error_d("stat(%s): %m", fl->data);
832 c_lex(fl->data, st.st_size);
833 /* trick for fast comparing found files with configs */
834 fl->data = xrealloc(fl->data, sizeof(struct stat));
835 memcpy(fl->data, &st, sizeof(struct stat));
843 scan_dir_find_ch_files(*argv++);
845 scan_dir_find_ch_files(".");
850 /* partial and simplified libbb routine */
851 static void bb_error_d(const char *s, ...)
856 vfprintf(stderr, s, p);
862 static char *bb_asprint(const char *format, ...)
869 r = vasprintf(&out, format, p);
873 bb_error_d("bb_asprint: %m");
877 /* partial libbb routine as is */
878 static void *xmalloc(size_t size)
880 void *p = malloc(size);
883 bb_error_d(msg_enomem);
887 static void *xrealloc(void *p, size_t size) {
888 p = realloc(p, size);
890 bb_error_d(msg_enomem);
894 static char *bb_xstrdup(const char *s)
899 bb_error_d(msg_enomem);
903 static char *bb_simplify_path(const char *path)
908 start = bb_xstrdup(path);
910 /* is not libbb, but this program have not chdir() */
911 start = bb_asprint("%s/%s", pwd, path);
917 if (*s == '/') { /* skip duplicate (or initial) slash */
919 } else if (*s == '.') {
920 if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */
922 } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) {
925 while (*--p != '/'); /* omit previous dir */
934 if ((p == start) || (*p != '/')) { /* not a trailing slash */
935 ++p; /* so keep last character */