/*
 *				b u i l d 0 . c
 */

#ifdef	DOCUMENTATION

title	build	Build compilation command files
index		Build compilation command files

synopsis

	build -options files

description

	Build is used to create command files for C programs and
	libraries.  It reads the program source files to determine
	dependencies and writes a command file for the operating
	system indirect command processor.
	.s
	The following options are defined:
	.lm +16
	.s.i-16;-a		Set protection codes on the output
	file (program or library) so that all users may access the
	file.
	.s.i-16;-b		Build (as a program) even if the
	"/*)BUILD" comment is not seen.
	.s.i-16;-d		Do not delete .obj files after build.
	.s.i-16;-h header	A header command file is copied to the
	output file before all processing.
	.s.i-16;-l library	An object library of C programs is
	built.  library is the (fully-qualified) name of the output file.
	(Note that the $(BIN) model is not appended).  If unspecified,
	the filetype will be .OBJ for RT11 and .ODL for RSX and VAX libraries.
	Note that, on VAX/VMS, if you build both the RSX and native library,
	you must select different library names as they will generally
	have the same filetype.
	.s.i-16;-m model_file	Command files are built by expanding
	macros in built-in model strings.  The model file allows the user
	to specify default definitions for model strings.
	.s.i-16;-o output	The output file is be changed from
	stdout to this file.
	.s.i-16;-s source	This string is pre-pended to all source
	file references (and include copy operations are not commented out).
	.s.i-16;-t trailer	A trailer command file is copied to the
	output file after all processing.
	.s.i-16;-v		Verbose -- log files as they are processed.
	.s.i-16;-x system	Specify the operating system on which
	the command file is to be run.  If not specified, your current
	operating system is used.
	.s.i-16;-z		Used to build a test datastream.  Programs
	are compiled using "[x]cc" and "newcc" and the outputs, named
	<program>.ols and <program>.s, are passed through diff.  Programs
	are neither assembled nor linked.  The .ols and .s files are
	deleted.  This option presupposes that the
	new compiler is named "newcc" (or, on rsx, "newxcc") and that diff
	has already been defined.  It is intended to simplify debugging the
	Decus C compiler.  There is no provision for testing native C.
	.s.lm -16
	If the -x option is not specified, the output file will be
	built for the current operating system.  The following operating
	systems are known:
	.lm +16
	.s.i-16;vaxnative, vaxrsx, vaxboth, vax
	.br
	Vaxnative generates a command file that uses the Vax-11 C compiler
	only; vaxrsx uses Decus-C only, while vax writes a command file
	that can be used for either compiler.
	.s.i-16;rstsrt11, rstsrsx, rsts
	.br
	Rsts defaults to rstsrt11.
	.s.i-16;rsxnative rt11native
	.br
	These are native-mode command files and may be abbreviated
	"rsx" and "rt11" respectively.
	.s.lm -16
	When specifing an operating system, a non-ambiguous string
	is required.  Thus, "rstsrt11" may be abbreviated to "rstsrt".
	.s
	For example, note the following examples:
	.s.nf
		build -l c:cu -s src: *.c >vxcubl.com
		build *.c -x rt11 >ttool.com
	.s.f
	The first builds the utility library for the current operating
	system, while the second builds tools for rt11 native mode.

Header and trailer file format

	These files are written to the output file before (header)
	and after (trailer) the generated command files.  They
	may be used to establish compilation defaults.
	
