[net.sources] Revised 'Make' for IBM PC - Here it is for Lattice!

guido@mcvax.UUCP (Guido van Rossum) (07/12/84)

Here is a version of John M Sellens' Make 1.1 which is usable with the
Lattice C compiler and library (version 2.04 or higher, at least).
(His version is intended for DeSmet C and uses embedded assembly code.
Mine uses the Lattice library function intdos.)  I have left the
original code (almost) unchanged, and made changes by #ifdef-ing
parts of it away.  If you have version 2.1 or higher, you may want to
modify the exec() routine to actually execute the command using system().

All signs of gratitude should be directed to John M Sellens, as he is
author of 99% of the code (see comments below).

--
	Guido van Rossum, "Stamp Out BASIC" Committee, CWI, Amsterdam
	guido @ mcvax

--------------------------------------------------------------------
This is not an archive.  Just copy the rest of this file to "make.c"
--------------------------------------------------------------------
/* John M. Sellens' Make, modified to run with Lattice C 2.04 */

/* Changed for Lattice C by Guido van Rossum, these changes are
   Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1984. */

/*
	I can't execute commands directly without using assembler code,
	so this program writes a batch file, MAKERUN.BAT (in the current
	directory) which must be executed manually after Make has run.
	This batch file contains a line IF ERRORLEVEL 1 GOTO ERRORS after each
	command, except when the '-i' flag (ignore errors) was given to Make.
	I suggest the following batch file be used to run Make and MAKERUN:
		MAKE %1 %2 %3 %4 %5 %6 %7 %8 %9
		IF ERRORLEVEL 1 GOTO ERRORS
		MAKERUN.BAT

	The restriction on built-in DOS command is hereby lifted.

	Wildcard characters filenames are not useful.

	Extra features when EXTRAS is defined (for Unixophiles):
	- a colon is allowed after a target;
	- make without parameters makes the first target in the first file read.
*/
	

/* Make

	Version 1.1, May 14, 1984
	Changed to use DOS calls 4E and 4F and to allow wildcard
	characters ('*' and '?') in depended-upon files. 'Howto'
	lines are now defined as lines that start with either a
	tab or a space (previous version accepted only tabs).

	Any "contributions" gratefully accepted.  I "suggest"
	a contribution of $25 (assuming that you find this useful).

	(c) Copyright, 1984, John M. Sellens

*/

