/* * Another dependences for Makefile mashine generator * * Copyright (C) 2005 by Vladimir Oleynik * * This programm do * 1) find #define KEY VALUE or #undef KEY from include/config.h * 2) save include/config/key*.h if changed after previous usage * 3) recursive scan from "./" *.[ch] files, but skip scan include/config/... * 4) find #include "*.h" and KEYs using, if not as #define and #undef * 5) generate depend to stdout * path/file.o: include/config/key*.h found_include_*.h * path/inc.h: include/config/key*.h found_included_include_*.h * This programm do not generate dependences for #include <...> * * Options: * -I local_include_path (include`s paths, default: LOCAL_INCLUDE_PATH) * -d (don`t generate depend) * -w (show warning if include files not found) * -k include/config (default: INCLUDE_CONFIG_PATH) * -c include/config.h (configs, default: INCLUDE_CONFIG_KEYS_PATH) */ #define LOCAL_INCLUDE_PATH "include" #define INCLUDE_CONFIG_PATH LOCAL_INCLUDE_PATH"/config" #define INCLUDE_CONFIG_KEYS_PATH LOCAL_INCLUDE_PATH"/config.h" #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include typedef struct BB_KEYS { char *keyname; const char *value; char *stored_path; int checked; struct BB_KEYS *next; } bb_key_t; /* partial and simplify libbb routine */ void bb_error_d(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); char * bb_asprint(const char *format, ...) __attribute__ ((format (printf, 1, 2))); /* stolen from libbb as is */ typedef struct llist_s { char *data; struct llist_s *link; } llist_t; llist_t *llist_add_to(llist_t *old_head, char *new_item); void *xrealloc(void *p, size_t size); void *xmalloc(size_t size); char *bb_xstrdup(const char *s); char *bb_simplify_path(const char *path); /* for lexical analyzier */ static bb_key_t *key_top; static void parse_inc(const char *include, const char *fname); static void parse_conf_opt(char *opt, const char *val, size_t rsz); #define CHECK_ONLY 0 #define MAKE_NEW 1 static bb_key_t *find_already(bb_key_t *k, const char *nk, int flg_save_new); #define yy_error_d(s) bb_error_d("%s:%d hmm, %s", fname, line, s) /* state */ #define S 0 /* start state */ #define STR '"' /* string */ #define CHR '\'' /* char */ #define REM '*' /* block comment */ #define POUND '#' /* # */ #define I 'i' /* #include preprocessor`s directive */ #define D 'd' /* #define preprocessor`s directive */ #define U 'u' /* #undef preprocessor`s directive */ #define LI 'I' /* #include "... */ #define DK 'K' /* #define KEY... (config mode) */ #define DV 'V' /* #define KEY "... or #define KEY '... */ #define NLC 'n' /* \+\n */ #define ANY '?' /* skip unparsed . */ /* [A-Z_a-z] */ #define ID(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') /* [A-Z_a-z0-9] */ #define ISALNUM(c) (ID(c) || (c >= '0' && c <= '9')) #define getc1() do { c = (optr >= oend) ? EOF : *optr++; } while(0) #define ungetc1() optr-- #define put_id(c) do { if(id_len == mema_id) \ id = xrealloc(id, mema_id += 16); \ id[id_len++] = c; } while(0) /* stupid C lexical analizator */ static void c_lex(const char *fname, int flg_config_include) { int c = EOF; /* stupid initialize */ int prev_state = EOF; int called; int state; int line; static size_t mema_id; char *id = xmalloc(mema_id=128); /* fist allocate */ size_t id_len = 0; /* stupid initialize */ char *val = NULL; unsigned char *optr, *oend; unsigned char *start = NULL; /* stupid initialize */ int fd; char *map; int mapsize; { /* stolen from mkdep by Linus Torvalds */ int pagesizem1 = getpagesize() - 1; struct stat st; fd = open(fname, O_RDONLY); if(fd < 0) { perror(fname); return; } fstat(fd, &st); if (st.st_size == 0) bb_error_d("%s is empty", fname); mapsize = st.st_size; mapsize = (mapsize+pagesizem1) & ~pagesizem1; map = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); if ((long) map == -1) bb_error_d("%s: mmap: %m", fname); /* hereinafter is my */ optr = (unsigned char *)map; oend = optr + st.st_size; } line = 1; called = state = S; for(;;) { if(state == LI || state == DV) { /* store "include.h" or config mode #define KEY "|'..."|' */ put_id(0); if(state == LI) { parse_inc(id, fname); } else { /* if(val[0] == '\0') yy_error_d("expected value"); */ parse_conf_opt(id, val, (optr - start)); } state = S; } if(prev_state != state) { prev_state = state; getc1(); } /* [ \t]+ eat first space */ while(c == ' ' || c == '\t') getc1(); if(c == '\\') { getc1(); if(c == '\n') { /* \\\n eat continued */ line++; prev_state = NLC; continue; } ungetc1(); c = '\\'; } if(state == S) { while(c <= ' ' && c != EOF) { /* [\000- ]+ */ if(c == '\n') line++; getc1(); } if(c == EOF) { /* <> */ munmap(map, mapsize); close(fd); return; } if(c == '/') { /* / */ getc1(); if(c == '/') { /* "//"[^\n]* */ do getc1(); while(c != '\n' && c != EOF); } else if(c == '*') { /* [/][*] */ called = S; state = REM; } /* eat / */ } else if(c == '#') { /* \"|\'|# */ start = optr - 1; state = c; } else if(c == STR || c == CHR) { /* \"|\'|# */ val = NULL; called = S; state = c; } else if(ISALNUM(c)) { /* [A-Z_a-z0-9] */ id_len = 0; do { /* [A-Z_a-z0-9]+ */ put_id(c); getc1(); } while(ISALNUM(c)); put_id(0); find_already(key_top, id, CHECK_ONLY); } else { /* . */ prev_state = ANY; } continue; } if(state == REM) { for(;;) { /* [^*]+ */ while(c != '*') { if(c == '\n') { /* \n */ if(called != S) yy_error_d("unexpected newline"); line++; } else if(c == EOF) yy_error_d("unexpected EOF"); getc1(); } /* [*] */ getc1(); if(c == '/') { /* [*][/] */ state = called; break; } } continue; } if(state == STR || state == CHR) { for(;;) { /* \n|<> */ if(c == '\n' || c == EOF) yy_error_d("unterminating"); if(c == '\\') { /* \\ */ getc1(); if(c != '\\' && c != '\n' && c != state) { /* another usage \ in str or char */ if(c == EOF) yy_error_d("unexpected EOF"); if(val) put_id(c); continue; } /* \\[\\\n] or \\\" or \\\' */ /* eat 2 char */ if(c == '\n') line++; else if(val) put_id(c); } else if(c == state) { /* \" or \' */ if(called == DV) put_id(c); state = called; break; } else if(val) put_id(c); /* . */ getc1(); } continue; } /* begin preprocessor states */ if(c == EOF) yy_error_d("unexpected EOF"); if(c == '/') { /* <#.*>/ */ getc1(); if(c == '/') yy_error_d("detect // in preprocessor line"); if(c == '*') { /* <#.*>[/][*] */ called = state; state = REM; continue; } /* hmm, #.*[/] */ yy_error_d("strange preprocessor line"); } if(state == '#') { static const char * const preproc[] = { "define", "undef", "include", "" }; const char * const *str_type; id_len = 0; while(ISALNUM(c)) { put_id(c); getc1(); } put_id(0); for(str_type = preproc; (state = **str_type); str_type++) { if(*id == state && strcmp(id, *str_type) == 0) break; } /* to S if another #directive */ ungetc1(); id_len = 0; /* common for save */ continue; } if(state == I) { if(c == STR) { /* \" */ val = id; state = STR; called = LI; continue; } /* another (may be wrong) #include ... */ ungetc1(); state = S; continue; } if(state == D || state == U) { while(ISALNUM(c)) { if(flg_config_include) { /* save KEY from #"define"|"undef" ... */ put_id(c); } getc1(); } if(!flg_config_include) { state = S; } else { if(!id_len) yy_error_d("expected identificator"); put_id(0); if(state == U) { parse_conf_opt(id, NULL, (optr - start)); state = S; } else { /* D -> DK */ state = DK; } } ungetc1(); continue; } if(state == DK) { /* #define (config mode) */ val = id + id_len; if(c == STR || c == CHR) { /* define KEY "... or define KEY '... */ put_id(c); called = DV; state = c; continue; } while(ISALNUM(c)) { put_id(c); getc1(); } ungetc1(); state = DV; continue; } } } static void show_usage(void) __attribute__ ((noreturn)); static void show_usage(void) { bb_error_d("Usage: [-I local_include_path] [-dw] " "[-k path_for_store_keys] [-s skip_file]"); } static const char *kp; static llist_t *Iop; static bb_key_t *Ifound; static int noiwarning; static llist_t *configs; static bb_key_t *find_already(bb_key_t *k, const char *nk, int flg_save_new) { bb_key_t *cur; for(cur = k; cur; cur = cur->next) { if(strcmp(cur->keyname, nk) == 0) { cur->checked = 1; return NULL; } } if(flg_save_new == CHECK_ONLY) return NULL; cur = xmalloc(sizeof(bb_key_t)); cur->keyname = bb_xstrdup(nk); cur->checked = 1; cur->next = k; return cur; } static int store_include_fullpath(char *p_i, bb_key_t *li) { struct stat st; int ok = 0; if(stat(p_i, &st) == 0) { li->stored_path = bb_simplify_path(p_i); ok = 1; } free(p_i); return ok; } static void parse_inc(const char *include, const char *fname) { bb_key_t *li; char *p_i; llist_t *lo; if((li = find_already(Ifound, include, MAKE_NEW)) == NULL) return; Ifound = li; if(include[0] != '/') { /* relative */ int w; const char *p; p_i = strrchr(fname, '/'); if(p_i == NULL) { p = "."; w = 1; } else { w = (p_i-fname); p = fname; } p_i = bb_asprint("%.*s/%s", w, p, include); if(store_include_fullpath(p_i, li)) return; } for(lo = Iop; lo; lo = lo->link) { p_i = bb_asprint("%s/%s", lo->data, include); if(store_include_fullpath(p_i, li)) return; } li->stored_path = NULL; if(noiwarning) fprintf(stderr, "%s: Warning: #include \"%s\" not found in specified paths\n", fname, include); } static void parse_conf_opt(char *opt, const char *val, size_t recordsz) { bb_key_t *cur = find_already(key_top, opt, MAKE_NEW); if(cur != NULL) { /* new key, check old key if present after previous usage */ char *s, *p; struct stat st; int fd; int cmp_ok = 0; static char *record_buf; static char *r_cmp; static size_t r_sz; recordsz += 2; /* \n\0 */ if(recordsz > r_sz) { record_buf = xrealloc(record_buf, r_sz=recordsz); r_cmp = xrealloc(r_cmp, recordsz); } s = record_buf; if(val) sprintf(s, "#define %s%s%s\n", opt, (*val ? " " : ""), val); else sprintf(s, "#undef %s\n", opt); /* may be short count " " */ recordsz = strlen(s); /* key converting [A-Z] -> [a-z] */ for(p = opt; *p; p++) { if(*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a'; if(*p == '_') *p = '/'; } p = bb_asprint("%s/%s.h", kp, opt); cur->stored_path = opt = p; while(*++p) { /* Auto-create directories. */ if (*p == '/') { *p = '\0'; if (stat(opt, &st) != 0 && mkdir(opt, 0755) != 0) bb_error_d("mkdir(%s): %m", opt); *p = '/'; } } if(stat(opt, &st) == 0) { /* found */ if(st.st_size == recordsz) { fd = open(opt, O_RDONLY); if(fd < 0 || read(fd, r_cmp, recordsz) != recordsz) bb_error_d("%s: %m", opt); close(fd); cmp_ok = memcmp(s, r_cmp, recordsz) == 0; } } if(!cmp_ok) { fd = open(opt, O_WRONLY|O_CREAT|O_TRUNC, 0644); if(fd < 0 || write(fd, s, recordsz) != recordsz) bb_error_d("%s: %m", opt); close(fd); } /* store only */ cur->checked = 0; if(val) { if(*val == '\0') { cur->value = ""; } else { cur->value = bb_xstrdup(val); } } else { cur->value = NULL; } key_top = cur; } else { /* present already */ for(cur = key_top; cur; cur = cur->next) { if(strcmp(cur->keyname, opt) == 0) { cur->checked = 0; if(cur->value == NULL && val == NULL) return; if((cur->value == NULL && val != NULL) || (cur->value != NULL && val == NULL) || strcmp(cur->value, val)) fprintf(stderr, "Warning: redefined %s\n", opt); return; } } } } static int show_dep(int first, bb_key_t *k, const char *a) { bb_key_t *cur; for(cur = k; cur; cur = cur->next) { if(cur->checked && cur->stored_path) { if(first) { const char *ext; if(*a == '.' && a[1] == '/') a += 2; ext = strrchr(a, '.'); if(ext && ext[1] == 'c' && ext[2] == '\0') { /* *.c -> *.o */ printf("\n%.*s.o:", (ext - a), a); } else { printf("\n%s:", a); } first = 0; } else { printf(" \\\n "); } printf(" %s", cur->stored_path); } cur->checked = 0; } return first; } static llist_t *files; static llist_t *filter_chd(const char *fe, const char *p, llist_t *pdirs) { const char *e; struct stat st; char *fp; char *afp; llist_t *cfl; if (*fe == '.') return NULL; fp = bb_asprint("%s/%s", p, fe); if(stat(fp, &st)) { fprintf(stderr, "Warning: stat(%s): %m", fp); free(fp); return NULL; } afp = bb_simplify_path(fp); if(S_ISDIR(st.st_mode)) { if(strcmp(kp, afp) == 0) { /* is autogenerated to kp/key* by previous usage */ free(afp); free(fp); /* drop scan kp/ directory */ return NULL; } free(afp); return llist_add_to(pdirs, fp); } if(!S_ISREG(st.st_mode)) { /* hmm, is device! */ free(afp); free(fp); return NULL; } e = strrchr(fe, '.'); if(e == NULL || !((e[1]=='c' || e[1]=='h') && e[2]=='\0')) { /* direntry is not directory or *.[ch] */ free(afp); free(fp); return NULL; } for(cfl = configs; cfl; cfl = cfl->link) { if(cfl->data && strcmp(cfl->data, afp) == 0) { /* parse configs.h */ free(afp); c_lex(fp, 1); free(fp); free(cfl->data); cfl->data = NULL; return NULL; } } free(fp); /* direntry is *.[ch] regular file */ files = llist_add_to(files, afp); return NULL; } static void scan_dir_find_ch_files(char *p) { llist_t *dirs; llist_t *d_add; llist_t *d; struct dirent *de; DIR *dir; dirs = llist_add_to(NULL, p); /* emulate recursive */ while(dirs) { d_add = NULL; while(dirs) { dir = opendir(dirs->data); if (dir == NULL) fprintf(stderr, "Warning: opendir(%s): %m", dirs->data); while ((de = readdir(dir)) != NULL) { d = filter_chd(de->d_name, dirs->data, d_add); if(d) d_add = d; } closedir(dir); if(dirs->data != p) free(dirs->data); d = dirs; dirs = dirs->link; free(d); } dirs = d_add; } } int main(int argc, char **argv) { int generate_dep = 1; char *s; int i; llist_t *fl; while ((i = getopt(argc, argv, "I:c:dk:w")) > 0) { switch(i) { case 'I': Iop = llist_add_to(Iop, optarg); break; case 'c': s = bb_simplify_path(optarg); configs = llist_add_to(configs, s); break; case 'd': generate_dep = 0; break; case 'k': if(kp) bb_error_d("Hmm, why multiple -k?"); kp = bb_simplify_path(optarg); break; case 'w': noiwarning = 1; break; default: show_usage(); } } if(argc > optind) show_usage(); /* defaults */ if(kp == NULL) kp = bb_simplify_path(INCLUDE_CONFIG_PATH); if(Iop == NULL) Iop = llist_add_to(Iop, LOCAL_INCLUDE_PATH); if(configs == NULL) { s = bb_simplify_path(INCLUDE_CONFIG_KEYS_PATH); configs = llist_add_to(configs, s); } scan_dir_find_ch_files("."); for(fl = files; fl; fl = fl->link) { c_lex(fl->data, 0); if(generate_dep) { i = show_dep(1, Ifound, fl->data); i = show_dep(i, key_top, fl->data); if(i == 0) putchar('\n'); } } return 0; } void bb_error_d(const char *s, ...) { va_list p; va_start(p, s); vfprintf(stderr, s, p); va_end(p); putc('\n', stderr); exit(1); } void *xmalloc(size_t size) { void *p = malloc(size); if(p == NULL) bb_error_d("memory exhausted"); return p; } void *xrealloc(void *p, size_t size) { p = realloc(p, size); if(p == NULL) bb_error_d("memory exhausted"); return p; } char *bb_asprint(const char *format, ...) { va_list p; int r; char *out; va_start(p, format); r = vasprintf(&out, format, p); va_end(p); if (r < 0) bb_error_d("bb_asprint: %m"); return out; } llist_t *llist_add_to(llist_t *old_head, char *new_item) { llist_t *new_head; new_head = xmalloc(sizeof(llist_t)); new_head->data = new_item; new_head->link = old_head; return(new_head); } char *bb_xstrdup(const char *s) { char *r = strdup(s); if(r == NULL) bb_error_d("memory exhausted"); return r; } char *bb_simplify_path(const char *path) { char *s, *start, *p; if (path[0] == '/') start = bb_xstrdup(path); else { static char *pwd; if(pwd == NULL) { /* is not libbb, but this program have not chdir() */ unsigned path_max = 512; char *cwd = xmalloc (path_max); #define PATH_INCR 32 while (getcwd (cwd, path_max) == NULL) { if(errno != ERANGE) bb_error_d("getcwd: %m"); path_max += PATH_INCR; cwd = xrealloc (cwd, path_max); } pwd = cwd; } start = bb_asprint("%s/%s", pwd, path); } p = s = start; do { if (*p == '/') { if (*s == '/') { /* skip duplicate (or initial) slash */ continue; } else if (*s == '.') { if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */ continue; } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) { ++s; if (p > start) { while (*--p != '/'); /* omit previous dir */ } continue; } } } *++p = *s; } while (*++s); if ((p == start) || (*p != '/')) { /* not a trailing slash */ ++p; /* so keep last character */ } *p = 0; return start; }