C source file format

	To generate the command file, build reads each source file,
	searching for a build command.  This appears as a comment
	(beginning in column 1) in the C source:
	.s.nf
		/*)BUILD
			arguments
		*/
	.s.f
	If a library is being built, build searches for "/*)LIBRARY".
	Thus libraries and programs may coexist in the same
	directory.
	.s
	Note that "/*)BUILD" or "/*)LIBRARY" must appear in upper-case
	at the beginning of a line.  Also, the terminating "*/" must be
	the first non white-space characters on a line.
	.s
	A '#' outside of a model argument will cause the rest of
	the source line to be ignored, allowing comments in model
	strings.
	.s
	If no build arguments are given, the current file will be compiled.
	.s
	Arguments may be given to override model (or built-in)
	definitions.  The format for an argument is as follows:
	.s.nf
		$X = Y
	.s.f
	where X may be either a single letter or a string enclosed
	in either parentheses or braces and Y may be either a single
	word or a string (which may contain blanks, newlines, etc.)
	bounded by braces.  For example:
	.s.nf
		/*)BUILD
			$(PROGRAM) = kwik
			$(STACK) = 2000
			$(FILES) = { kwik sortc }
		*/
	.s.f
	Within model definitions, the '$' character may appear as
	itself if it is doubled "$$" or followed by a blank "$ ".
	.s
	The following may be specified in build arguments or model
	files:
	.lm +16
	.s.i-16;$(DTOA)		This program requires the floating-point
	conversion routines.  This should be written as:
	.s
		$(DTOA) = 1
	.s
	.s.i-16;$(SRC)		The account where the sources are.
	This overrides any -s option value.
	.s.i-16;$(BIN)		The account where the image goes.
	.s.i-16;$(FILES)	The list of files to be built.  If
	not specified, the current file name is used.  $(FILES) may
	be specified for programs and libraries.
	.s.i-16;$(MP)		To preprocess program source files
	by the MP macro pre-processor, define this as:
	.s
		$(MP) = 1
	.s
	As the MP preprocessor does not have a default filetype,
	the build command will append ".C" to any source file that
	does not have an explicit filetype.  This may be overridden
	by providing the filetype in the $(FILES) command:
	.s
		$(FILES) = { prog.txt }
	.s
	.s.i-16;$(INCLUDE)	The list of files to be included.  If
	not specified, no include files will be copied.  The copy is
	supressed if neither $(SRC) nor $(COPYINCLUDE) have been
	redefined as the file would
	be copyied from the current disk/account to itself.  These
	files are not deleted after compilation, even if they were copied.
	$(INCLUDE) may be specified for programs or libraries.
	.s.i-16;$(PROGRAM)	The name of the program image to be generated.
	.s.i-16;$(STACK)	A value for the RT11 /B[OTTOM] link specifier.
	If not specified, "$(STACK) = 2000" will be used.
	.s.i-16;$(TKBOPTIONS)	RSX task builder options (stack, units,
	task name, etc.) are specified by this argument as follows:
	.s.nf
	$(TKBOPTIONS) = {
		STACK	= 1500
		TASK	= ...FOO
		; ... etc
	}
	.f
	.s.i-16;$(OBJS)		A string naming the actual object
	files.  If specified, it overrides the default use of the
	files just compiled.
	.s.i-16;$(LIBS)		A string naming additional libraries
	to be searched (before searching the C runtime library).
	.s.i-16;$(UTLIB)	A utility library:  "C:VAXLIB/LIB" on
	vax native, nothing on other operating systems.
	.s.i-16;$(SUPORT)	The RT11 runtime support routine:
	"C:SUPORT.OBJ".
	.s.i-16;$(??LIB)	The C runtime library.  By default:
	.s.i    -12;$(VXLIB)	The VAX/VMS native runtime library, by default
	"SYS$LIBRARY:CRTLIB/LIB"
	.s.i    -12;$(RXLIB)	The RSX runtime library.  The default
	depends on the operating system:  on VAX/VMS and RSTS, it is "C:C/LB", 
	while on native RSX-11M, it is "LB:[1,1]C/LB".
	.s.i    -12;$(RTLIB)	The RT11 runtime library.  The default
	is "C:SUPORT,C:CLIB".
	.s.i-16;$(ODL) = {...}	An RSX overlay description.
	If specified, a temporary file will be created (and subsequently
	deleted)
	containing a configured overlay file.  Note that you should
	reference the run-time library by using $(RXLIB) to obtain
	the correct library designation.  If $(ODL) is specified,
	$(OBJS) is ignored and $(LIBS) and $(RXLIB) will be processed
	only if present in the $(ODL) model.  For example:
	.s.nf
	$(ODL) = {	
		.ROOT MAIN1-MAIN2-$(RXLIB)-*(OVR1,OVR2)
		;
		OVR1	.FCTR	SUB1-SUB1L
		SUB1L	.FCTR	$(RXLIB):FWILD:FGETNA:RTIME
		;
		OVR2	.FCTR	SUB2-SUB2L
		SUB2L	.FCTR	$(RXLIB):ASL$$LI # really asl$li
		;
		.END
	}
	.s.f
	Note the use of $(RXLIB):XXX to select modules from the C library
	and the doubling of the '$' in referencing subroutine ASL$LI.
	.s.i-16;$(OVR) = {...}	An RT11 overlay description.  If specified,
	$(OBJS) is ignored.  For example:
	.s.nf
	$(OVR) = {
		root1,root2,$(SUPORT),$(RTLIB)
		over1/o:1
		over2/o:2
	}
	.f
	.s.i-16;$(COMMENT)	A string that will comment a command
	line for the target operating system.
	.s.i-16;$(PREFIX)	This model is expanded at the start
	of every compilation.  The default is a comment suitable for
	the particular operating system.
	.s.i-16;$(POSTFIX)	This model is expanded after every
	program build.  On VAX/VMS, the default defines the program as
	an external command.  On other systems, the default does nothing.
	.s.i-16;$(FIRST)	This model is expanded before the
	first program is built.  By default, it does nothing.  It may
	be defined by a user-specified model file.
	.s.i-16;$(LAST)		This model is expanded after the last
	program is built. By default, it does nothing.  It may
	be defined by a user-specified model file.
	.s.lm -16
	For example,
	.s.nf
	/*)BUILD
	*/
	.s.f
	This is a "normal" build for e.g., grep or echo, signalling
	build that this file is a program, rather than a subroutine.
	.s.nf
	/*)BUILD
		$(PROGRAM)	= xrf
		$(INCLUDE)	= xrf.h
		$(FILES)	=
			{ xrf0 xrf1 xrf2 xrf3 xrfi xrfd }
		$(TKBOPTIONS)	= {
			TASK	= ...XRF
		}
		$(ODL)		= {
		.ROOT	XRF0-XRF2-XRFD-$(RXLIB)-*(X1,X2,X3)
		;
		X1:	.FCTR	XRFI-X1L
		X1L:	.FCTR	$(RXLIB):CONCAT:CTIME:FWILD
		;
		X2:	.FCTR	XRF1-X2L
		X2L:	.FCTR	$(RXLIB):ASL$$I
		;
		X3:	.FCTR	XRF3-X3L
		X3L:	.FCTR	$(RXLIB):CPYSTR
			.END
		}
		$(OVR)		= {
			xrf0,xrf2,xrfd,c:suport,c:clib
			xrfi,c:clib/o:1
			xrf1,c:clib/o:1
			xrf3,c:clib/o:1
		}
	*/
	.s.f
	This slightly more complex build shows specification of an
	include file, the creation of a program whose name is
	different from its component files, task-builder options,
	and overlay files, including library module selection.
	.s.nf
	/*)LIBRARY
		$(INCLUDE) = chrtab.h
	*/
	.s.f
	This library build file shows the naming of an include file.

