459 lines
12 KiB
C
459 lines
12 KiB
C
#define _GNU_SOURCE
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "fmt.h"
|
|
#include "fmt/fmt_strm_internal.c"
|
|
#include "fmt/fmt_str.c"
|
|
#include "mmap/mmap_read.c"
|
|
#include "mmap/mmap_unmap.c"
|
|
#include "open/open_read.c"
|
|
#include "open/open_trunc.c"
|
|
|
|
// this program is a dependency generator
|
|
// we have two kinds of dependencies, libdep and dep
|
|
// libdep: "TAI_OBJS=tai_add.o tai_now.o tai_pack.o tai_sub.o tai_uint.o tai_unpack.o"
|
|
// dep: "tai_add.o: tai/tai_add.c tai.h libowfat/uint64.h"
|
|
// we will open *.h and */*.c, looking for #include directives
|
|
|
|
// a holds pointers to filenames in a line for libdep
|
|
size_t al; // allocated and length for a
|
|
char* a[1024]; // array of pointers to strings for libdep
|
|
|
|
// buf holds the current line for libdep; used by objdup, flushlibdep
|
|
size_t bl; // allocated and length for buf
|
|
char buf[8196]; // will assert if not enough, increase size then
|
|
|
|
// curlibbuf holds the name of the library, used by flushlibdep
|
|
const char* curlib;
|
|
char curlibbuf[256];
|
|
|
|
// libs holds an array of the libdep strings for each library
|
|
char* libs[64]; // will assert if not enough, increase size then
|
|
size_t nl; // index of next free slot in libs
|
|
|
|
// For qsort
|
|
static int scmp(const void* a,const void* b) {
|
|
return strcmp(*(char**)a,*(char**)b)>0;
|
|
}
|
|
|
|
// Are we still working on the same library? Used by addlibdep
|
|
// If not we need to flush the string we have been building and make a new one
|
|
static bool samelib(const char* s) {
|
|
return s == curlib;
|
|
}
|
|
|
|
// Add current libcur_dep plus \n to libdep, and reset libdep_cur
|
|
static void flushlibdep(const char* libname) {
|
|
if (curlibbuf[0]) { // we were working on a library
|
|
size_t i;
|
|
// curlibbuf is "byte", make it "BYTE" instead
|
|
for (i=0; curlibbuf[i]>='a' && curlibbuf[i]<='z'; ++i)
|
|
curlibbuf[i]-=32;
|
|
|
|
// sort all the file names alphabetically
|
|
qsort(a,al,sizeof(a[0]),scmp);
|
|
|
|
// count bytes needed for all file names
|
|
size_t n=0;
|
|
// don't worry about integer overflows since all fit into global buf
|
|
for (i=0; i<al; ++i)
|
|
n+=strlen(a[i])+1;
|
|
|
|
// add 50 bytes scratch space
|
|
char s[n+50];
|
|
|
|
// s="BYTE"
|
|
char* c=stpcpy(s,curlibbuf);
|
|
// s="BYTE_OBJS="
|
|
c=stpcpy(c,"_OBJS=");
|
|
// add sorted file names
|
|
for (i=0; i<al; ++i) {
|
|
// we allocated enough for all the file names + 50
|
|
c=stpcpy(c,a[i]);
|
|
*c++=' ';
|
|
}
|
|
c[-1]='\n';
|
|
assert(nl<sizeof(libs)/sizeof(libs[0]));
|
|
libs[nl++]=strndup(s,c-s);
|
|
}
|
|
memset(curlibbuf,0,sizeof(curlibbuf));
|
|
if (libname) {
|
|
assert(strlen(libname)<sizeof(curlibbuf)-1);
|
|
strcpy(curlibbuf,libname);
|
|
curlib=libname;
|
|
}
|
|
}
|
|
|
|
const char* objext="o";
|
|
static size_t extlen;
|
|
|
|
// add filename to dependency list for current library (for libdep)
|
|
// called with foo.c from readdir, will add foo.o to dependencies string
|
|
static char* objdup(const char* s,size_t len) {
|
|
char* r;
|
|
r=memchr(s,'.',len); // we get called with *.c from a subdir
|
|
assert(r); // so they must contain a '.' (expect exactly one)
|
|
++r; // point to 1st char past '.'
|
|
size_t ofs=r-s;
|
|
if (extlen==0) extlen=strlen(objext); // initialize
|
|
size_t needed=ofs+extlen+1;
|
|
assert(len<256 && needed<sizeof(buf)-bl);
|
|
r=memcpy(buf+bl,s,ofs);
|
|
memcpy(buf+bl+ofs,objext,extlen+1); // +1 to include 0 terminator
|
|
bl+=needed;
|
|
return r;
|
|
}
|
|
|
|
enum { MAPLEN=1000 };
|
|
const char* map[MAPLEN];
|
|
size_t mapuse;
|
|
const char* headermap[256];
|
|
size_t headermapuse;
|
|
|
|
// Called when we parsed a whole file for #include directives and built
|
|
// the dependency string. Adds it to a map so we can in the end do
|
|
// transitive dependencies without having to open and parse header files
|
|
// multiple times.
|
|
static void adddepstolist(const char* s) {
|
|
// we a regular list for all files and a separate list for header
|
|
// files, because *.c files don't have other *.c files are dependency.
|
|
// we'll only need to search for *.h
|
|
const char* x=strchr(s,':');
|
|
if (x && x-s>3 && x[-2]=='.' && x[-1]=='h') {
|
|
// bounds check
|
|
if (headermapuse>=sizeof(headermap)/sizeof(headermap[0])) {
|
|
write(2,"increase size of headermap in n.c!\n",35);
|
|
exit(1);
|
|
}
|
|
// add entry
|
|
headermap[headermapuse++]=strdup(s);
|
|
} else {
|
|
// make sure we don't exceed MAPLEN
|
|
if (mapuse>=MAPLEN) {
|
|
write(2,"increase MAPLEN in n.c!\n",24);
|
|
exit(1);
|
|
}
|
|
// add entry to list
|
|
map[mapuse++]=strdup(s);
|
|
}
|
|
}
|
|
|
|
// Look for filename s in the map of dependencies
|
|
// Return pointer to map entry or NULL
|
|
static const char* finddep(const char* s,size_t l,const char** ret) {
|
|
size_t i;
|
|
const char** m=map;
|
|
size_t ml=mapuse;
|
|
if (l>3 && s[l-2]=='.' && s[l-1]=='h') {
|
|
// we put the headers in a separate list so we don't have to work so
|
|
// much here
|
|
m=headermap;
|
|
ml=headermapuse;
|
|
// if we are looking for "libowfat/byte.h", redirect to "byte.h"
|
|
if (l>10 && !memcmp(s,"libowfat/",9)) {
|
|
s+=9;
|
|
l-=9;
|
|
}
|
|
}
|
|
// the strings in the map look like this:
|
|
// "byte_copy.o: byte/byte_copy.c byte.h libowfat/compiler.h"
|
|
// so to find a file, we look for ':' as a separator
|
|
for (i=0; i<ml; ++i) {
|
|
const char* b=strchr(m[i],':');
|
|
if (!b)
|
|
return 0; // can't happen
|
|
if (l == (size_t)(b-m[i]) && !memcmp(s,m[i],l)) {
|
|
++b;
|
|
if (*b==' ') ++b;
|
|
if (ret) *ret=m[i];
|
|
return b;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// a self-growing bag of strings with deduplication
|
|
struct stringbag {
|
|
char* base;
|
|
size_t l,a;
|
|
};
|
|
|
|
// init
|
|
void init_stringbag(struct stringbag* sb) {
|
|
sb->base=malloc(4096);
|
|
if (sb->base==0) {
|
|
perror("malloc");
|
|
exit(111);
|
|
}
|
|
sb->l=0;
|
|
sb->a=4096;
|
|
}
|
|
|
|
// add s to stringbag (returns offset relative to sb->base)
|
|
static size_t addstring(struct stringbag* sb,const char* s,size_t l) {
|
|
assert(l<=127);
|
|
if (!sb->base) init_stringbag(sb);
|
|
size_t i;
|
|
for (i=0; i<sb->l; i+=sb->base[i]+1) {
|
|
if (sb->base[i] == (int)l && !memcmp(sb->base+i+1,s,l)) {
|
|
// found!
|
|
return i;
|
|
}
|
|
}
|
|
if (sb->l+l+2 > sb->a) {
|
|
sb->a += 4096;
|
|
char* x = realloc(sb->base, sb->a);
|
|
if (!x) {
|
|
perror("realloc");
|
|
exit(111);
|
|
}
|
|
sb->base = x;
|
|
}
|
|
sb->base[i]=l;
|
|
memcpy(sb->base+i+1, s, l+1);
|
|
sb->l = i+1+l;
|
|
return i;
|
|
}
|
|
|
|
// reset stringbag to empty for reuse
|
|
static void resetbag(struct stringbag* sb) {
|
|
sb->l=0;
|
|
}
|
|
|
|
// taken from str/str_chr.c
|
|
size_t str_chr(const char *in, char needle) {
|
|
size_t i;
|
|
for (i=0; in[i] && in[i]!=needle; ++i) ;
|
|
return i;
|
|
}
|
|
|
|
static void followdep(struct stringbag* sb, const char* s) {
|
|
// "uint64_pack.o: uint/uint64_pack.c uint64.h uint32.h"
|
|
|
|
const char* c=strchr(s,':');
|
|
if (!c || c[1]!=' ') return; // either not found or no dependencies
|
|
c+=2; // c = "uint/uint64_pack.c uint64.h uint32.h"
|
|
while (*c && *c!=' ') {
|
|
size_t n=str_chr(c,' ');
|
|
size_t prev=sb->l;
|
|
size_t ofs=addstring(sb,c,n);
|
|
if (ofs>=prev) { // was not the bag, need to recurse into it
|
|
const char* x;
|
|
if (finddep(c,n,&x))
|
|
followdep(sb,x);
|
|
}
|
|
c += n + (c[n]==' ');
|
|
}
|
|
|
|
}
|
|
|
|
static void dumpdep(const char* s) {
|
|
static struct stringbag sb;
|
|
if (!sb.base) init_stringbag(&sb);
|
|
|
|
followdep(&sb,s);
|
|
|
|
size_t i;
|
|
i=str_chr(s,' ');
|
|
printf("%.*s",(int)i,s);
|
|
for (i=0; i<sb.l; i+=sb.base[i]+1) {
|
|
printf(" %.*s",sb.base[i],sb.base+i+1);
|
|
}
|
|
putchar('\n');
|
|
|
|
resetbag(&sb);
|
|
}
|
|
|
|
// dump all dependencies
|
|
static void dumpdeps() {
|
|
size_t i;
|
|
for (i=0; i<mapuse; ++i) {
|
|
dumpdep(map[i]);
|
|
// puts(map[i]);
|
|
}
|
|
}
|
|
|
|
// pn == "byte/byte_copy.c"
|
|
// fn == "byte_copy.c"
|
|
// s == pointer to memory mapped file contents
|
|
// l == length of memory map
|
|
static void find_deps(const char* pn,const char* fn,const char* s,size_t l) {
|
|
// go through memory mapped file, look for #include
|
|
char d[1024];
|
|
// we want an output like "byte_copy.o: byte/byte_copy.c byte.h libowfat/compiler.h"
|
|
memset(d,0,sizeof d);
|
|
assert(strlen(fn)<256); // more like 10 in practice
|
|
|
|
char* c=stpcpy(d,fn);
|
|
assert(c>d); // empty filename!?
|
|
|
|
int header=0;
|
|
|
|
// filename ends with .c, but we want .o
|
|
if (c-d>2 && !strcmp(c-2,".h")) {
|
|
// it's a header file, we don't need to change .c to .o
|
|
header=1;
|
|
} else {
|
|
--c;
|
|
assert(c+extlen+5 < d+sizeof(d));
|
|
c=stpcpy(c,objext);
|
|
}
|
|
c=stpcpy(c,": ");
|
|
|
|
if (header) {
|
|
--c; // prevent " "
|
|
} else {
|
|
assert(c+strlen(pn)+5 < d+sizeof(d));
|
|
c=stpcpy(c,pn);
|
|
}
|
|
|
|
while (l) {
|
|
if (*s == '#') {
|
|
if (l>sizeof("#ifdef UNITTEST") && !memcmp(s,"#ifdef UNITTEST",15)) {
|
|
// we only care until #ifdef UNITTEST
|
|
break;
|
|
}
|
|
if (l>9 && !memcmp(s,"#include ",9)) {
|
|
// I never put spaces between # and ifdef
|
|
if (s[9]=='"') {
|
|
const char* f=s+10;
|
|
size_t i;
|
|
for (i=0; f+i<s+l && f[i]!='"'; ++i) ;
|
|
|
|
assert(c+i+5 < d+sizeof(d));
|
|
*c=' ';
|
|
c=memcpy(c+1,f,i)+i;
|
|
} else if (s[9]=='<') {
|
|
const char* f=s+10;
|
|
size_t i;
|
|
for (i=0; f+i<s+l && f[i]!='>'; ++i) ;
|
|
// we only care about <libowfat/...>
|
|
if (i>9 && !memcmp(f,"libowfat/",9)) {
|
|
assert(c+i+5 < d+sizeof(d));
|
|
*c=' ';
|
|
c=memcpy(c+1,f,i)+i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const char* n=memchr(s,'\n',l);
|
|
if (!n) break; // this was the last line
|
|
++n;
|
|
l-=(n-s);
|
|
s=n;
|
|
}
|
|
*c=0;
|
|
adddepstolist(d);
|
|
// *c='\n';
|
|
// write(1,d,c-d+1);
|
|
}
|
|
|
|
// fn is byte/byte_copy.c, f is byte_copy.c (both can be the same)
|
|
static void adddeps(const char* fn, const char* f) {
|
|
size_t l;
|
|
const char* x=mmap_read(fn,&l);
|
|
if (x) {
|
|
find_deps(fn,f,x,l);
|
|
mmap_unmap(x,l);
|
|
}
|
|
}
|
|
|
|
static void addlibdep(const char* p,size_t plen,const char* f) {
|
|
// if p is "byte", libdep_cur should start with BYTE_OBJS
|
|
// if it does, append f to libdep_cur
|
|
// if it doesn't, append libdep_cur to libdep, set up libdep_cur
|
|
size_t flen;
|
|
if (plen>255 || (flen=strlen(f))>255) return;
|
|
if (samelib(p)) {
|
|
a[al++]=objdup(f,flen);
|
|
} else {
|
|
flushlibdep(p);
|
|
a[0]=objdup(f,flen);
|
|
al=1;
|
|
}
|
|
|
|
adddeps(fmt_strm_alloca(p,"/",f),f);
|
|
}
|
|
|
|
int main() {
|
|
DIR* D=opendir(".");
|
|
assert(D);
|
|
struct dirent* d;
|
|
while ((d=readdir(D))) {
|
|
// we are interested in *.h and */*.c
|
|
if (d->d_type==DT_REG) { // *.h
|
|
const char* dot=strrchr(d->d_name,'.');
|
|
if (dot && !strcmp(dot,".h")) {
|
|
adddeps(d->d_name,d->d_name);
|
|
}
|
|
} else if (d->d_name[0]!='.' && d->d_type==DT_DIR &&
|
|
strcmp(d->d_name,"test") &&
|
|
strcmp(d->d_name,"examples")) {
|
|
size_t n=strlen(d->d_name);
|
|
DIR* DS=opendir(d->d_name);
|
|
if (DS) {
|
|
struct dirent* ds;
|
|
while ((ds=readdir(DS))) {
|
|
const char* dot=strrchr(ds->d_name,'.');
|
|
if (dot && !strcmp(dot,".c")) {
|
|
addlibdep(d->d_name,n,ds->d_name);
|
|
// printf("%s/%s\n",d->d_name,ds->d_name);
|
|
}
|
|
}
|
|
closedir(DS);
|
|
}
|
|
}
|
|
}
|
|
closedir(D);
|
|
flushlibdep(NULL);
|
|
|
|
// libs now as a list of lines for the file
|
|
// sort them alphabetically
|
|
qsort(libs,nl,sizeof(libs[0]),scmp);
|
|
// save string lengths in ll
|
|
size_t ll[sizeof(libs)/sizeof(libs[0])];
|
|
size_t i,n;
|
|
// count total bytes
|
|
for (i=n=0; i<nl; ++i)
|
|
n+=(ll[i]=strlen(libs[i]));
|
|
if (!n) {
|
|
fprintf(stderr,"no files?!\n");
|
|
return 111;
|
|
}
|
|
// copy all library strings in one buffer so we can compare against
|
|
// existing libdep file
|
|
char* x=malloc(n);
|
|
char* y=x;
|
|
for (i=0; i<nl; ++i) {
|
|
memcpy(y,libs[i],ll[i]);
|
|
y+=ll[i];
|
|
}
|
|
|
|
// overwrite libdep with new data if changed
|
|
size_t libdeplen=0;
|
|
const char* libdep=mmap_read("libdep",&libdeplen);
|
|
if (libdeplen != n || memcmp(libdep,x,n)) { // different contents
|
|
int fd=open_trunc("libdep");
|
|
assert(fd!=-1);
|
|
write(fd,x,n);
|
|
close(fd);
|
|
fprintf(stderr,"libdep updated\n");
|
|
} else
|
|
fprintf(stderr,"libdep is unchanged\n");
|
|
free(x); y=0;
|
|
|
|
#if 0
|
|
const char* z;
|
|
if (finddep("libowfat/buffer.h",17,&z))
|
|
dumpdep(z);
|
|
#endif
|
|
dumpdeps();
|
|
|
|
// write(1,libdep.s,libdep.len);
|
|
}
|