/*
	This a called 'Make' and is a much simplified version of
	the make utility on UNIX (a trademark or something of AT&T)
	written using the DeSmet C compiler/assembler and editor
	for the IBM Personal Computer.  The DeSmet package is
	available from C Ware, P.O. Box 710097, San Jose, California,
	95171-0097, (408) 736-6905 for around $100US.  I think it's
	a good deal.

	'Make' takes a file of dependencies (a 'makefile') and
	decides what commands have to be executed to bring the files
	up to date.  These commands are either executed directly from
	'Make' or written to the standard output without executing
	them.

'Makefile' format:
	- There must be a 'makefile'; you can't take input from the
	standard input.
	- The default name of the 'makefile' is 'MAKEFILE.DAT' on the
	default disk.  Different 'makefiles' can be specified using
	the '-f' option on the command line.  If the '-f' option is
	used, the default 'makefile' is not processed.
	- Any blank lines in the 'makefile(s)' are ignored.
	- A line in a 'makefile' that starts with a tab character is
	a 'howto' line and consists of a command name followed by
	arguments.  The command name must be a full file name, e.g.
	'cc.exe'.  When commands are executed, the PATH environment
	variable is used to find the command, in (hopefully) the
	same manner as DOS does.  If a command name contains a ':'
	or a '\', it is assumed to be a complete pathname and the
	PATH is not searched.  'Howto' lines apply to the most
	recently preceding 'dependency' line.  It is an error for
	a 'howto' line to precede the first 'dependency' line.
	- Any other non-blank line is a 'dependency' line.  'Dependency'
	lines consist of a filename followed by a (possibly empty) list
	of dependent filenames.

Operation:
	Syntax:
		make [filename] [-f makefilename] [-i] [-n]
	-i means continue even if an error is encountered while
		executing a command.
	-n means don't execute the commands, just write the ones that
		should be executed to the standard output.  This is useful
		for creating batch files, for example.
	-f specifies that the following argument is the name of a makefile
		to be used instead of the default (MAKEFILE.DAT).
	All arguments may be repeated and relative position of the
	arguments is not important.  If multiple definitions of a file
	are found, only the first one is significant.

	First, 'Make' reads all of the makefiles.  It then proceeds through
	all of the filename arguments, 'making' each one in turn.  A file
	is remade if it is out of date with respect to the files it depends
	on or is non-existent.  Dependencies are processed in a 'tree' fashion,
	so that the lowest-order files are remade first.

	'Make' cannot execute DOS built-in commands e.g. 'cd' or 'dir'.
	'Make' uses the first 75k or so after the resident portion of DOS.
	64k of that is stack and heap space: all definitions and howto's
	are stored in dynamically allocated struct's.  Any executed commands
	are loaded above 'Make' in memory.

	'Make' REQUIRES DOS 2.0 (or higher?).

	'Make' requires 'Find' and 'Path' in order to search the PATH.
	These are in (hopefully) adjacent articles.


   Known portability problems:
		- assembler code embedded in the C code
		- uses 'exec()' to execute the commands.
		- 'Path' requires that code segment register (CS) be set to the
		  same value as at program invocation so that the
		  program segment prefix can be located.  This would
		  probably only be a problem if a "large model"
		  compiler is used.

	The code is a little kludgy in places.

	No guarantees or warranties of any kind:  I think it works and
	I use it.

	Any suggestions for improvements gratefully accepted.

	I believe that commercial versions exist.  I also beleive that they
	would be superior to this.

	If you do not have DeSmet C, I am willing to provide source,
	object, and linked files for 'Make', 'Find' and 'Path' for a price.
	Send me a floppy in a sturdy mailer, and $15 (you can tell I really
	don't want to do this, but I will), and I will return your disk to you.
	The $15 includes postage back to you.


*/


/*
Written by John M Sellens, April, 1984

Code is all original except where indicated otherwise.

Until August, 1984:
	jmsellens@watrose.UUCP

	107 - 180 Brybeck Cres.
	Kitchener, Ontario
	N2M 5G4

After August, 1984:
	c/o 1135 Lansdowne Ave. SW
	Calgary, Alberta
	T2S 1A4

(c) Copyright 1984 John M Sellens
Permission is granted to use, distribute and/or modify this code unless
done for direct commercial profit.  If you find these routines useful,
modest contributions (monetary or otherwise) will be gratefully accepted.
Author's name, address and this notice must be included in any copies.

*/



#define LATTICE /* Use Lattice C compiler and library */
#define EXTRAS /* Compile optional extra features */

#include <stdio.h>

#ifdef LATTICE

#include <ctype.h>
#include <dos.h>
	/* You may need a file "M8086.H" which is identical to "SM8086.H"
	   as delivered by Lattice. */

#define TRUE 1
#define FALSE 0

#define void int /* Lattice C doesn't know (void) -- yet */

#undef max /* Lattice stdio.h defines max and min... */
#undef min /* ...it shouldn't because this bites our own min/max routines */

#define errout(s) fputs(s, stderr) /* No need for DeSmet kludge */

#define MAKERUN "MAKERUN.BAT" /* File on which commands are written */

#endif LATTICE

#define DEFAULT	"MAKEFILE.DAT"
#ifdef LATTICE
#define INMAX	BUFSIZ
#else !LATTICE
#define INMAX	130		/* maximum input line length */
#endif LATTICE