Model file format

	Model files consist of strings of macros and their definitions.
	Each definition is separated by one or more blanks, tabs, or
	newlines.  Definitions are in the same format as their )BUILD
	parameter counterparts.  A model will (re)define defaults
	for the entire build.  For example, the model file:
	.s.nf
		$(SRC) = DK1:[6,1]	# source files
		$(BIN) = SY:[1,3]	# image output
	.s.f
	Causes all program source to be taken from DK1:[6,1] and all
	compiled programs to be written to SY:[1,3].
	.s
	Model file definitions replace operating-system specific
	defaults.  Anything more elaborate than redefinition of, say,
	$(SRC) or $(BIN), will probably require understanding
	of the inner logic of the build program.  You should be especially
	cautious of changing a link or library build string.

bugs

	It will never replace make.  It's not supposed to, anyways.
	The problem that build was written to solve is the extrordinary
	proliferation of tiny files -- each tool program requiring
	five or six command files: one or two for each operating system.

author

	Martin Minow

#endif


/*)BUILD
	$(PROGRAM)	= build
	$(INCLUDE)	= build.h
	$(FILES)	= { build0 build1 build2 build3 build4 build5 }
	$(STACK)	= 4000		# the program is recursive
	$(TKBOPTIONS)	= {
		TASK	= ...BUI
		STACK	= 2000
	}
*/

#include	<stdio.h>
#include	"build.h"

