/* CMAKE - simplified dependency builder

Determines dependencies by looking at lines in the program beginning with
//LIBS or //USES, as well as #include "..." directives.  Sub-dependencies
will be built first as necessary; you can nest //USES directives.

   #include "file.h"	Recompile if file.h is newer.

   //USES foo bar	Build foo.o & bar.o from foo.c & bar.c (if the .o
			files don't exist or they're older than the .c
			files) before linking the executable.

   //ASM baz		Assemble baz.asm to baz.o (using NASM)

   //LIBS m vga vgagl	Link with -lm -lvga -lvgagl

Notes:
   #include "..." directives are assumed to be at the beginning of a line.
   Compiler & assembler commands are hardcoded (gcc & nasm)
   Circular dependencies will cause CMAKE to barf

REV   DATE    REVISION NOTES
 -   9/13/02  Initial release - considered finished
*/



#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

#define err(s...)  {printf("CMAKE ERROR: "); printf(s); putchar('\n'); exit(1);}

char objs[1024];
char libs[1024];

// Append src to dst, if it's not already in the list.
add(dst,src)
	char *dst, *src;
{
	char *p=dst, *q;
	int l= strlen(src);

	// Search for src in dst (whole word)
	while (*p) {
		if (!strncmp(p,src,l) && !p[l]) return;
		for ( ; *p && !isspace(*p); p++); // skip word
		for ( ; *p && isspace(*p); p++);  // skip blanks
	}

	// If not found, append src to dst
	*p++ = ' ';
	strcpy(p,src);
}

time_t
filedate(char *fn) {
	struct stat buf;
	if (-1 == stat(fn, &buf)) return 0;
	return buf.st_mtime;
}

// Build .o from .asm
nasm(fn, t) 
	char *fn;	// basename
	time_t t;	// returns 1 if any dependency is newer than t
{
	char fno[256];	// basename.o
	char fnc[256];  // basename.asm
	int result=0;
	time_t tc, to;
	
	snprintf(fnc,256,"%s.asm",fn);
	snprintf(fno,256,"%s.o",fn);
	add(objs,fno);
	tc= filedate(fnc);
	to= filedate(fno);
// printf("asm\t%08X\t%s\n",to,fno);
	result= (tc>to);
	if (result) {
		char cmd[256];	// compiler command
		snprintf(cmd,256,"nasm -felf %s",fnc);
		puts(cmd);
		if (system(cmd)) exit(1);
	}
	return result || (to>t);
}

// Check dependencies in a .c or .h file
dep(fn, t)
	char *fn;	// full name (ie, .c .h .cpp)
	time_t t;	// returns 1 if any dependency is newer than t
{
	char buf[256], *p;
	FILE *f;
	int result=0;

// printf("  dep\t%08X\t%s\n",filedate(fn),fn);

	f= fopen(fn,"r");
	if (!f) err("Can't open '%s'", fn);
	while (fgets(buf,256,f)) {
		if (!strncasecmp(buf,"//LIBS",6))
		  for(p=buf+6; *p; ) {
		 	char s[256], *q;
			for ( ; isspace(*p); p++);
			for (q=p; !isspace(*q); q++);
			*q++= 0;
			snprintf(s,256,"-l%s",p);
			p= q;
			add(libs,s);
		}
		else if (!strncasecmp(buf,"//USES",6))
		  for(p=buf+6; *p; ) {
		  	char *q;
			for ( ; isspace(*p); p++);
			for (q=p; !isspace(*q); q++);
			*q++= 0;
			result |= build(p,t);
			p= q;
		}
		else if (!strncasecmp(buf,"//ASM",5))
		  for(p=buf+6; *p; ) {
		  	char *q;
			for ( ; isspace(*p); p++);
			for (q=p; !isspace(*q); q++);
			*q++= 0;
			result |= nasm(p,t);
			p= q;
		}
		else if (!strncasecmp(buf,"#include",8)) {
			for (p=buf+8; isspace(*p); p++);
			if (*p++ == '"') {
				strtok(p,"\"");
				if (filedate(p) > t) result=1;
				result |= dep(p,t);
			}
		}
	}
	fclose(f);
	return result;
}

// Build .o from .c
build(fn, t) 
	char *fn;	// basename
	time_t t;	// returns 1 if any dependency is newer than t
{
	char fno[256];	// basename.o
	char fnc[256];  // basename.c
	int result=0;
	time_t tc, to;
	
	snprintf(fnc,256,"%s.c",fn);
	snprintf(fno,256,"%s.o",fn);
	add(objs,fno);
	tc= filedate(fnc);
	to= filedate(fno);
// printf("build\t%08X\t%s\n",to,fno);
	result= dep(fnc,to) || (tc>to);
	if (result) {
		char cmd[256];	// compiler command
		snprintf(cmd,256,"gcc -c %s",fnc);
		puts(cmd);
		if (system(cmd)) exit(1);
	}
	return result || (to>t);
}

// Make an executable
make(fn)
	char *fn;	// basename
{
	*objs= *libs= 0;
	if (build(fn, filedate(fn))) {
		char cmd[256];	// linker command
		snprintf(cmd,256,"gcc -o %s %s %s",fn,objs,libs);
		puts(cmd);
		if (system(cmd)) exit(1);
	}
	else printf("'%s' is up to date.\n", fn);
}

main(int argc, char **argv) {
	argc--, argv++;
	if (!argc) printf("Usage: cmake <program> ... (See cmake.c for details)\n"), exit(1);
	while (argc) make(*argv), argc--, argv++;
	exit(0);
}
