/* © Copyright Steve Simon 2008 */ /* mkmk.c - human readable mkfile generator for APE apps */ #include #include #include #include #include "mkmk.h" #include "objtab.h" int Debug = 0; /* debug */ int Wrap = Defwrap; /* word wrap collumn */ Biobuf *op; /* output */ List *Incdir = nil; /* -I args from command line */ char *Objname = nil; /* $objtype from environment */ char Objletter; /* Object letter */ int Autoconf = 0; /* generate a config.h and fill with defines */ static int Slow = 0; /* slow but accurate mode */ static int Apemode = 0; /* to be used in ape environment only */ static Depend *Dep = nil; /* dependency graph */ static char *Target = nil; /* output target/library name */ static File *Files = nil; /* file list */ static List *Cleanfiles = nil; /* -C args from command line */ static List *Defs = nil; /* -D args from command line */ static List *Undefs = nil; /* -U args from command line */ static List *Cflags = nil; /* -c args from command line */ static List *Exclude = nil; /* -x args from command line */ static List *Ldflags = nil; /* -h args from command line */ static List *Libs = nil; /* -H args from command line */ static List *Cppinc = nil; /* Cpp system include dirs */ static List *F77inc = nil; /* f77 system include dirs */ static Lang Langtab[] = { { ".c", Tsrc, &Cppinc, parse_cpp }, { ".s", Tsrc, &Cppinc, parse_cpp }, { ".h", Thdr, &Cppinc, parse_cpp }, { ".y", Tyacc, &Cppinc, parse_cpp }, { ".l", Tlex, &Cppinc, parse_cpp }, { ".1", Tman1, nil, nil }, { ".man", Tman1, nil, nil }, { ".3", Tman1, nil, nil }, { ".f", Tsrc, &F77inc, parse_f77 }, { ".f77", Tsrc, &F77inc, parse_f77 }, { ".r", Tsrc, &F77inc, parse_f77 }, // { ".H", Thdr, &Cppinc, parse_cpp }, // { ".C", Tsrc, &Cppinc, parse_cpp }, // { ".cpp", Tsrc, &Cppinc, parse_cpp }, // { ".hpp", Thdr, &Cppinc, parse_cpp }, // { ".c++", Tsrc, &Cppinc, parse_cpp }, // { ".h++", Thdr, &Cppinc, parse_cpp }, // { ".rat", Tsrc, &F77inc, parse_rat }, // { ".rat4", Tsrc, &F77inc, parse_rat }, // { ".f90", Tsrc, &F90inc, parse_f90 }, // { ".p", Tyacc, &Pasinc, parse_pas }, // { ".pas", Tsrc, &Pasinc, parse_pas }, }; #pragma varargck type "t" char* static void dumpdeps(Depend *dep, int depth) { Depend *d; for(d = dep; d; d = d->next){ fprint(2, "%*.s%-20s \n", depth, "", d->file->name); if(d->child) dumpdeps(d->child, depth +4); } } static void dumpfiles(void) { File *f; for(f = Files; f; f = f->next) fprint(2, "%-20s %-4d %-4d %s\n", f->name, f->ref, f->type, (f->global)? "y": "n"); } static int issrc(File *f) { switch(f->type){ case Tyacc: return 1; case Tlex: return 1; case Tsrc: return 1; } return 0; } static void prdeps(int *colp, int isglobal, Depend *root) { Depend *c; for(c = root; c; c = c->next){ if(c->file->sysdir) continue; if(!isglobal && c->file->global) continue; if(isglobal) c->file->global = 1; if(c->child) prdeps(colp, isglobal, c->child); wrap(colp, "%s ", c->file->name); c->file->ref++; } } static void dependencies(void) { Depend *d, *c; int col, found; for(d = Dep; d; d = d->next){ if(! issrc(d->file)) continue; found = 0; for(c = d->child; c; c = c->next) if(! c->file->global){ found = 1; break; } if(found){ col = 0; wrap(&col, "%t.$O: ", d->file->name); prdeps(&col, 0, d->child); Bprint(op, "\n"); } } } static void ofiles(void) { int col; File *f; List *l; col = 0; wrap(&col, "OFILES="); for(f = Files; f; f = f->next){ for(l = Exclude; l; l = l->next){ if(strcmp(l->name, f->name) == 0) break; } if(l) continue; switch(f->type){ case Tsrc: wrap(&col, "%t.$O ", f->name); f->ref++; break; case Tyacc: wrap(&col, "y.tab.$O "); f->ref++; break; case Tlex: wrap(&col, "lex.yy.$O "); f->ref++; break; default: break; } } Bprint(op, "\n"); } static void yacclex(char *name, int type) { File *f; int col; for(f = Files; f; f = f->next) if(f->type == type) break; if(f == nil) return; col = 0; wrap(&col, "%s", name); for(f = Files; f; f = f->next) if(f->type == type){ wrap(&col, "%s ", f->name); f->ref++; } Bprint(op, "\n"); } static void ldflags(void) { List *l; int col; if(Ldflags){ col = 0; wrap(&col, "LDFLAGS="); for (l = Ldflags; l; l = l->next) wrap(&col, "%s ", l->name); Bprint(op, "\n"); } if(Libs){ col = 0; wrap(&col, "LIB="); for (l = Libs; l; l = l->next) wrap(&col, "%s ", l->name); Bprint(op, "\n"); } } static void manpages(void) { File *f; int sect; char *ape; /* any manpages to print ? */ for(f = Files; f; f = f->next) if(f->type == Tman1 || f->type == Tman3) break; if(f == nil) return; ape = ""; if(Apemode) ape = "ape"; Bprint(op, "install:V:\n"); for(f = Files; f; f = f->next){ switch(f->type){ case Tman1: sect = 1; break; case Tman3: sect = 2; break; default: continue; } Bprint(op, "\tcp %s /sys/man/%d%s/%t\n", f->name, sect, ape, f->name); f->ref++; } Bprint(op, "\n"); } static void hfiles(void) { int col, found, nsrc, n; Depend *d, *c, *d1, *c1; col = 0; found = 0; /* count number of source files */ nsrc = 0; for(d = Dep; d; d = d->next) if(issrc(d->file)) nsrc++; if(nsrc == 0) return; /* find the first source file */ for(d = Dep; d; d = d->next) if(issrc(d->file)) break; /* for each child of this source file */ for(c = d->child; c; c = c->next){ /* local headers only */ if(c->file->sysdir) continue; n = 0; /* for each source file */ for(d1 = Dep; d1; d1 = d1->next){ if(! issrc(d1->file)) continue; /* check that this header is included by them also */ for(c1 = d1->child; c1; c1 = c1->next) if(strcmp(c->file->name, c1->file->name) == 0){ n++; break; } } //print("# HFILES: file=%s refs=%d/%d\n", c->file->name, n, nsrc); if((double)n / (double)nsrc > Most){ if(!found){ wrap(&col, "HFILES="); found = 1; } wrap(&col, "%s ", c->file->name); prdeps(&col, 1, c->child); c->file->global = 1; } } if(found) Bprint(op, "\n"); } static void dirs(void) { File *f; int col, found; found = 0; for(f = Files; f; f = f->next) if(f->type == Tdir) found = 1; if(! found) return; col = 0; wrap(&col, "DIRS="); for(f = Files; f; f = f->next) if(f->type == Tdir) wrap(&col, "%s ", f->name); Bprint(op, "\n"); } static void unreferenced(void) { File *f; for(f = Files; f; f = f->next) if((issrc(f) || f->type == Thdr) && !f->ref) fprint(2, "%s: %s not referenced\n", argv0, f->name); } static void gnu_configh(void) { List *l; char *p; Biobuf *bp; if((bp = Bopen("config.h", OWRITE)) == nil) sysfatal("config.h - cannot open\n"); Bprint(bp, "/* Auto generated file, do not edit */\n\n"); for (l = Lochints; l; l = l->next){ if((p = strchr(l->name, '=')) != nil){ *p = ' '; Bprint(bp, "#define %s\n", l->name); } else Bprint(bp, "#define %s 1\n", l->name); } Bterm(bp); } static void cflags(void) { int col; List *l; col = 0; wrap(&col, "CFLAGS=-c "); for (l = Incdir; l; l = l->next) wrap(&col, "-I%s ", l->name); for (l = Cflags; l; l = l->next) wrap(&col, "%s ", l->name); for (l = Defs; l; l = l->next) wrap(&col, "-D%s ", l->name); for (l = Undefs; l; l = l->next) wrap(&col, "-U%s ", l->name); for (l = Glbhints; l; l = l->next) wrap(&col, "-D%s ", l->name); if(Autoconf){ gnu_configh(); /* * Sometimes autoconf appears self-doubting. Even though the * code won't compile without config.h we need to set this * define on the command line to tell it to use config.h. */ } else{ for (l = Lochints; l; l = l->next) wrap(&col, "-D%s ", l->name); } Bprint(op, "\n"); } static int directories(void) { File *f; int ndir, nsrc; ndir = nsrc = 0; for(f = Files; f; f = f->next){ if(f->type == Tdir) ndir++; if(issrc(f)) nsrc++; } if(! ndir) return -1; Bprint(op, "\n"); Bprint(op, "all:V:\n"); Bprint(op, " for (i in $DIRS)\n"); Bprint(op, " @{ cd $i; mk }\n"); Bprint(op, "\n"); if(Libs){ Bprint(op, "$LIB:V:\n"); Bprint(op, " for (i in $LIB)\n"); Bprint(op, " @{ cd `{echo $i|sed 's@^(.*)/.*$@\\1@'}; mk }\n"); Bprint(op, "\n"); } Bprint(op, "install:V:\n"); Bprint(op, " for (i in $DIRS)\n"); Bprint(op, " @{ cd $i; mk $target }\n"); Bprint(op, "\n"); Bprint(op, "clean:V:\n"); Bprint(op, " for (i in $DIRS)\n"); Bprint(op, " @{ cd $i; mk $target }\n"); if(nsrc) Bprint(op, " rm -f [$OS].* *.[$OS] $CLEANFILES\n"); Bprint(op, "\n"); Bprint(op, "nuke:V:\n"); Bprint(op, " for (i in $DIRS)\n"); Bprint(op, " @{ cd $i; mk $target }\n"); if(nsrc) Bprint(op, " rm -f *.[$OS] y.tab.? y.debug y.output [$OS].$TARG $TARG\n"); Bprint(op, "\n"); return 0; } static void cleanfiles(void) { List *l; int col; if(Cleanfiles == nil) return; col = 0; wrap(&col, "CLEANFILES="); for (l = Cleanfiles; l; l = l->next) wrap(&col, "%s ", l->name); } static void manytarg(void) { int col; File *f; col = 0; wrap(&col, "TARG="); for(f = Files; f; f = f->next) if(issrc(f)){ wrap(&col, "$O.%t ", f->name); f->ref++; } Bprint(op, "\n"); } static void mkmany(void) { manytarg(); yacclex("LFILES=", Tlex); yacclex("YFILES=", Tyacc); ldflags(); if(Apemode) Bprint(op, "BIN=/$objtype/bin/ape\n"); else Bprint(op, "BIN=/$objtype/bin\n"); hfiles(); cleanfiles(); Bprint(op, "\n"); Bprint(op, "next){ for(c = d->child; c; c = c->next) if(strcmp(c->file->name, file) == 0 && c->child){ *dep = c->child; return 0; } } return -1; } static int doscan(Depend **dep, char *file, Lang *l, char *efile, int eline) { State *s; char *buf, *path; static depth = 0; if(clonedep(dep, file) != -1){ if(Debug) fprint(2, "skipped: clone %s\n", file); return 0; } if(depth++ > Maxdepth){ /* catch circular dependencies */ fprint(2, "%s: %s:%d too many nested includes, skipping file\n", argv0, efile, eline); depth--; return -1; } *dep = nil; s = emallocz(sizeof(State), 1); if((s->bp = Bopen(file, OREAD)) == nil){ depth--; return -1; } s->dep = dep; s->file = file; s->langincdirs = *l->incdirp; l->parser(s); Bterm(s->bp); free(s); depth--; return 0; } int scanfile(Depend **dep, File *f, char *file, int line) { int rc; Lang *l; char *p; Depend *d; if(f->type == Tdir) return 0; if((p = strrchr(f->name, '.')) == nil){ if(Debug) fprint(2, "%s no extension, ignored\n", f->name); return -1; } for(l = Langtab; l < &Langtab[nelem(Langtab)]; l++) if(strcmp(p, l->ext) == 0) break; if(l >= &Langtab[nelem(Langtab)]){ if(Debug) fprint(2, "%s unknown type, ignored\n", f->name); return -1; } f->type = l->type; d = emallocz(sizeof(Depend), 1); d->file = f; if(line == -1 && !issrc(f)) /* don't scan headers unless they are included */ return 0; rc = 0; if(l->parser) if((rc = doscan(&d->child, f->name, l, file, line)) != -1) f->ref++; d->next = *dep; *dep = d; return rc; } File * addfile(char *name, int sysdir) { File **p, *f; cleanname(name); for(p = &Files, f = Files; f; p = &f->next, f = f->next) if(strcmp(name, f->name) == 0) return f; f = emallocz(sizeof(File)+strlen(name)+1, 1); f->name = (char *)&f[1]; strcpy(f->name, name); f->sysdir = sysdir; *p = f; return f; } static int scandir(char *path) { List *l; File *f; int fd, n; Dir *dir, *d; if((fd = open(path, OREAD)) == -1) sysfatal("%s - cannot open %r\n", path); n = dirreadall(fd, &dir); close(fd); f = nil; for(d = dir; d < &dir[n]; d++){ for(l = Exclude; l; l = l->next){ if(strcmp(l->name, d->name) == 0) break; } if(l) continue; f = addfile(d->name, 0); if(d->mode & DMDIR) f->type = Tdir; } free(dir); return (f == nil)? -1: 0; } static void usage(void) { fprint(2, "usage: %s -m|-o|-l|-L [name]\n", argv0); fprint(2, " -m many applications\n"); fprint(2, " -o one application\n"); fprint(2, " -l a library\n"); fprint(2, " -s a system library\n"); fprint(2, " -w collumn for wordwrap\n"); fprint(2, " -S slow - disable include optimisation\n"); fprint(2, " -I include directory\n"); fprint(2, " -D [=value] define preprocessor macro\n"); fprint(2, " -U un-define preprocessor macro\n"); fprint(2, " -B add -B option to CFLAGS (a common case)\n"); fprint(2, " -c 'args' aditional CFLAGS (can appear multiple times)\n"); fprint(2, " -a APE mode - add /ape/ to destination directories\n"); fprint(2, " -x exclude this source file (can appear multiple times)\n"); fprint(2, " -h LDFLAGS passed to linker (can appear multiple times)\n"); fprint(2, " -H libraries to link with\n"); fprint(2, " -C add file to CLEANFILES\n"); fprint(2, " -A autoconf mode (put CFLAGS in config.h)\n"); exits("usage"); } void main(int argc, char *argv[]) { Dir *d; File *f; List *l; int i, rc; Biobuf bout; void (*func)(void); char tmp[128], *name, *val; if((Objname = getenv("objtype")) == nil) sysfatal("/env/objtype not set"); for(i = 0; i < nelem(Objtab); i++) if(strcmp(Objtab[i].name, Objname) == 0){ Objletter = Objtab[i].letter; break; } if(Objletter == 0) sysfatal("%s - unknown object type", Objname); op = &bout; Binit(&bout, OWRITE, 1); quotefmtinstall(); fmtinstall('t', trimfmt); /* initialise our environment just as pcc(1) does */ setsym("__STDC__", "1"); snprint(tmp, sizeof(tmp), "/%s/include/ape", Objname); addlist(&Cppinc, tmp); addlist(&Cppinc, "/sys/include/ape"); func = nil; Files = nil; ARGBEGIN{ case 'd': Debug++; break; case 'o': func = mkone; Target = EARGF(usage()); break; case 's': func = mksyslib; Target = EARGF(usage()); break; case 'l': func = mklib; Target = EARGF(usage()); break; case 'm': func = mkmany; break; case 'w': Wrap = atoi(EARGF(usage())); break; case 'B': addlist(&Cflags, "-B"); break; case 'c': addlist(&Cflags, EARGF(usage())); break; case 'D': name = EARGF(usage()); addlist(&Defs, name); if((val = strchr(name, '=')) != nil) *val++ = 0; else val = "1"; setsym(name, val); break; case 'U': name = EARGF(usage()); rmsym(name); addlist(&Undefs, name); break; case 'I': addlist(&Incdir, EARGF(usage())); break; case 'S': Slow = 1; break; case 'a': Apemode = 1; break; case 'A': Autoconf = 1; addlist(&Glbhints, "HAVE_CONFIG_H"); break; case 'h': addlist(&Ldflags, EARGF(usage())); break; case 'H': addlist(&Libs, EARGF(usage())); break; case 'x': addlist(&Exclude, EARGF(usage())); break; case 'C': addlist(&Cleanfiles, EARGF(usage())); break; default: usage(); }ARGEND; if(argc == 0) scandir("."); else for(i = 0; i < argc; i++){ if((d = dirstat(argv[i])) == nil){ fprint(2, "%s: %s - cannot stat %r\n", argv0, argv[i]); continue; } f = addfile(d->name, 0); if(d->mode & DMDIR) f->type = Tdir; free(d); } if(Files == nil){ fprint(2, "%s: no files found\n", argv0); exits("no files"); } Dep = nil; for(f = Files; f; f = f->next) scanfile(&Dep, f, "?", -1); if(Autoconf && access("config.h", 0) != -1) sysfatal("Autoconf mode but config.h already exists\n"); if(Autoconf) gnu_pkgvers(); Bprint(op, "