int		debug		= FALSE;
int		allow		= FALSE;	/* -a allow access	*/
int		verbose		= FALSE;	/* -v verbosity		*/
int		profile		= FALSE;	/* -p profile compile	*/
int		build		= FALSE;	/* -b build anyways	*/
int		delete		= FALSE;	/* -d don't delete .obj	*/
int		zflag		= FALSE;	/* -z build test stream	*/
char		*src_name	= NULL;		/* -s filename		*/
char		*lib_name	= NULL;		/* -l library file	*/
char		*out_name	= NULL;		/* -o output .com file	*/
char		*opsys_name	= NULL;		/* -x operating system	*/
char		*hdr_name	= NULL;		/* -h header file	*/
char		*trail_name	= NULL;		/* -t trailer file	*/
char		*model_name	= NULL;		/* -m model file	*/
char		work[WORKSIZE];
char		inline[INLINESIZE];
int		mpinstall	= FALSE;
int		opsys;			/* Gets base operating system	*/
					/* VAX, RSX, RT11, RSTS		*/
int		op_type;		/* Gets build "flavor"		*/
					/* VNATIVE, etc.		*/

main(argc, argv)
int		argc;
char		*argv[];
/*
 * Here we go
 */
{
#ifndef	vms
	my_free = malloc(1);		/* For myrealloc()		*/
#endif
	getopsys();			/* Base operating system	*/
	settime();			/* Time of day			*/
	getargs(argc, argv);		/* Parse arg list		*/
	if (out_name != NULL) {		/* if -o option given		*/
		if (freopen(out_name, "w", stdout) == NULL) {
			fatal("can't continue", out_name);
		}
	}
	opmatch(opsys_name);		/* Check for -x options		*/
	define(comm_model);		/* Common model strings		*/
	define(op_base[opsys], 0);	/* Opsys we're running under	*/
	define(op_model[op_type], 0);	/* Opsys flavor for vax or rsts	*/
	if (profile) {			/* If -p option given		*/
		sysave("RSXP", "-p", 0);
		syset("RT11P", "/p", 0);
	}
	if (src_name != NULL) {		/* If -s option given		*/
		sysave("SRC", src_name, 0);
	}
	if (allow) {
		sysave("ALLOWEXECUTE",	"$(EXECUTABLE)");
		sysave("ALLOWREAD",	"$(READABLE)");
	}
	if (zflag) {			/* If test stream is desired,	*/
		sysave("CC",   "$(CCTEST)");	/* Replace CC and	*/
		sysave("CCMP", "$(CCMPTEST)");	/* CCMP models		*/
	}
	setmodel();			/* If -m option given		*/
	if (delete)
		syset("DELCOMMENT", "$(COMMENT)");
	syperm();			/* Permantize the options	*/
	if (debug)
		sydump();
	docopy(hdr_name);		/* If -h option given		*/
	doprog(argc, argv, lib_name != NULL);	/* Do the work		*/
	docopy(trail_name);		/* If -t option given		*/
#ifdef	CHECKALLOC
	if (verbose || debug)
		fprintf(stderr, "%u bytes in free storage\n", hwmallo);
#endif
}

setmodel()
/*
 * Process model file
 */
{
	register FILE	*fd;
	char		*model;

	if ((fd = myfopen(model_name, "r")) == NULL)
		return;
	model = savest("");
	while (getline(fd) != NULL) {
		model = csavest(model, inline);
	}
	fclose(fd);
	define(model, MODEL);
	myfree(model);
}

docopy(name)
char		*name;
/*
 * Copy the named file
 */
{
	register FILE	*fd;
	if ((fd = myfopen(name, "r")) != NULL) {
		while (getline(fd) != NULL)
			fputs(inline, stdout);
		fclose(fd);
	}
}	

settime()
/*
 * Store the time of day
 */
{
	register char	*tp;
	extern char	*ctime();
	extern long	time();
	long		tbuf;

	time(&tbuf);
	tp = ctime(&tbuf);
	tp[24] = EOS;
	syset("DATE", savest(tp), 0);
}

getopsys()
/*
 * Sets the opsys variable
 */
{

#ifdef	rt11
	extern int	$$rsts;

	op_type = ($$rsts) ? RSTSRT : RT11NATIVE;

#else
#ifdef	rsx
	extern int	$$rsts;
	extern int	$$vms;

	op_type = ($$rsts) ? RSTSRSX
		: ($$vms) ? VRSX
		: RSXNATIVE;
#else
#ifdef	vms
	op_type = VNATIVE;
#else
	fprintf(stderr, "Don't know the operating system, default to vms\n");
	op_type = VNATIVE;
#endif
#endif
#endif
}

