jon@boulder.UUCP (Jonathan Corbet) (05/18/85)
[fnord!] $ $ ! This is VMS make part 4. $ $ babble :== write sys$output $ babble "Creating STAB.C" $ create stab.c /* * Handles the symbol table. Due to be rewritten. * * Jonathan Corbet * National Center for Atmospheric Research, Field Observing Facility. */ # include <stdio.h> /* * A symbol table entry. */ struct stab { char * st_sym; /* The symbol to be defined */ char * st_val; /* The value of that symbol */ struct stab *st_next; /* Next entry in the list */ }; /* * The symbol table. */ static struct stab *s_table[128] = { 0 }; extern char *malloc (); st_defsym (sym, val) char *sym, *val; /* * Define the given symbol as "val". */ { int index; struct stab *stp; /* * Try to find the symbol in the table. */ index = st_hash (sym); for (stp = s_table[index]; stp; stp = stp->st_next) if (! strcmp (stp->st_sym, sym)) break; /* * If we found it, free up the old definition. */ if (stp) free (stp->st_val); /* * Else add a new entry at this index. */ else { stp = (struct stab *) malloc (sizeof (struct stab)); stp->st_sym = malloc (strlen (sym) + 1); strcpy (stp->st_sym, sym); stp->st_next = s_table[index]; s_table[index] = stp; } /* * In either case, allocate and store space for the definiton. */ stp->st_val = malloc (strlen (val) + 1); strcpy (stp->st_val, val); } char *st_getsym (sym) char *sym; /* * Return the value of the given symbol. If no definition exists, NULL * is returned. */ { int index; struct stab *stp; /* * Try to find the symbol in the table. */ index = st_hash (sym); for (stp = s_table[index]; stp; stp = stp->st_next) if (! strcmp (stp->st_sym, sym)) return (stp->st_val); /* * Nope. */ return (NULL); } st_dump () /* * Print out the symbol table. */ { int s; struct stab *stp; printf ("\nSlot\tSymbol\t\t Value"); for (s = 0; s < 128; s++) for (stp = s_table[s]; stp; stp = stp->st_next) printf ("\n%3d\t%-20s '%s'", s, stp->st_sym, stp->st_val); } char *st_symcat (symbol, string) char *symbol, *string; /* * Concatenate the value of "string" onto the given symbol's value. If * "symbol" is not defined, this routine will define it. A pointer to the * new value is returned. */ { int index; struct stab *stp; char *sp; /* * Try to find the symbol in the table. */ index = st_hash (symbol); for (stp = s_table[index]; stp; stp = stp->st_next) if (! strcmp (stp->st_sym, symbol)) break; /* * If the symbol is defined, allocate a new string, copy in the info, * then release the old string. */ if (stp) { sp = malloc (strlen (stp->st_val) + strlen (string) + 1); strcpy (sp, stp->st_val); strcat (sp, string); free (stp->st_val); stp->st_val = sp; return (sp); } /* * Otherwise define the symbol. */ else { stp = (struct stab *) malloc (sizeof (struct stab)); stp->st_sym = malloc (strlen (symbol) + 1); strcpy (stp->st_sym, symbol); stp->st_val = malloc (strlen (string) + 1); strcpy (stp->st_val, string); stp->st_next = s_table[index]; s_table[index] = stp; return (stp->st_val); } } int st_hash (sym) char *sym; /* * Return the hash entry for the given symbol. * * The current strategy is: * if (length (string) >= 6) * return 6th char + 1st char % 127 * else * return last char + 1st char % 127 * * The idea is to cause all of the MAKE$ symbols to end up in different * slots. */ { int sl = strlen (sym); if (sl >= 6) return ((sym[0] + sym[5]) % 127); else return ((sym[0] + sym[sl - 1]) % 127); } st_undef (sym) char *sym; /* * Undefine a symbol. */ { int index = st_hash (sym); struct stab *stp, *last; /* * Try to find the symbol. */ for (stp = s_table[index]; stp; stp = stp->st_next) { if (! strcmp (stp->st_sym, sym)) { /* * Found it. Free up the storage and * unlink the structure. */ free (stp->st_sym); free (stp->st_val); if (stp == s_table[index]) s_table[index] = stp->st_next; else last->st_next = stp->st_next; free (stp); return; } last = stp; } } $ $ babble "Creating SUBPROC.C" $ create subproc.c /* * Subprocess routines. * * Jonathan Corbet * National Center for Atmospheric Research, Field Observing Facility. */ # include <ssdef.h> # include <iodef.h> # include <stdio.h> # include "make.h" # include "debug.h" static char opened = FALSE, closed = FALSE; static short p_chan = 0; static int p_pid; p_open () /* * Create the subprocess. */ { int status; short l_p_name; char proc_name[80], name[80], p_name[80]; int p_ribbit (); # ifdef pr_name /* * Get the process name. */ who_am_i (descr_n (proc_name, 80), &l_p_name); proc_name[l_p_name] = NULL; str$upcase (descr (proc_name), descr (proc_name)); # else sprintf (proc_name, "%X", getpid ()); # endif /* * Create the mailbox. */ strcpy (name, "MKMB_"); strcat (name, proc_name); status = sys$crembx (0, /* Temporary mailbox */ &p_chan, /* Channel to use */ ___, /* Default max message */ ___, /* Default quota */ ___, /* Default protection */ ___, /* Default access mode */ descr (name)); /* Logical name to use */ if (error (status)) { printf ("Unable to create mailbox., status: %d", status); exit (status); } /* * Now create the subprocess. */ strcpy (p_name, "MK_"); strcat (p_name, proc_name); p_name[15] = 0; /* $creprc complains otherwise for long names */ strcat (name, ":"); sys$setast (0); status = lib$spawn ( ___, /* No command */ descr (name), /* Read from the mailbox */ descr ("SYS$ERROR"), /* Write to sys$error */ &1, /* NOWAIT flag set */ descr (p_name), /* Process name */ &p_pid, /* Get process ID for later */ ___, ___, /* No completion status, EF */ p_ribbit, ___); /* Call p_ribbit on croak */ opened = TRUE; sys$setast (1); /* * Check on subprocess completion. If the process already exists, * put out a warning, and assume we can use it. */ if (status == SS$_DUPLNAM) printf ("\nWARNING: Subprocess already exists! Will try to use it."); /* * Anything else other than SS$_NORMAL is fatal. */ else if (error (status)) { printf ("\nUnable to create subprocess! status: %d", status); exit (status); } /* * Make the subprocess die on warnings. */ p_write ("$ On warning then stop/id=0"); } p_zap () /* * Kill the subprocess, immediately, with no regard to * any operation in progress. */ { int status; if (opened) { opened = FALSE; closed = TRUE; if (p_pid) status = sys$delprc (&p_pid, ___); else { printf ("P_pid is zero!"); status = 1; } if (error (status)) { printf ("Unable to kill subprocess."); exit (status); } } } p_ribbit () /* * Called on an AST when the subprocess dies. */ { if (opened && ! closed) { printf ("\nSubprocess died."); opened = FALSE; closed = TRUE; m_exit (); } } p_write (string) char *string; /* * Write a line to the subprocess. */ { int status; if (debug (D_P_WRITE)) printf ("\nP_write: %s", string); /* * Make sure the subprocess exists. */ if (! opened) p_open (); /* * Do the write. */ status = sys$qiow (___, /* Default event flag */ p_chan, /* Our mbx channel */ IO$_WRITEVBLK, /* Write... */ ___, ___, ___, /* No IOSB, AST. */ string, /* Our line ... */ strlen (string), /* ... and its length */ ___, ___, ___, ___); /* P3 - P6 null */ if (error (status)) { p_zap (); printf ("Unable to write to subprocess."); exit (status); } } p_close () /* * Gracefully close the subprocess. */ { if (! opened) return; /* * Wait for the current operation to complete. */ p_write ("$ "); p_write ("$ "); closed = TRUE; /* * Now send the suicide command. */ p_write ("$ stop /identification=0"); opened = FALSE; } $ $ babble "Creating TIMER.C" $ create timer.c int timing = 0; tim_init () /* * Initialize the timer. */ { lib$init_timer (); timing = 1; /* Let handler know. */ } tim_show () /* * Display the timer setting. */ { if (timing) lib$show_timer (); timing = 0; } $ $ babble "Creating WHOAREWE.C" $ create whoarewe.c who_are_we () /* * Find out who is running the program. * * Jonathan Corbet * National Center for Atmospheric Research, Field Observing Facility. */ { char u_name[80]; int len; my_name (descr_n (u_name, 80), &len); u_name[len] = 0; st_defsym ("MAKE$USERNAME", u_name); } $ $ babble "Creating CPANIC.C" $ create cpanic.c /* * c_panic: * * A panic call for C programs that does a sprintf first... */ c_panic (fmt, args) char *fmt; int args; { char line[133]; extern ftl_panic (); sprintrmt (line, fmt, &args); lib$signal (ftl_panic, 1, descr (line)); } $ $ babble "Creating DESCR.C" $ create descr.c #define COM_DESCR # include "descr.h" /* * This routine fills character descriptors (stored internally) and * returns the address of the descriptor as its value. An array is * used in a circular manner, in the hopes that no conflicts will take * place... */ # define N_DESCR 128 static struct st_descr d_array[N_DESCR]; static int cur_descr = 0; struct st_descr *descr (string) char *string; { struct st_descr *ret; d_array[cur_descr].st_length = strlen (string); d_array[cur_descr].st_address = string; d_array[cur_descr].st_type = 0x10D; /* Class 1, type 14 */ ret = &d_array[cur_descr]; if (++cur_descr >= N_DESCR) cur_descr = 0; return (ret); } struct st_descr *descr_n (string, len) char *string; int len; { struct st_descr *ret; d_array[cur_descr].st_length = len; d_array[cur_descr].st_address = string; d_array[cur_descr].st_type = 0x10D; /* Class 1, type 14 */ ret = &d_array[cur_descr]; if (++cur_descr >= N_DESCR) cur_descr = 0; return (ret); } $ $ babble "Creating DESCR.H" $ create descr.h /* * Descr.h * * Definition of string descriptors. */ struct st_descr { short st_length; /* Length of the string */ short st_type; char * st_address; /* Address of the string */ }; #ifndef COM_DESCR struct st_descr *descr (), *descr_n (); #endif $ $ babble "Creating ERRMES.MAR" $ create errmes.mar .title errmes ; ; This routine prints the message associated with a given ; error code. ; It is an equivalent of the previous FORTRAN routine ; of the same name. The advantage of this implimentation is ; that it is re-entrant and hence callable from an AST. ; .list meb .ENTRY ERRMES,^M<R2> clrl -(sp) ; pushl @4(ap) ; pushl #1 movzbl (ap),r1 ; r1 has counter of # of args loop: movl (ap)[r1],r2 ; r2 has addr of an arg (in reverse order) pushl (r2) ; store arg on stack in reverse order sobgtr r1,loop ; decrement ptr and loop till no more left pushl (ap) ; save arg count. moval (sp),r2 $putmsg_s msgvec=(r2) blbc r0,baderr ret baderr: $exit_s code=#ss$_abort .end $ $ babble "Creating FTLERRMSG.MSG" $ create ftlerrmsg.msg .facility ftl,52 .severity fatal FTLERR <Program generated fatal error.> FTLERR2 <FATAL ERROR: !AS>/fao=1 PANIC <!AS>/fao=1 .end $ $ babble "Creating LENS.FOR" $ create lens.for integer function lens(line,max) c+ integer function lens(line,max) c c entry: c line is a character string c max is the length of the line c c exit: c the value of the function is the position of the last non-blank c character c- character*(*) line character*1 tab integer max parameter (tab=char(9)) j = max 10 if ((line(j:j) .ne. ' ') .and. (line(j:j) .ne. tab)) goto 100 j = j - 1 if (j .gt. 1) goto 10 100 lens = j return end $ $ babble "Creating MYNAME.FOR" $ create myname.for subroutine my_name (name, length) c c Find the name of the person running the program. c implicit none include '($ssdef)' include '($jpidef)' character *(*) name integer*4 item(4), status, flag, lib$len, length integer *2 iosb(4), l integer lib$get_ef, sys$waitfr, len, sys$getjpi, ipd, ulen, llen integer lens c c Get an event flag. c call iferr (lib$get_ef(flag)) c c Set up the item list. c item(1) = ishft (jpi$_username, 16) .or. len (name) item(2) = %loc(name) item(3) = %loc(l) item(4) = 0 c c Now call sys$getjpi. c ipd = 0 99 status = sys$getjpi (%val(flag), ipd, , item, iosb, ,) call iferr (status) c c Wait for the service to complete. c status = sys$waitfr (%val(flag)) call iferr (status) call lib$free_ef (flag) status = iosb(1) if (status .gt. 1) then call errmes (status) name = 'BIZARRE' length = 7 end if c c Find the real length, since the system service always returns 12. c length = l length = lens (name, length) c c Done. c end $ $ babble "Creating QUADMATH.MAR" $ create quadmath.mar .TITLE QUADMATH ; ; SUBROUTINE SUBQUAD.MAR ; ; FORTRAN CALLABLE ROUTINE TO SUBTRACT TWO QUAD WORD INTEGERS ; ; CALL SUBQUAD (A,B,C) ; ; RETURNS: A - B -> C ; .ENTRY SUBQUAD ^M<R2> ; note that you cannot enable integer overflow or this will fail. ; ; A = 4 B = 8 C = 12 D = 16 MOVQ @A(AP),R0 ;GET FIRST PARAM FOR SUBTRACT ;NEED TO USE REGISTERS BECAUSE ;SUBWC IS ONLY 2 ADDRESS INSTR. MOVAQ @B(AP),R2 ;GET ADDRESS OF SECOND PARAM. SUBL (R2)+,R0 ;SUBTRACT FIRST HALF OF ARGUMENTS SBWC (R2),R1 ;THEN DO THE SECOND HALF MOVQ R0,@C(AP) ;STORE THE RESULT RET ; ; ; SUBROUTINE ADDQUAD.MAR ; ; FORTRAN CALLABLE ROUTINE TO ADD TWO QUAD WORD INTEGERS ; ; CALL ADDQUAD (A,B,C) ; ; RETURNS: A + B -> C ; .ENTRY ADDQUAD ^M<R2> ; note that you cannot enable integer overflow or this will fail. ; ; A = 4 B = 8 C = 12 MOVQ @A(AP),R0 ;GET FIRST PARAM FOR SUBTRACT ;NEED TO USE REGISTERS BECAUSE ;SUBWC IS ONLY 2 ADDRESS INSTR. MOVAQ @B(AP),R2 ;GET ADDRESS OF SECOND PARAM. ADDL (R2)+,R0 ;SUBTRACT FIRST HALF OF ARGUMENTS ADWC (R2),R1 ;THEN DO THE SECOND HALF MOVQ R0,@C(AP) ;STORE THE RESULT RET ; ; ; .ENTRY EDIV ^M<R2,IV> ; ; CALL EDIV (A,B,C) ; RETURNS A/B -> C ; MOVQ @A(AP),R0 ;GET FIRST ARGUMENT ; MOVAL @B(AP),R2 ;GET LONGWORD DIVISOR ADDRESS ; EDIV (R2),R0,R0,R1 ;DO THE DIVISION MOVL @B(AP),R2 EDIV R2,R0,R0,R1 MOVL R0,@C(AP) ;STORE INTEGER QUOTIENT,IGNORE REM. RET .ENTRY EMUL ^M<R2,IV> ; ; CALL EMUL (A,B,C,D) ; RETURNS D = A*B + C ; MOVAQ @D(AP),R0 ;GET RESULT ADDRESS EMUL @A(AP),@B(AP),@C(AP),(R0) ;DO MULT AND STORE RESULT RET .ENTRY QMUL ^M<R2,R3,R4,R5,IV> ; ; CALL QMUL (A,B,C) ; RETURNS C = A * B ; where A, B, & C are quadwords. ; ; this code is copied from the VAX11 Architecture Handbook ; MOVAQ @A(AP),R2 MOVAQ @B(AP),R3 EMUL (R2),(R3),#0,R4 MULL3 4(R2),(R3),R0 MULL3 (R2),4(R3),R1 ADDL R1,R0 TSTL (R2) BGEQ 10$ ADDL (R3),R0 10$: TSTL (R3) BGEQ 20$ ADDL (R2),R0 20$: ADDL R0,R5 MOVQ R4,@C(AP) RET .END $ $ babble "Creating SCTLC.FOR" $ create sctlc.for integer function set_ctl_c(funct) c+ subroutine set_ctl_c(funct) c c this routine enables a control_C interrupt. Funct is the name of a c routine to be called on the receipt of a control_C. Note that after c a control_C is caught, set_ctl_c must be called again to reenable the c ast. A return value of 1 indicates that the call was successful c- character *10 in logical first data first /.true./ integer setctlc if (first) then c c assign a channel to the terminal c call sys$assign ('TT', ichani,,) first = .false. endif c c enable the AST c set_ctl_c = setctlc(ichani,funct) return end $ $ babble "Creating SETCTLC.MAR" $ create setctlc.mar .title set_ctlc ; ; setctlc -- catch control_C interrupts. ; call in fortran as: ; ; call setctlc(channel,routine) ; ; where: ; channel is a channel to the terminal generating control_C's ; routine is the name of the routine to be called on a control_C. ; this routine must be declared external in the calling program. ; .entry setctlc,^M<R2> $qiow_s CHAN=@4(ap),func=#io$_setmode!io$m_ctrlcast,- P1=@8(ap),P3=#3 ret .entry setctly,^M<R2> $qiow_s CHAN=@4(ap),func=#io$_setmode!io$m_ctrlyast,- P1=@8(ap),P3=#3 ret .end $ $ babble "Creating SETEXH.FOR" $ create setexh.for subroutine set_exh (handler) c c Set an exit handler for the image. c integer c_blk(5), sys$dclexh, status external handler c c Fill in the control block. Note that the exit reason is essentially c being thrown away. c c_blk(1) = 0 c_blk(2) = %loc(handler) c_blk(3) = 0 c_blk(4) = %loc(reason) c_blk(5) = 0 c c Now declare the exit handler. c status = sys$dclexh (c_blk) call iferr (status) return end $ $ babble "Creating SPRINTRMT.C" $ create sprintrmt.c /* sprintrmt(buf,arg) does what sprintf(buf,"%r",arg) used to */ sprintrmt(buf, fmt, arg) char *buf, *fmt; register char **arg; { /* this silly routine should really be calling _doprnt */ register char *t1, *t2, *t3; int sprintf (); /* * DON'T ASK, YOU DON'T WANT TO KNOW... */ t1 = *--arg; *arg = fmt; t2 = *--arg; *arg = buf; t3 = *--arg; *arg = (char *) 255; lib$callg (arg, sprintf); *arg++ = t3; *arg++ = t2; *arg++ = t1; } $ $ babble "Creating MAKEMAKE.COM" $ create makemake.com X$ ! This is MAKEMAKE.COM, which will put together the initial version of X$ ! MAKE for you. Be sure to remove the leading X's before invoking! X$ ! X$ ! Compile everything. X$ ! X$ cc CCONS.C X$ cc CHARUTL.C X$ cc CHDIR.C X$ cc CHECKDEF.C X$ cc COMLINE.C X$ cc CONS.C X$ cc CPANIC.C X$ cc DCL.C X$ cc DEFCLP.C X$ cc DESCR.C X$ cc DIRTY.C X$ cc DOCOND.C X$ macro ERRMES.MAR X$ cc EXECUTE.C X$ cc FDATE.C X$ cc FILE.C X$ cc FILETYPE.C X$ cc FINDCONS.C X$ message FTLERRMSG.MSG X$ cc GETTARGET.C X$ fortran LENS.FOR X$ cc LINETYPE.C X$ cc LOG.C X$ cc MAKE.C X$ cc MEXIT.C X$ fortran MYNAME.FOR X$ cc PROCESSL.C X$ macro QUADMATH.MAR X$ cc REFSYM.C X$ fortran SCTLC.FOR X$ cc SEARCH.C X$ macro SETCTLC.MAR X$ cc SETDEBUG.C X$ fortran SETEXH.FOR X$ cc SHARP.C X$ cc SHEXEC.C X$ cc SHMAKE.C X$ cc SHSKIP.C X$ cc SKIPTOELS.C X$ cc SPRINTRMT.C X$ cc STAB.C X$ cc SUBPROC.C X$ cc TIMER.C X$ cc WHOAREWE.C X$ ! X$ ! Now create the library and stuff everything into it. X$ ! X$ library /create/object makelib *.obj X$ delete *.obj; X$ ! X$ ! Now link the goddam thing. X$ ! X$ link /exe=make makelib/lib/incl=make, sys$library:crtlib/lib $ $ babble "Creating LIBTARGET.C" $ create libtarget.c # include <stdio.h> int lib_target (target, source, module, type) char *target, *source, *module, *type; /* * Parse a library reference. Target is a symbol containing the full * specification, source is a symbol to define as the source file * name, and module is the symbol to define as the module name. * "target" is redefined to have only the library name. Target * is also forced to have a type, using "type" if necessary. * * Return status is TRUE iff the target was successfully parsed. * * Jonathan Corbet * National Center for Atmospheric Research, Field Observing Facility. */ { char *strchr (), *prp, *vbp, *tp, *file_type (), *st_getsym (); char *temp, *mknstr (), *cp; /* * Separate out the library name. */ if ((tp = st_getsym (target)) == NULL) c_panic ("No file for %s", target); if ((prp = strchr (tp, '(')) == NULL) c_panic ("Bad lib_target call: %s", tp); temp = mknstr (tp, prp - tp); /* * Make a copy of the whole target, the redefine "target" */ st_defsym ("MAKE$LIB_TEMP", tp); st_defsym (target, temp); free (temp); /* * Make sure the library name has a type. */ if (! file_type (st_getsym (target))) st_symcat (target, type); /* * Separate out the module name. */ tp = st_getsym ("MAKE$LIB_TEMP"); prp = strchr (tp, '('); if ((vbp = strchr (prp, '|')) == NULL) if ((vbp = strchr (prp, ')')) == NULL) { printf ("\nBad library format: %s", tp); return (FALSE); } temp = mknstr (prp + 1, vbp - prp - 1); st_defsym (module, temp); /* * Now we have to come up with a source name. If we found a |, * it is in the library specification. Otherwise we have to derive * it from the module name. */ if (*vbp == '|') { if ((prp = strchr (vbp, ')')) == NULL) { printf ("\nBad library format: %s", tp); return (FALSE); } free (temp); temp = mknstr (vbp + 1, prp - vbp - 1); } else { /* * For VMS V3 systems, fix up the file name. */ if (! st_getsym ("MAKE$VMS_V4")) { for (cp = temp; *cp; ) if (*cp == '_') strcpy (cp, cp + 1); else cp++; if (strlen (temp) > 9) temp[9] = NULL; } } /* * Now define the source name. We don't worry about the type here. */ st_defsym (source, temp); free (temp); return (TRUE); } $ $ babble "Creating LBRDEF.H" $ create lbrdef.h /* * Lbrdef for C. */ # define LBR$C_TYP_UNK 0 # define LBR$C_TYP_OBJ 1 # define LBR$C_TYP_MLB 2 # define LBR$C_TYP_HLP 3 # define LBR$C_TYP_TXT 4 # define LBR$C_TYP_SHSTB 5 # define LBR$C_TYP_DECMX 5 # define LBR$C_TYP_RDEC 127 # define LBR$C_TYP_USRLW 128 # define LBR$C_TYP_USRHI 255 # define LBR$C_CREATE 0 # define LBR$C_READ 1 # define LBR$C_UPDATE 2 # define LBR$_KEYNOTFND 0x269062 /* Obtained thru testlib... */ $ $ babble "Creating MAKE.DOC <--- Documentation! " $ create make.doc MAKE - A program maintainer. MAKE is a program that keeps track of other programs, insuring that everything is up to date. It is most useful for large programs that are kept in many source files, each of which may call for the inclusion of other files. MAKE reads a file describing all of the file dependancies, and makes sure that all dependant files are more recent than those depended upon, executing DCL commands to bring that state about if necessary. To use MAKE, you need to have the following line in your login file: X$ make :== $ds:[corbet.cmd.make]make [NOTE TO FOLKS WITH THE NET DISTRIBUTION: change the directory above to the one that contains your make executable!] MAKEFILES MAKE takes its input from a file termed a "makefile." The makefile describes file dependancies, contains DCL commands to be executed directly, and contains commands that MAKE interprets itself. The default name for the makefile is "1MAKEFILE.", so named so it will sort out at the top of a directory listing, but makefiles with other names may be used if desired. There are three different types of lines that may appear in the makefile. Direct command, or "DCL" lines are DCL commands to be executed directly. DCL lines always start with "$". Directives to MAKE start with "#", and are sometimes called "sharp" lines for that reason. Finally, "conditional" or "dependancy" lines start with the name of a target. All of these line types are discussed in greater detail below. Comments are marked with "!". Anything past an exclamation mark is ignored, up to the end of the line. If a line needs to be continued on another line, the first line should be ended with a backslash ("\"). There is no limit on the number of continuation lines. Command execution In the performance of its duties, MAKE often has to pass commands to the command interpreter (DCL). To do this, MAKE spawns a subprocess when it is initially invoked. Commands are passed to this process for execution. Note that command execution is asynchronous! A command to be executed is handed to the subprocess, and MAKE goes on to its next task. This allows some overlap of computations, but requires some care on the part of the user. Places where synchronization is important are pointed out below. Only one command is in execution at a time; if MAKE needs to execute a command, the program will block until execution of the previous command is finished. Symbol substitution MAKE maintains a symbol table, and substitutes symbols into all lines read from the makefile. Symbols are manipulated with the "#define", "#undef", and "#ifdef" directives described below. A symbol reference has the form "^symbol". When MAKE encounters such a string, the symbol table is searched for "symbol", and the corresponding value is substituted for the reference. If the symbol is not defined, a warning is printed to the terminal and the null string is substituted. There is no restriction on the length of a symbol or its value. Note that a string like "^symbol" has the same effect as "'symbol'" in DCL, or "$symbol" in the UNIX shells. An alternate reference form is "^{symbol}", which is useful when the symbol value is to be run up next to an alphanumeric string, where it would otherwise be impossible to parse out the symbol name. For your own safety, symbol names that start with "MAKE$" should be avoided, as all symbols used by MAKE itself start that way. DCL lines Any line that starts with "$" is executed directly by the subprocess. Thus a line like X$ directory would cause a listing of the current directory to appear on the screen. There are two variants on DCL lines. If a hyphen ("-") appears after the "$", the command itself is not echoed to the screen. By default, all DCL lines are echoed to the screen before execution. If a right angle bracket (">") appears after the "$" (or the "-") then the leading "$" is not prepended to the line sent to the subprocess. MAKE directives Lines that start with "#" are interpreted directly by MAKE. The legal "#" lines are: # chdir directory Causes MAKE to change the default directory of both the parent and subprocesses to "directory." This directive causes a subprocess syncronization. # cons Defines a construction. Constructions are described in their own section, below. # dirty file Deletes the entry for the given file or library reference from the file or library cache. The caches are described in their own section, below. It is not an error if the cache entry does not exist. # dump Lists out information to the terminal. The valid arguments to #dump are: constructions Lists the construction table. See the section on constructions, below. file_cache Lists the contents of the file cache. file_stats Lists information on the file and library caches. lib_cache Lists the contents of the library cache. symbols Lists the contents of the symbol table. # defclp Defines parameters on the command line as symbols with null value. See the section on the command line, below. # define symbol value Defines the symbol "symbol" with the given value, which may be null. If a definition of "symbol" already exists, it is silently discarded. # ifdef symbol If "symbol" has an entry in the symbol table, all lines up to the corresponding "#else" or "#endif" are executed. Otherwise, those lines are skipped, and the lines after the "#else" are executed, if one exists. All "#ifdef" lines must have a corresponding "#endif." #Ifdef's may be nested. # exit Causes execution of the current makefile to be terminated. If the current makefile is the only makefile, MAKE exits. # include file MAKE opens the given file and interprets its contents. When the end of the file is encountered, execution continues at the line after the "#include." # message line MAKE prints "line" to the terminal. # quit MAKE synchronizes with the subprocess, then immediately exits. # undef symbol The definition of "symbol" is removed from the symbol table. If "symbol" is not defined, the "#undef" line is a silent no-op. # wait Causes a subprocess synchronization. Constructions. A construction is a series of commands used by MAKE to generate one file from another. For example, if MAKE determines that the file PROGRAM.FOR is more recent than PROGRAM.OBJ, a construction from type ".FOR" to ".OBJ" will be searched out of the construction table, and invoked. The default construction in this case is a single command, being: fortran ^for_opts ^MAKE$SOURCE where the symbol "for_opts" is defined as the command line options for the FORTRAN command, which by default are "/check /nolist". The symbol "MAKE$SOURCE" is defined as the name of the file to be compiled, "PROGRAM" in this case. MAKE contains a substantial list of constructions by default. However, you will probably find that you wish to add new constructions, or change the existing ones. The "#cons" directive is used for this purpose. The form of a construction definition is: # cons from to line 1 . . . line n # end where "from" is the source type, such as ".FOR"; and "to" is the destination type, like ".OBJ". The lines consists of DCL commands to be executed to generate the destination file, without the leading "$". The "-" and ">" may be present, just like in DCL commands. Most MAKE directives may also appear within the construction. They are not interpreted until the construction is invoked. The directives that may not appear in a construction are: #cons, #include, #ifdef, #else, and #endif. Symbol references inside a construction are not substituted until the construction is invoked. Sometimes there is no source file from which to create the destination. A typical example of this situation is the creation of an object library. To define a construction of this type, specify the source type as "X". The default construction to create an object library would be given as: # cons X .olb library /create ^MAKE$TARGET # wait # end The necessity for the "# wait" is discussed below. Finally, sometimes the destination of a construction is an entry in a library. In this case, the destination type should be the type of the library, enclosed in parentheses. For example, the default construction to generate an object library entry from a FORTRAN file is: # cons .for (.olb) fortran ^for_opts ^MAKE$SOURCE library ^MAKE$TARGET ^MAKE$SOURCE delete ^MAKE$SOURCE.OBJ # end One important thing to remember when defining library constructions: It is critical that any DCL commands that operate on the library MUST terminate before the program resumes after invoking the construction. Since subprocess execution is asynchronous, the last command in a construction is usually still executing when the program goes on to the next line. If this line operates on a library, MAKE may collide with the subprocess in trying to access the library. Thus, the construction above to create a library must include a "# wait". The DELETE command in the fortran construction is sufficient to insure that the subprocess is done with the library before MAKE tries to access it again. Dependancy lines Dependancy lines are the heart of MAKE. These lines specify which files must be up to date, and which files they depend on. The form of a dependency line is: flref: flref flref .... where "flref" is a file or library reference. The simplest form of a "flref" is just a file name. Thus, the simplest dependency line is: program: The response to the this line by MAKE would be: (1) a default type of ".OBJ" would be assumed, thus making the target "PROGRAM.OBJ". MAKE's task would thus be to insure that PROGRAM.OBJ is up to date. (2) MAKE would derive the appropriate source type. MAKE does this by looking at all files with name "program", searching for one that it can use to generate PROGRAM.OBJ. If the files that exist are "PROGRAM.OBJ", "PROGRAM.FOR", and "PROGRAM.LIS"; MAKE would take PROGRAM.FOR as the source, since a construction exists from .FOR to .OBJ. Once a construction is found, it is invoked if (1) PROGRAM.OBJ does not exist, or (2) PROGRAM.FOR is more recent than PROGRAM.OBJ. Now suppose that PROGRAM.FOR contained a line of the form "include 'incl.inc'". If "incl.inc" has been modified since the last time PROGRAM.FOR was compiled, then PROGRAM.OBJ is out of date. To include this dependancy, the line should read as: program: incl.inc The default type for include files -- that is, those that appear to the right of the colon -- is ".H". MAKE also supports libraries. If you have a subroutine SUBR that exists in SUBR.FOR, but you keep the object code in LIB.OLB, the appropriate dependency line is: lib(subr): The full form of a library reference is lib_name(module|source) where "lib_name" is the name of the library, "module" is the name of the module, and "source" is the name of the source file. So, if "UTILITY.FOR" contained a function named "FUN", the reference would be: lib(fun|utility) If the source name is not present in the library reference, it is derived from the module name by (1) deleting all underscores, and (2) truncating the name to 9 characters. (If the symbol MAKE$VMS_V4 is defined this translation is not done). Library references may appear on either side of the colon. The default library type is ".OLB" for references to the left of the colon, and ".TLB" for those on the right. The following symbols are defined before a construction is invoked, and may be used by the construction. MAKE$TARGET The name (no type) of the file to be generated. For library constructions, the target name is the name of the library. MAKE$TARGET_TYPE The type of the target file. MAKE$SOURCE The name (no type) of the source file. For compatibility, the symbol "$" is also defined with the source file name. MAKE$SOURCE_TYPE The type of the source file. MAKE$MODULE The name of the module being generated, in a library construction. MAKE$OUT_OF_DATE The name of the file that is more recent than the target file. If more than one file is more recent, only the first one found by MAKE is defined as this symbol's value. If the target does not exist, the target name is used. The command line The format of the MAKE command line is: X$ make [flags] [params] Flags to the MAKE command are UNIX style, set off with "-". The legal flags are: -dsymbol=value Defines the given symbol as having the given value. An alternate form is "-dsymbol", which defines the symbol as having a null value -f file Uses "file" as the makefile, instead of the default of "1MAKEFILE.". The symbols "1" through "9" are defined as the remaining parameters on the command line. Thus for the following command X$ MAKE -f junk. xxx yyy zzz MAKE would read the file "JUNK."; the symbol reference "^1" would substitute as "xxx", "^2" as "yyy", "^3" as "zzz", and "^4 through "^9" as the null string. A note about case: DCL, on reading the command line, converts the entire thing to upper case. The C startup routine, not to be outdone, responds by converting the whole thing back to lower case. Thus, unless defeated with double quotes, all symbols and values will be defined in lower case. Finally, the #defclp command is another way to define symbols on the command line. When MAKE encounters a #defclp (DEFine Command Line Parameters) command, every parameter on the command line is defined as the name of a symbol with null value. See the example makefile at the end of this document for a use of this command. To deal with the case switching that goes on, all command line parameters are defined in both upper and lower case. File and library caches Checking the date on a file or library entry is a relatively expensive operation. In an attempt to minimize these operations, MAKE caches both file and library module dates. This attempt is not always successful, and may cause trouble in a few cases. The operation of each cache is described below, to aid the user in obtaining the best results. The file cache is a simple pairing between file names and dates. A hashing scheme is used, so a search of the cache is usually a relatively quick operation. However, if file cache hits are rare, the cache does little if any good. The file cache is most helpful for programs where the same files are included into several different source files. In a typical makefile, include files are the only ones that are checked more than once, so if they are relatively few the user is probably better off disabling this cache. To disable the cache, simply define the symbol MAKE$DIS_FILE_CACHE. The library cache is more complicated, since it must keep track of both libraries and modules. Library names are hashed, but modules are kept in a linear linked list. Search times can thus be long for makefiles with large numbers of library references. If it is determined that the library cache does not help, it may be disabled by defining the symbol MAKE$DIS_LIB_CACHE. Statistics on cache hit rates may be obtained with the "#dump file_stats" command. The use of this command, along with MAKE$TIMER, described below, can aid the user in finding out whether (s)he is better off with or without the caches. Special symbols Certain symbols have a special meaning to the program, and may be used to alter its behaviour. They are: MAKE$DIS_FILE_CACHE When this symbol is defined, the file cache is disabled. MAKE$DIS_LIB_CACHE When this symbol is defined, the library cache is disabled. MAKE$SILENT When MAKE$SILENT is defined, no DCL commands are echoed to the terminal, whether they are preceded by a hyphen or not. MAKE$TIMER This symbol may be used for performance measurements. When this symbol is defined, the run-time timer is initialized. When either (1) it is undefined again, or (2) the program terminates, elapsed time, CPU time and I/O statistics are put out to the terminal screen. MAKE$TIMER may be used in evaluating the usefullness of the library and file caches. MAKE$VMS_V4 This symbol changes the way file names are derived from library module names. If it is not defined, module names are (1) stripped of underscore characters, and (2) truncated to nine characters. Otherwise, there is no translation performed. Miscellaneous hints The subprocess created by MAKE thinks it's a batch job. You may that it is deluded, but it certainly is not interactive. Anyway, the result is that unless you specify otherwise, the compilers will produce listing files, and the linker will produce a map. Thus, the default options for the compilers all include "/nolist". If you decide to abort a MAKE run, it is suggested that you do so with ^C, instead of ^Y. ^C causes immediate termination of the subprocess, while ^Y does not cause such termination until image rundown. When the system is loaded, spawning a subprocess can take a long time. If MAKE seems to go off into the ozone when first invoked, it is probably hung up in the spawn. The worst part of this "feature" of VMS is that the spawn, once initiated, can not be stopped even with ^Y. If you start a MAKE on a busy day, then change your mind, you may have to wait a while anyway. MAKE tries to optimize library module checking by only closing libraries when necessary. The definition of "necessary" is (1) when a construction is invoked, or (2) when a DCL command is executed. Thus, DCL commands sprinkled throughout a makefile that uses libraries can signifigantly slow execution. Speed may be increased in this case by grouping the DCL commands together whenever possible. An example makefile. Here is the makefile for MAKE itself. The exact actions result from just how MAKE is invoked. If the command is X$ MAKE then a version of MAKE is created, but nothing special is done with it. When the command is X$ MAKE debug a version with the interactive debugger is created. Finally, when the command is X$ MAKE install then the copy made is installed as the production version of make. ! ! Makefile for make version 3. ! ! Define the command line parameters as symbols. ! # defclp ! ! If we are making a debug version, define options appropriately. ! # ifdef DEBUG # define c_opts /nolist /debug /nooptimize # define linkopts /nomap /debug # else # define c_opts /nolist # define linkopts /nomap # endif ! ! Disable traceback if this is to be an installed version. ! # ifdef INSTALL # define linkopts ^linkopts /notrace # endif # define LIB makelib.olb ! ! Get timer info ! # define MAKE$TIMER ! ! Fancy constructions. They just print a line telling which files are ! to be recompiled, then do the work silently. ! # cons .c (.olb) # message ^MAKE$SOURCE\ [^MAKE$OUT_OF_DATE] - cc ^c_opts ^MAKE$SOURCE ! All this stuff executes silently - library ^LIB ^MAKE$SOURCE - delete ^MAKE$SOURCE.obj;* # end # cons .c .obj # message ^MAKE$SOURCE\ [^MAKE$OUT_OF_DATE] - cc ^c_opts ^MAKE$SOURCE # end # cons x .olb # message Library is gone -- recreating - library /create ^$ # wait # end ! ! Make sure that the library exists. ! ^LIB: ! ! The main program does not go into the library. ! make.obj: make ! ! Everything else does. ! ^LIB(chdir): make ^LIB(cons): ltype ^LIB(ccons): ^LIB(charutl): ^LIB(checkdef): ^LIB(comline): ^LIB(dcl): ^LIB(defclp): ^LIB(dirty): ^LIB(docond): fdate ^LIB(execute): ^LIB(fdate): fdate make ^LIB(file): make ^LIB(filetype): ^LIB(findcons): make ^LIB(gettarget): ^LIB(libtarget): ^LIB(linetype): ltype ^LIB(mexit): ^LIB(processl): ltype ^LIB(refsym): ^LIB(search): make ^LIB(setdebug): ^LIB(sharp): make ^LIB(skiptoels): make ^LIB(stab): ^LIB(timer): make ! ! Do the link, if required. ! # cons .obj .exe link ^linkopts make,^LIB/lib,sys$library:crtlib/lib # end # wait ! Make sure all work is done. make.exe: ^LIB(chdir) ^LIB(cons) ^LIB(ccons) ^LIB(charutl) \ ^LIB(comline) ^LIB(dcl) ^LIB(dirty) ^LIB(docond) \ ^LIB(execute) ^LIB(fdate) ^LIB(file) ^LIB(filetype)\ ^LIB(findcons) ^LIB(gettarget) ^LIB(libtarget) ^LIB(linetype)\ ^LIB(mexit) ^LIB(processl) ^LIB(refsym) ^LIB(sharp) \ ^LIB(skiptoels) ^LIB(stab) ^LIB(search) ^LIB(checkdef)\ ^LIB(timer) ^LIB(defclp) ! ! Install the executable if necessary. The previous version is saved, ! just in case this one does not work. ! # ifdef INSTALL X$ rename cmd:make.exe cmd:make.sav X$ copy make.exe cmd: X$ purge cmd: # else # message ------------------- No installation ----------------- # endif # dump file_stats $ babble "Done. (whew!) $ exit -- Jonathan Corbet National Center for Atmospheric Research, Field Observing Facility {seismo|hplabs}!hao!boulder!jon (Thanks to CU CS department)