/* © Copyright Steve Simon 2008 */
/* mkmk.c - human readable mkfile generator for APE apps */
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
#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, "</sys/src/cmd/mkmany\n");
Bprint(op, "\n");
Bprint(op, "CC=pcc\n");
Bprint(op, "LD=pcc\n");
cflags();
Bprint(op, "\n");
dependencies();
}
static void
mkone(void)
{
Bprint(op, "TARG=%s\n", Target);
ofiles();
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, "</sys/src/cmd/mkone\n");
Bprint(op, "\n");
Bprint(op, "CC=pcc\n");
Bprint(op, "LD=pcc\n");
cflags();
Bprint(op, "\n");
dependencies();
}
static void
mklib(void)
{
Bprint(op, "LIB=%s.a$O\n", Target);
ofiles();
yacclex("LFILES=", Tlex);
yacclex("YFILES=", Tyacc);
hfiles();
cleanfiles();
Bprint(op, "\n");
Bprint(op, "</sys/src/cmd/mklib\n");
Bprint(op, "\n");
Bprint(op, "CC=pcc\n");
Bprint(op, "LD=pcc\n");
cflags();
Bprint(op, "\n");
dependencies();
Bprint(op, "\n");
Bprint(op, "nuke:V:\n");
Bprint(op, "\tmk clean\n");
Bprint(op, "\trm -f $LIB\n");
}
static void
mksyslib(void)
{
if(Apemode)
Bprint(op, "LIB=/$objtype/lib/ape/%s.a$O\n", Target);
else
Bprint(op, "LIB=/$objtype/lib/%s.a$O\n", Target);
ofiles();
yacclex("LFILES=", Tlex);
yacclex("YFILES=", Tyacc);
hfiles();
cleanfiles();
Bprint(op, "\n");
Bprint(op, "</sys/src/cmd/mksyslib\n");
Bprint(op, "\n");
Bprint(op, "CC=pcc\n");
Bprint(op, "LD=pcc\n");
cflags();
Bprint(op, "\n");
dependencies();
Bprint(op, "nuke:V:\n");
Bprint(op, "\tmk clean\n");
Bprint(op, "\trm -f $LIB\n");
}
static void
noptargets(void)
{
Bprint(op, "default:VQ:\n");
Bprint(op, " ;\n\n");
Bprint(op, "install:VQ:\n");
Bprint(op, " ;\n\n");
Bprint(op, "clean:VQ:\n");
Bprint(op, " ;\n\n");
Bprint(op, "nuke:VQ:\n");
Bprint(op, " ;\n\n");
}
/*
* This is typically a huge efficency win, we check to see if we have
* read a file and extracted its dependency list already, if so we don't
* re-read it.
*
* It can lead to erronous results as silly games with #ifdef can result
* to a header file pulling in different clild headers depending on what
* #defines the parent source file has active.
*
* Because of these problems we give the user the -S (slow) option
* which disables the optimisation.
*/
static int
clonedep(Depend **dep, char *file)
{
Depend *d, *c;
if(Slow)
return -1;
for(d = Dep; d; d = d->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 <opts> -m|-o|-l|-L [name]\n", argv0);
fprint(2, " -m many applications\n");
fprint(2, " -o <app> one application\n");
fprint(2, " -l <lib> a library\n");
fprint(2, " -s <syslib> a system library\n");
fprint(2, " -w <n> collumn for wordwrap\n");
fprint(2, " -S slow - disable include optimisation\n");
fprint(2, " -I <dir> include directory\n");
fprint(2, " -D <name>[=value] define preprocessor macro\n");
fprint(2, " -U <name> 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 <file> exclude this source file (can appear multiple times)\n");
fprint(2, " -h <flags> LDFLAGS passed to linker (can appear multiple times)\n");
fprint(2, " -H <lib> libraries to link with\n");
fprint(2, " -C <file> 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, "</$objtype/mkfile\n");
dirs();
if(func){
func();
unreferenced();
}
rc = directories();
if(! func && rc == -1)
noptargets();
manpages();
if(Debug){
fprint(2, "\nvariable value\n");
dumpvars();
fprint(2, "\nfile refs type global\n");
dumpfiles();
fprint(2, "\nsource dependencies...\n");
dumpdeps(Dep, 0);
}
free(Objname);
exits(0);
}
|