static OPCODE	optable[] = {
	{ "vaxnative",	4, VNATIVE	},
	{ "vaxrsx",	4, VRSX		},
	{ "vax",	3, VBOTH	},
	{ "rsxnative",	3, RSXNATIVE	},
	{ "rstsrsx",	6, RSTSRSX	},
	{ "rstsrt11",	6, RSTSRT	},
	{ "rsts",	4, RSTSRT	},
	{ "rt11native",	4, RT11NATIVE	},
	{ NULL,		0, NONE		},
};

static char	typetable[] = {		/* Maps op_type onto opsys	*/
/*	NONE	VNATIVE	VBOTH	VRSX	RSX	RSTSRSX	RSTSRT	RT11	*/
	NONE,	VAX,	VAX,	VAX,	RSX,	RSTS,	RSTS,	RT11,
};

opmatch(string)
char		*string;		/* What to look for		*/
/*
 * Search the opcode table for a string that matches.  If found, set
 * op_type and opsys.
 */
{
	register OPCODE	*tp;

	if (string != NULL) {
		for (tp = optable; tp->o_name != NULL; tp++) {
			if (mmatch(string, tp->o_name, tp->o_minimum)) {
				op_type = tp->o_value & 0377;
				goto gotcha;
			}
		}
		fprintf(stderr, "?No operating system match for \"%s\"\n",
				string);
	}
gotcha:	opsys = typetable[op_type];
}

struct flags {
	int	fl_byte;		/* Match this byte		*/
	int	*fl_value;		/* Flag to increment		*/
};

struct flags simple_flag[] = {
	{ 'a',	&allow		},
	{ 'b',	&build		},
	{ 'd',	&delete		},
	{ '?', 	&debug		},
	{ 'p',	&profile	},
	{ 'v',	&verbose	},
	{ 'z',	&zflag		},
	{ EOS,	NULL		},
};

struct flags name_flag[] = {
	{ 'h',	&hdr_name	},
	{ 'l',	&lib_name	},
	{ 'm',	&model_name	},
	{ 'o',	&out_name	},
	{ 's',	&src_name	},
	{ 't',	&trail_name	},
	{ 'x',	&opsys_name	},
	{ EOS,	NULL		},
};

int
getargs(argc, argv)
int		argc;
char		*argv[];
/*
 * Process the argument vector, setting global flags.
 * On return, entries in argv[] that have been processed have
 * been NULLified.
 *
 * Return TRUE on errors.
 */
{
	register char		*ap;
	register char		c;
	register struct flags	*fl;
	int			i;
	int			flag;

	flag = FALSE;
	/*
	 * mmatch wants lowercase
	 */
	for (i = 1; i < argc; i++) {
		for (ap = argv[i]; *ap != EOS; ap++)
			*ap = tolower(*ap);
	}
	for (i = 1; i < argc; i++) {
		ap = argv[i];
		if ((c = *ap++) != '-') {
#ifdef	vms
			/*
			 * Vms (currently) does not support
			 * file redirection, so...
			 */
			if (c == '>') {
				if (freopen(ap, "w", stdout) == NULL) {
					perror(ap);
					fatal("Can't reopen stdout");
				}
				argv[i] = NULL;
			}
#endif
			continue;
		}
		/*
		 * Switch
		 */
		argv[i] = NULL;
		while ((c = tolower(*ap++)) != EOS) {
			for (fl = simple_flag;
				fl->fl_byte != EOS && fl->fl_byte != c;
				fl++);
			if (fl->fl_byte != EOS) {
				++(*fl->fl_value);	/* Set the flag	*/
			}
			else if (++i >= argc || argv[i][0] == '-') {
				fprintf(stderr, "?No argument after '%c'\n", c);
				flag = TRUE;
				--i;
			}
			else {
				for (fl = name_flag;
					fl->fl_byte != EOS && fl->fl_byte != c;
					fl++);
				if (fl->fl_byte != EOS) {
					*((char **)(fl->fl_value)) = argv[i];
					argv[i] = NULL;
				}
				else {
					fprintf(stderr,
						  "?Illegal option '%c'\n", c);
				}
			}
		}
	}
	return (flag);
}