#define DEPENDANT	1	/* used in calling getmodified */
#define DEFINED		2

struct howrec {
	char *howcom,*howargs;
	struct howrec *nexthow;
};

struct deprec {
	char *name;
	struct defnrec *def;
	struct deprec *nextdep;
};

struct defnrec {
	char *name;
	int uptodate;
	long modified;
	struct deprec *dependson;
	struct howrec *howto;
	struct defnrec *nextdefn;
};

struct dorec {
	char *name;
	struct dorec *nextdo;
};

struct defnrec *defnlist;
struct dorec *dolist;

int execute;
int stopOnErr;
int madesomething;
int knowhow;

main(argc,argv)
int argc;
char *argv[];
{
	long make();

	init(argc,argv);

	/* now fall down the dolist and do them all */
	while (dolist != NULL) {
		madesomething = FALSE;
		(void)make(dolist->name);	/* ignore return value */
		if (!madesomething) {
			if (knowhow) {
				errout("Make: '");errout(dolist->name);
				errout("' is up to date\r\n");
			} else {
				errout("Make: Don't know how to make '");
				errout(dolist->name);errout("'\r\n");
				if (stopOnErr)
					exit(-1);
			}
		}
		dolist = dolist->nextdo;
	}

}


init(argc,argv)
int argc;
char *argv[];
{
	int i, usedefault;
	dolist = NULL;
	defnlist = NULL;
	usedefault = TRUE;
	execute = TRUE;
	stopOnErr = TRUE;

	for (i=1; i < argc; i++) {
		if (argv[i][0] == '-') {	/* option */
			switch (argv[i][1]) {
				case 'f': case 'F':	/* arg following is a makefile */
					if (++i < argc) {
						readmakefile(argv[i]);
						usedefault = FALSE;
					} else {
						errout("Make: '-f' requires filename\r\n");
						exit(-1);
					}
					break;
				case 'i': case 'I':	/* ignore errors on execution */
					stopOnErr = FALSE;
					break;
				case 'n': case 'N':	/* don't execute commands - just print */
					execute = FALSE;
					break;
				default:
					errout("Make: unknown option '");errout(argv[i]);
					errout("'\r\n");
			}
		} else {	/* it must be something to make */
			add_do(argv[i]);
		}
	}
	if (usedefault)
		readmakefile(DEFAULT);
#ifdef EXTRAS
	if (dolist == NULL && defnlist != NULL)
		add_do(defnlist->name); /* Default: make first entry in first file */
#endif EXTRAS

}

long make(s)	/* returns the modified date/time */
char *s;
{
	struct defnrec *defnp;
	struct deprec *depp;
	struct howrec *howp;
	long latest, getmodified(), max();

	/* look for the definition */
	defnp = defnlist;
	while (defnp != NULL) {
		if (strcmp(defnp->name,s) == 0)
			break;
		defnp = defnp->nextdefn;
	}

	if (defnp == NULL) {	/* don't know how to make it */
		knowhow = FALSE;
		latest = getmodified(s,DEFINED);
		if (latest==0) {	/* doesn't exist but don't know how to make */
			errout("Make: Can't make '");errout(s);errout("'\r\n");
			exit(-1);
		} else	/* exists - assume it's up to date since we don't know */
			return(latest);
	}

	if (defnp->uptodate)
		return(defnp->modified);

	/* now make sure everything that it depends on is up to date */
	latest = 0;
	depp = defnp->dependson;
	while (depp != NULL) {
		latest = max(make(depp->name),latest);
		depp = depp->nextdep;
	}

	knowhow = TRUE;	/* has dependencies therefore we know how */

	/* if necessary, execute all of the commands to make it */
	/* if (out of date) || (depends on nothing)             */
	if (latest > defnp->modified || defnp->dependson==NULL) {
		/* make those suckers */
		howp = defnp->howto;
		while (howp != NULL) {
			printf("%s %s\n",howp->howcom,howp->howargs);
			if (execute) {
				char filename[100];	/* extra space */
				if (find(howp->howcom,filename)) {
					if (exec(filename,howp->howargs) != 0) {
						errout("\r\nMake: error on '");errout(filename);
						errout(" ");errout(howp->howargs);errout("'");
						if (stopOnErr)
							exit(-1);
					}
				} else {
					errout("\r\nMake: Can't find '");
					errout(howp->howcom);errout("'\r\n");
					if (stopOnErr)
						exit(-1);
				}
#ifndef LATTICE
				putchar('\n');	/* in case command leaves us dangling */
#endif !LATTICE
			}
			howp = howp->nexthow;
		}
/* Only say it`s as recent as it's latest dependent - that way
	if we don't actually have a file for this dependency, it works
	ok
*/
		defnp->modified = latest;
		defnp->uptodate = TRUE;
		if (defnp->howto != NULL)	/* we had instructions */
			madesomething = TRUE;
	}

	return(defnp->modified);

}


add_do(s)
char *s;
{
	struct dorec *ptr1, *ptr2;
	char *getmem();

	ptr1 = (struct dorec *) getmem(sizeof(struct dorec));

	ptr1->name = s;	/* okay since only called with an argv */
	ptr1->nextdo = NULL;

	uppercase(ptr1->name);

	/* now go down the dolist */
	if (dolist == NULL)
		dolist = ptr1;
	else {
		ptr2 = dolist;
		while (ptr2->nextdo != NULL)
			ptr2 = ptr2->nextdo;
		ptr2->nextdo = ptr1;
	}

}


readmakefile(s)
char *s;
{
#ifdef LATTICE
	FILE *fil;
	int doneline, pos, i, j;
#else !LATTICE
	int fil, doneline, pos, i, j;
#endif !LATTICE
	char inline[INMAX], info[INMAX];
	char *getmem();
	struct defnrec *defnp, *defnp2;
	struct deprec *depp, *depp2;
	struct howrec *howp, *howp2;

#ifdef LATTICE
	if ( (fil = fopen(s,"r")) == NULL ) {
#else !LATTICE
	if ( (fil = open(s,0)) < 0) {
#endif !LATTICE
		errout("Make: Couldn't open '");errout(s);errout("'\r\n");
		return;
	}

	while (fgets(inline,INMAX,fil) != NULL) {
		inline[strlen(inline)-1] = '\0';	/* strip trailing newline */

		if (inline[0] == '\0')	/* ignore blank lines */
			continue;

		if (!isspace(inline[0])) {	/* start of a new definition */
			uppercase(inline);
#ifdef EXTRAS
			dropcolon(inline);
#endif EXTRAS

			/* get what we're defining into info */
			if (sscanf(inline,"%s ",info) != 1) {
				errout("Make: Can't scan: '");errout(inline);errout("'\r\n");
				continue;
			}

			/* test for wildcards in file being defined */
			for(i=0;info[i]!='\0';i++)
				if (s[i]=='?' || s[i]=='*') {
					errout("Make: Definition of '");errout(info);
					errout("' contains wildcard character(s)\r\n");
#ifdef LATTICE
					errout("Make: This makes no sense\r\n");
#else !LATTICE
					errout("Make: This may or may not make any sense\r\n");
#endif !LATTICE
					break;
				}

			/* get a new struct */
			defnp = (struct defnrec *) getmem(sizeof(struct defnrec));
			/* add it to the end of defnlist */
			if (defnlist == NULL)
				defnlist = defnp;
			else {
				defnp2 = defnlist;
				while (defnp2->nextdefn != NULL)
					defnp2 = defnp2->nextdefn;
				defnp2->nextdefn = defnp;
			}
			/* initialize it */
			defnp->name = getmem(strlen(info)+1);
			strcpy(defnp->name,info);
			defnp->uptodate = FALSE;	/* actually unknown */
			defnp->modified = getmodified(defnp->name,DEFINED);
			defnp->dependson = NULL;
			defnp->howto = NULL;
			defnp->nextdefn = NULL;

			/* now go through all of its dependecies */
			/* first move past the first name */
			pos = 0;
			while (isspace(inline[pos]))
				pos++;
			while (!isspace(inline[pos]) && inline[pos]!='\0')
				pos++;
			/* now loop through those suckers */
			doneline = FALSE;
			while (!doneline) {
				while (isspace(inline[pos]))
					pos++;
				if (inline[pos] == '\0') {
					doneline = TRUE;
					continue;
				}
				for(i = 0; !isspace(inline[pos]) && inline[pos]!='\0'; )
					info[i++] = inline[pos++];
				info[i] = '\0';
				/* get a new struct */
				depp = (struct deprec *) getmem(sizeof(struct deprec));
				/* add it to the end of deplist */
				if (defnp->dependson == NULL)
					defnp->dependson = depp;
				else {
					depp2 = defnp->dependson;
					while (depp2->nextdep != NULL)
						depp2 = depp2->nextdep;
					depp2->nextdep = depp;
				}
				depp->name = getmem(strlen(info)+1);
				strcpy(depp->name,info);
				depp->nextdep = NULL;
			}
		} else {	/* a how to line that starts with blank or tab */
			if (defnp == NULL) {
				errout("Make: Howto line without a definition\r\n");
				errout("Make: '");errout(inline);errout("'\r\n");
			}
			/* now split the line up into command and args */
			for (pos=0;isspace(inline[pos]); pos++);
				;
			for (i=pos; !isspace(inline[i]) && inline[i]!='\0'; i++)
				;
			/* if there is something there, allocate mem and copy */
			if (i != pos) {
				/* get a new struct */
				howp = (struct howrec *) getmem(sizeof(struct howrec));
				/* add it to the end of howlist */
				if (defnp->howto == NULL)
					defnp->howto = howp;
				else {
					howp2 = defnp->howto;
					while (howp2->nexthow != NULL)
						howp2 = howp2->nexthow;
					howp2->nexthow = howp;
				}
				/* copy command filename */
				howp->howcom = getmem(i-pos+1);
				for(j=0; pos < i; )
					howp->howcom[j++] = inline[pos++];
				howp->howcom[j] = '\0';
				/* now look for any argumentative part */
				while (isspace(inline[pos]))
					pos++;
				howp->howargs = getmem(strlen(inline)-pos + 1);
				for(i=0; inline[pos] != '\0'; )
					howp->howargs[i++] = inline[pos++];
				howp->howargs[i] = '\0';
				howp->nexthow = NULL;
			}
		}
	}
#ifdef LATTICE
	fclose(fil);
#endif LATTICE
}


uppercase(s)
char *s;
{
	for( ; *s != '\0'; s++)
		*s = toupper(*s);
}

#ifdef EXTRAS
dropcolon(s) /* Routine to change a colon to a space */
char *s;
{
	while (!isspace(*s)) {
		if (*s == ':') {
			*s = ' ';
			break;
		}
		++s;
	}
}
#endif EXTRAS


#ifndef LATTICE /* Lattice has its own getmem()--exactly what we need */
char *getmem(size)
int size;
{
	char *p;
	if ((p = malloc(size)) == 0) {
		errout("Make: Ran out of memory...\r\n");
		exit(-1);
	}
	return(p);
}
#endif !LATTICE


#ifdef LATTICE /* getmodified routine for Lattice C */
long getmodified(name,which) /* Does not support *? matching characters */
char *name;
int which; /* unused */
{
	long times[2];

	if (getutime(name, times) < 0)
		return 0l;
	return times[1];
}

#define SVC_OPEN 0x3d
#define SVC_CLOSE 0x3e
#define SVC_FTIME 0x57

getutime(file, times)
char *file;
long times[2];
{
	int handle;
	union REGS regs;
	long t;
	unsigned ti;
	int y, m, d, hh, mm, ss;
	int f;

	f = open(file, 0); /* Kludge to determine file existence */
	if (f < 0)
	    return -1;
	close(f);

	regs.h.ah = SVC_OPEN;
	regs.h.al = 0;
	regs.x.dx = file;
	handle = intdos(&regs, &regs);

	regs.h.ah = SVC_FTIME;
	regs.h.al = 0;
	regs.x.bx = handle;
	intdos(&regs, &regs);

	ti = (regs.h.dh<<8) + regs.h.dl;
	y = ti>>9; /* Year - 1980 */
	m = (ti>>5)&017;
	d = ti&037;
	t = (y * 12l + m-1) * 31l + d-1;

	ti = (regs.h.ch<<8) + regs.h.cl;
	hh = ti>>11;
	mm = (ti>>5)&077;
	ss = (ti&037)<<1;
	t = ((t * 24l + hh) * 60l + mm) * 60l + ss;

	regs.h.ah = SVC_CLOSE;
	regs.x.bx = handle;
	intdos(&regs, &regs);

	times[0] = 0l;
	times[1] = t;
	return 0;
}

#else !LATTICE
long getmodified(name,which)
char *name;
int which;
{
	static int save_es, dta_seg, dta_offset;
	long datetime, dateof(), max(), min();
#asm
	mov		word getmodified_save_es_,es	; save ES just in case
	mov		ah,2fh							; get dta into es:bx
	int		21h
	mov		word getmodified_dta_seg_,es
	mov		word getmodified_dta_offset_,bx
	mov		es,word getmodified_save_es_	; restore
#
	datetime = 0;	/* as old as possible if does not exist */
	if (_os(0x4e,name) == 0) {	/* at least one matching file exists */
		datetime = dateof(dta_seg,dta_offset);
		/* now loop through all the rest of the matching files */
		while (_os(0x4f,0) == 0) {
			if (which == DEPENDANT)
				datetime = max(datetime, dateof(dta_seg,dta_offset));
			else	/* this is DEFINED */
				datetime = min(datetime, dateof(dta_seg,dta_offset));
		}
	} else	/* doesn't exist */
		datetime = 0;	/* as old as possible */
	return(datetime);
}

long dateof(dta_seg,dta_offset)
/*	return a long encoding the date and time of the file just found */
int  dta_seg, dta_offset;
{
	static long ret_dt;
	static int save_es;

#asm
	mov		word dateof_save_es_,es		; save it just in case
	mov		es,word [bp+4]
	mov		bx,word [bp+6]
	mov		ax,es:[bx+22]
	mov		word dateof_ret_dt_,ax	; store time in low word of ret_dt.
	mov		ax,es:[bx+24]
	mov		word dateof_ret_dt_+2,ax	; date to high word of ret_dt
	mov		es,word dateof_save_es_		; restore
#
	return ret_dt;
}
#endif !LATTICE

long max(a,b)
long a,b;
{
	return(a>b ? a : b);
}

long min(a,b)
long a,b;
{
	return(a>b ? b : a);
}

#ifdef LATTICE /* find/exec replacement -- write a batch file. */
find(command,filename)
char *command;
char *filename;
{
	strcpy(filename, command);
	return TRUE;
}

exec(command,args)
char *command;
char *args;
{
	static FILE *execfile;

	if (!execfile) {
		execfile = fopen(MAKERUN, "w");
		if (!execfile) {
			errout("Make: can't create ");
			errout(MAKERUN);
			errout("\r\n");
			exit(-1);
		}
	}
	fprintf(execfile, "%s %s\n", command, args);
	if (stopOnErr)
		fprintf(execfile, "if errorlevel 1 goto errors\n");
	return 0;
}
#endif LATTICE