Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

A Parallel Make With Desqview


NOV89: A PARALLEL MAKE WITH DESQVIEW

A PARALLEL MAKE WITH DESQVIEW

dvmake can easily run four tasks at once

Mark Streich

Mark is currently a graduate student at the University of Colorado in Boulder, specializing in compilers and parallel computers. He may be contacted through the DDJ office.


The make utility started out as a Unix tool for creating executable applications from a large number of program files. Its main purpose is to determine which files of an application need to be recompiled and issue whatever commands are necessary to do so.

As computers with multiple processors became available, some developers created make utilities capable of executing the various make commands on separate processors. This allowed each processor to make a separate target, which resulted in faster make times, although the speedup was not as great as expected.

Unfortunately, parallel computers have not yet reached the masses, although multitasking environments and operating systems for single-CPU computers have; such operating systems, Unix and OS/2 among them, are great -- if you have several megabytes of memory, a fast hard disk, a 386, and the money to buy the toolkits.

With this in mind, I decided to develop a parallel make that DDJ readers can both use and afford. After surveying the available development environments, I turned to Quarterdeck Office System's (QOS, Santa Monica, Calif.) DESQview, which can juggle programs running on everything from the original to fast 386-based PCs, to create a parallel make I call dvmake (short for "DESQview make"). Listing One, page 86, lists the C code for the program.

dvmake requires DESQview in order to run. DESQview-specific applications like dvmake make extensive use of DESQview's API and usually take advantage of DESQview's ability to spawn new tasks, display menus, and communicate with other applications. (Other types of DESQview applications include those that act differently running under DESQview than they do running under DOS alone, as well as those that don't take advantage of any of DESQview's capabilities.)

What Is There to Gain?

The idea behind parallel computers, and supposedly behind multitasking software, is that a computer can get more done in the same amount of time if it's doing two or more things simultaneously.

make often needs to do several things, and until now it had to do them one after another. For example, make may have to compile three C source files one after another, then link the object files together to create an executable program. Let's assume that it takes 1 minute for each compilation and 1 for the linking step for a total of 4 minutes.

dvmake, on the other hand, can easily run four tasks at once, so theoretically, you can remake the entire program in 1 minute. Wrong. The best that you could hope for is 2 minutes because the linking step can't even start until all of the object files have been created (meaning the source files have been completely compiled).

Let's assume it takes 2 minutes to compile a program called farm.c, and that you only have enough memory to run two tasks at a time. If you compile both a program called pig.c and another called cow.c in parallel (taking 1 minute), and then compile farm.c, you spend 3 minutes compiling. If you rearrange the scheduling, you could compile farm.c and pig.c in parallel, and after pig.c completes, then compile cow.c. This ordering would take only 2 minutes.

Now that you know how to schedule parallel tasks, you should know that dvmake does nothing to optimize the scheduling because no matter what you do, a single-CPU computer requires (at least) the same amount of time to run several tasks in parallel as it does to run the same tasks sequentially. After all, it can only execute one instruction at a time.

Who's on First?

Although dvmake does not optimize the scheduling, it does have to guarantee that a target file's dependents are available before the target can be made. This means: All object files must be created before they can be linked together to create the executable program.

dvmake uses a three-step process to determine what needs to be made and then, using a "greedy" algorithm, schedules those items to be made. (For a description of the greedy algorithm, see "Simulated Annealing" by Michael McLaughlin, DDJ, September 1989.)

The first step is similar to the original mk program written by Allen Holub (DDJ, August 1985 ). A binary tree, sorted on the target file name (being_made), is created by reading the mkfile. Each node in the tree contains the target, its dependents, its time and date stamp, and the commands to remake the target. The dependencies( ) function creates this binary tree.

The binary tree is traversed recursively in make_queue( ), starting at First, which is either the first target listed in the mkfile or at the target name you add to the command line when running dvmake. Rather than traversing the binary tree depth-first, left-to-right, the recursion is based on the dependents of the target. For a particular target file to be made, it must be either a direct or indirect dependent of First.

If a target file has dependents that have time stamps more recent than its own, the target file is added to a queue of items to be made (MkQueue). The target's time stamp (in the binary tree, not on disk) is changed to fool any parents of the target file into thinking that they too must be made.

You may wonder why I put the item to make in a queue, when I could just as easily make the item once I know it has to be made. (In fact, this is exactly how the mk program works.) The answer is scheduling.

By placing the targets to make in a queue, we can easily find any targets whose dependents have all been made and start making them in parallel. We're not stuck with the order provided to us by recursively searching the binary tree. If some intrepid reader decides to add a smarter scheduling algorithm, it is much easier to do so with a queue.

dvmake is a sequential program up through the execution of make_queue( ). By keeping most of the program sequential, it can be debugged using available tools. Once multiple tasks are created, neither CodeView nor Turbo Debugger can help, and the QOS API Debugger only tracks the API calls our program makes, not where our program is executing.

Once make_queue( ) has completed, MkQueue is either empty or contains a list of target files to make. If there is something to make, we start the third part of the make process. To help you understand what is about to be explained, Figure 1 shows when parallel tasks are started and stopped.

The startup( ) function creates two new, concurrently executing tasks. As Figure 2 illustrates, one task executes the dvmenu( ) function that displays a small menu that allows you to stop the program. The other task runs the make( ) function that determines which target files can be made.

Both of the new tasks run in parallel with the "main line" program that calls the output( ) function. output( ) manages the output from the various programs that are specified in the command blocks of the mkfile.

Figure 2: dvmake being executed

# Make farm.exe using the turbo c
   compiler

farm.exe: cow.obj pig.obj farm.obj
   tlink /x farm cow pig,farm,,cc

pig.obj: pig.c animals.h stdio.h
   tcc -mc -c pig

farm.obj: farm.c stdio.h
   tcc -mc -c farm

cow.obj: cow.c animals.h stdio.h
   tcc -mc -c cow

# Null targets not needed in a "real"
       makefile
# but required for dvmake

animals.h:

stdio.h:

pig.c:

cow.c:

farm.c:

The make( ) function constantly checks to see if there is a target file in MkQueue that has all of its dependents available, as noted by the made flags in their nodes. If it finds one, it attempts to start an application window capable of running the commands listed below the target in the mkfile. (Starting an application window is identical to using DESQview's Open Window command.)

Once the application window has been created, another task is started that executes the dispatch( ) function. dispatch( ) sends commands to the application window to create the target file, and when all of the commands have been sent, it sends an Exit command for the window to close itself.

Although I've glossed over many of the details so far, you probably have realized that there are always three tasks executing concurrently, plus two tasks for each target file being made. It may seem like overkill, but the division of labor among the tasks keeps intertask communication (and interruptions) to a minimum. And there is little housekeeping on our part. Each task does its work and stops. DESQview gets rid of completed tasks.

More Details.

The DESQview API

Before we get into the details of the program, you should have a basic understanding of the DESQview API. The API allows you to access numerous "objects" such as windows, keyboards, and mouse pointers. As I've already mentioned, you can start or stop separate threads of control known as "tasks." The API also provides mailboxes for intertask communication or, as we use them, for semaphores.

Semaphores are extremely important in parallel programs because they guarantee that only one task can access a device or data structure at a time. dvmake uses them to ensure that only one task accesses a given queue at any time. If several tasks attempted to get an item from a queue at the same time, they might all get the same item, rather than separate items that they really want.

Mailboxes are treated as semaphores by using two API mailbox functions, mal_lock( ) and mal_unlock( ). If a mailbox is currently locked, any tasks that attempt to lock it are suspended until it becomes unlocked, at which time the first task that tried to lock it succeeds.

All API objects are known by their handle, an unsigned long integer. Each object is accessed using that object's functions. Window function names start with win_, task functions with tsk_, mailbox functions with mal_, application functions with app_, and so on. API functions that do not fit one of the object categories start with api_.

Meet Your Maker

The make( ) function is only concerned with finding the target files in MkQueue that have all of their dependents available. It removes a target from the queue, runs through the target's dependent list (depends_on), and either puts the target back in MkQueue (if the target has unmade dependents) or gets ready to start making the target.

Making a target requires an application window capable of executing the commands listed in the mkfile and another task to issue all of the commands to the application window. The application window is simply a DOS shell that waits for commands to be entered through the keyboard.

The API function, app_start( ), takes as an argument a pointer to a program information file (PIF) that has been either loaded into memory from disk or created in memory, which pif_init( ) does. The PIF structure has the same layout as the DVP files that DESQview creates for applications in the Open Window menu.

The app_start( ) function has several problems that need to be addressed. DESQview can only start as many programs as can fit in RAM. When it attempts to start a program when there is not enough RAM, DESQview swaps other programs to disk to make room, causing those programs to stop whatever they were doing.

You should set System Swapping in the Rearrange-Tune Performance menu to No to avoid the performance penalty of swapping. Or you can simply set the Swapping option to No for the window in which you run dvmake. If you don't, dvmake may get swapped to disk, leaving the open application windows idle.

Another problem with app_start( ) is that whenever it is called, any DESQview menus that are being displayed are removed from the screen as if you had escaped out of them. Because make( ) sits in a loop while attempting to start applications, you are essentially forbidden access to any of DESQview's menus, including resizing or moving windows.

To access DESQview menus, you must first enter DESQview with "Exdev" via the menu that is put on the screen by dvmenu( ). When you select the menu option, the make( ) function stops calling app_start( ) until you either quit or deselect the option. This does not stop the other tasks from running, it merely keeps any new tasks from starting.

The final problem I had with app_start( ) is that the application window it creates always becomes the foreground window. This can be partially overcome by forcing the application to the background with the app_go-back( ) API call. Even with the small time delay between the two calls, the new application window can "eat" some keystrokes, including dvmake, that you may have typed into the foreground window.

After the application window is open, another task is created that runs the dispatch( ) function. dispatch( ) sends commands to the application by placing the keystrokes directly into the application's keyboard buffer. It accomplishes this by using the key_write() API call.

More Details.

dispatch( ) also plays a key role in how you see the output from the various commands that are running concurrently. Rather than forcing you to make sense of output being displayed in each of the application windows, dispatch( ) attempts to redirect each command's output to a temporary file. The output( ) function is responsible for displaying the temporary files in the main window. The output is displayed in a reasonable order, and you can then redirect it to any file you choose.

Making Something

Running dvmake is extremely simple, although there are many cautionary notes. You can run dvmake in a DOS window with 64K or more RAM available to it. The window must be nonswappable, have about 15K of system memory (setable on the Advanced Options screen from Change a Program), be able to run in the background, not write directly to the screen, and share the CPU when in the foreground.

Because dispatch( ) sends commands to application windows through the keyboard, any programs you use cannot intercept keyboard input. If they do, the commands that dispatch( ) sends may be received by your program rather than the application window, and will therefore not be executed as DOS commands.

Most development tools allow you to specify all needed parameters on the command line, which you must do. There is little reason to run several programs at the same time if all of them are expecting input from you, because you can only type into one of them at a time.

The command to run the program is: dvmake [-w] [-k nnn] [target], and all three parameters are optional. The -w switch allows you to see the application windows rather than the default of hiding them. The -k nnn switch allows you to specify the amount of memory to give to each application window if the default of 256K is either not enough or too much. You can specify which target file to make by adding its name to the command line.

You must have the DESQview API C Library to compile the program, although I have included a runnable version on this month's source code disk. Be sure to compile the program with byte-alignment ON, or else the PIF structure will not be correct.

The Bad News

I forewarned that running multiple tasks on a single-CPU computer was inefficient; I must slightly modify Jerry Pournelle's Law from "One user per CPU" to "One task per CPU."

To test dvmake, I had it make the Small C compiler using the Small C compiler and Borland International's Turbo Assembler and linker. I ran the program using DESQview, Version 2.24, on my Dell Model 310 (20MHz 386) with 2 Mbytes of memory and Quarterdeck's QEMM memory manager. I also compared dvmake's time with Borland's make program running under DOS alone and as the only task under DESQview. Table 1 shows the results. Comparing the times for Borland's make shows how much overhead is involved to simply have DESQview available. There was not much additional overhead to run multiple tasks under dvmake. For those of you who are interested, I was able to get all four parts of Small C compiling at once.

Table 1: dvmake comparison with make

  Program          Time
                   (seconds)
----------------------------

  dvmake           332
  make (DESQview)  278
  make (DOS)       131

Extending the Program

You can improve dvmake in any number of ways, many of which I considered doing, but rejected because the program would have ended up all make and little DESQview.

You may consider adding the aforementioned optimized scheduling algorithm, although as I mentioned, you will get little more than the satisfaction of knowing that the program is not wasting its time.

A more useful addition would be some type of parameter for each target file that specifies the amount of RAM to allocate for its application window. You could then tailor each window to the programs that make the target, and get more tasks running concurrently.

Final Impressions

dvmake should satisfy everyone's desire to put their computer through its paces. Those of you with 33MHz 386s and 8 Mbytes of RAM should be able to slow it down to about half the speed of the original PC, creating more leisurely development cycles.

Overall, I was impressed with how the DESQview API performed. As the source code shows, it's fairly simple to create multitasking programs. This is a far cry from the two-page "Hello, World" programs using OS/2, but then again, you still have to shoehorn your programs into 640K.

References

Baalbergen, Erik H. "Design and Implementation of Parallel Make," Computing Systems, Vol. 1, No. 2, Spring 1988.

Holub, Allen. "C Chest," Dr. Dobb's Journal, August 1985.

Davis, Stephen R. DESQview, A Guide to Programming the DESQview Multitasking Environment, (Redwood City, CA: M&T Books, 1989).

Almasi, George S. and Gottlieb, Allan. Highly Parallel Computing, (Redwood City, CA: Benjamin/Cummings, 1989).

The DESQview Toolset

To create DESQview-aware or DESQview-specific programs, you need some of the API tools Quarterdeck provides. Although dvmake only uses the API C Library, QOS also provides an API Reference, Turbo Pascal Library, API Debugger, and Panel Designer.

The API reference is the basis for the libraries, and includes the assembly language hooks required to use the API, and a manual that describes the concepts embodied in the API. You could get by with only this reference, but you can save a lot of time by also purchasing one of the libraries.

The C and Turbo Pascal Libraries provide the same functions as the reference, but uses C or Pascal rather than assembly language. The C Library supports compilers available from Borland, Microsoft, Lattice, Metaware, and Watcom, and use either the ANSI C definition or the more traditional K&R definition. The Turbo Pascal Library supports version 4.0 and later.

The API Debugger is neither a source-level nor an assembly-level debugger. It allows you to debug multiple tasks, trace and break on various types of API calls, and display the contents of memory or the registers used in the calls. The source debuggers provided with the supported languages are unable to debug multiple tasks running under DESQview, so debugging can be difficult.

The API Debugger was unable to let me find out why a window running dvmake would disappear when the program completed. It would also make itself the foreground application whenever dvmake started a new application window, which made it impossible to debug some keyboard-related problems.

The Panel Designer is handy if you need to design help or message windows, selection boxes, or menus. dvmake only has one menu, and you can get the same results with library calls, so I decided to ignore the Panel Designer for this project. -- M.S.

What Makes make Make

The concept of what make does is simple, but is best explained with an example. For the sake of continuity, I repeat the example provided by Allen Holub in the August 1985 DDJ. Allen originally wrote mk ("a make with half of it missing"), upon which dvmake is based.

Suppose you want to create an executable program called farm.exe. farm.exe is based upon three object files, farm.obj, cow.obj, and pig.obj, all of which are, in turn, based upon C source files. All three source files have an #include <stdio.h> statement, and both cow.c and pig.c have an #include <animals.h> statement.

If you change cow.c, you need to recompile it to create a new cow.obj, and then link this with the other object files to create farm.exe. If you change something in animals.h, you need to repeat the same process for both cow.c and pig.c because they "depend" upon animals.h.

You describe the dependencies in a makefile, or in dvmake's case, a mkfile. You also include in the mkfile the commands to be executed to either recompile or relink the appropriate files. dvmake reads the mkfile, figures out what has changed, decides what needs to be updated, and executes the commands. A mkfile for the above program is shown in Figure 3. mkfiles have four parts:

  • You designate a line as a comment by placing a # sign in column 1 of the line.
  • 'Targets" are files that dvmake has to decide whether or not to make. You specify targets by typing their names followed immediately by a colon.
  • On the same line as the target, you list the "dependencies." Dependencies are any files that the target depends upon; that is, if the dependency file changes, you want the target file updated. A target is not required to have any dependencies, and if it has none, it is always made.
  • On the line or lines immediately after the target line, you list the commands required to make the target. There may be as few as zero, and up to MAXBLOCK commands listed. An empty line is required to terminate the command block and separate it from further target lines.
All lines may be continued to the next line by ending with a \ character, just as in C. For example

  This is\
  one line\
  of text.

For those of you who are make aficionados, note that I do not mention macros. Macros allow you to simplify many of the command lines. Don't fret over this loss, however, because as you will see in Table 1, dvmake won't be your primary make utility. -- M.S.

_A PARALLEL MAKE WITH DESQVIEW_ by Mark Steich

[LISTING ONE]

<a name="022e_0014">
/*---------------------------------------------------------------------
 * DVMAKE, adapted by Mark Streich
 * Original Mk by Allen Holub, Doctor Dobb's Journal, August 1985
 *
 * Some functions may be specific to Borland Int'l. Turbo C 2.0.
 * DESQview 2.01 (or later), and API C Library required.
 *
 * Compile with byte alignment ON.
 *
 * Run from within a non-swappable DOS window with at least 64K
 * of memory, 15K of system memory, runs in the background and
 * shares the cpu when in the foreground.  On non-386 systems,
 * you will have to say that it does not write directly to the screen.
 */

#include <stdio.h>        /* for printf(), sprintf(), etc.   */
#include <io.h>           /* for open(), close(), getftime() */
#include <fcntl.h>        /* for O_RDONLY used in open()     */
#include <string.h>       /* for various string functions    */
#include <stdarg.h>       /* for variable argument err()     */
#include "dvapi.h"        /* provided by DESQview API        */

/*----------------------------------------------- DEFINES -----------------*/

#define MAXLINE  127      /* Maximum DOS command line length    */
#define MAXBLOCK 16       /* Max number of lines in an action   */
#define MAXDEP   32       /* Max number of dependencies         */
#define MAXFNM   64       /* Max length of file name/dir        */
#define COMMENT  '#'      /* Delimits a comment                 */
#define MAKEFILE "mkfile" /* Name of makefile                   */
#define OLDTIME  0x0      /* the Beginning of Time (very old)   */
#define NEWTIME  0xFFFFFFFFL /* the End of Time    (very young) */

#define DV_VER   0x201    /* DESQview version required: 2.01    */
#define STKSIZE  512      /* size of tasks' local stack         */
#define NORMAL   0        /* normal status state                */
#define ACCESSDV 1        /* access DESQview, so no new tasks   */
#define ABORT    2        /* kill of all processes              */

/*----------------------------------------------------------------------
 *    iswhite(c)        evaluates true if c is white space.
 *    skipwhite(s)      skips the character pointer s past any white space.
 *    skipnonwhite(s)   skips s past any non-white characters.
 *    waitwhile(event)  gives up task's time slice while event is true.
 */

#define iswhite(c)       ((c)==' ' || (c)=='\t')
#define skipwhite(s)     while( iswhite(*s) )        ++s;
#define skipnonwhite(s)  while( *s && !iswhite(*s) ) ++s;
#define waitwhile(event) while( event ) api_pause();

/*----------------------------------------------- TYPEDEFS ----------------












 * The entire mkfile is read into memory before it's processed.  It's
 * stored in a binary tree composed of the following structures:
 * depends_on and do_this are argv-like arrays of pointers to character
 * pointers. The arrays are null terminated so no count is required.
 * The time field is a 32 bit ulong consisting of the date and time
 * fields returned from DOS. The date and time are concatanated with
 * the date in the most significant bits and the time in the least
 * significant. This way they can be compared as a single number.
 */

typedef struct _tn             /* node for dependencies */
{
    struct _tn   *lnode;       /* pointer to left sub-tree         */
    struct _tn   *rnode;       /* pointer to right sub-tree        */
    char         *being_made;  /* name of file being made          */
    char         **depends_on; /* names of dependent files         */
    char         **do_this;    /* Actions to be done to make file  */
    ulong        time;         /* time & date last modified        */
    ulong        apphan;       /* what app is making this item     */
    char         made;         /* flag indicating made or not      */
    int          tsknum;       /* what task number was assigned    */
}
TNODE;

typedef struct _qn             /* queue of items */
{
    void         *item;        /* item in queue          */
    struct _qn   *next;        /* next item in the queue */
}
QNODE;

typedef struct  /* definition of Program Information File (PIF) */
{
    char reserved1[2];
    char prog_title[30];       /* blank filled */
    uint maxmem;               /* max memory size in k-bytes */
    uint minmem;
    char program[64];          /* command to start program, 0-terminated */
    char def_drive;            /* 'A', 'B', ..., or blank */
    char def_dir[64];          /* default directory, 0-terminated */
    char params[64];           /* parameters, 0-terminated */
    byte init_screen;          /* screen mode (0-7) */
    byte text_pages;           /* # of text pages used */
    byte first_intr;           /* # of first interrupt vector to save */
    byte last_intr;            /* # of last interrupt */
    byte logical_rows;         /* logical size of window buffer */
    byte logical_cols;
    byte init_row;             /* initial row to display window */
    byte init_col;
    uint system_mem;           /* system memory in k-bytes */
    char shared_prog[64];      /* shared program file name, 0-terminated */
    char shared_data[64];      /* shared program data, 0-terminated */
    byte control_byte1;        /* control byte 1, encoded as follows:
                                    80H - writes direct to screen












                                    40H - foreground only
                                    20H - uses math coprocessor
                                    10H - accesses system keyboard buffer
                                    01H - swappable */
    byte control_byte2;        /* control byte 2, encoded as follows:
                                    40H - uses command line parameters
                                    20H - swaps interrupt vectors */
    char open_keys[2];         /* keys to use for Open Window menu */
    uint script_size;          /* size of script buffer in bytes */
    uint auto_pause;           /* pause after this many tests for input
                                  during one clock tick (normally 0) */
    byte color_mapping;        /* non-zero to disable auto color mapping */
    byte swappable;            /* non-zero if application is swappable */
    char reserved2[3];         /* should be zero */
    byte auto_close;           /* non-zero to automatically close on exit */
    byte disk_reqd;            /* non-zero if diskette required */
    byte reserved3;            /* MUST HAVE VALUE OF 1 */
    byte shared_mem;           /* non-zero if program uses shared system mem
*/
    byte physical_rows;        /* initial size of physical window */
    byte physical_cols;
    uint max_expanded_mem;     /* max amount of expanded mem avail to app */
    byte control_byte3;        /* control byte 3, encoded as follows:
                                    80H - automatically assigns position
                                    20H - honor maximum memory value
                                    10H - disallow Close command
                                    08H - foreground-only when in graphics
                                    04H - don't virtualize */
    byte key_conflict;         /* keyboard conflict (0-4, usually 0) */
    byte graphics_pages;       /* # graphics pages used */
    uint system_mem2;          /* system memory - overrides system_mem */
    byte init_mode;            /* initial screen mode, normally 0FFH */
    char reserved4[22];

} PIF; /* note that the sizeof(PIF) MUST be 416, or else we've made a typo */???

/*----------------------------------------------- GLOBAL VARIABLES --------*/

static TNODE   *Root     = NULL;   /* Root of file-name tree     */
static FILE    *Makefile;          /* Pointer to opened makefile */
static int     Inputline = 1;      /* current input line number  */
static char    *First    = NULL;   /* Default file to make       */

static char    ShowWin   = 0;      /* Display tasks?             */
static char    Parallel  = 0;      /* Are we multitasking yet?   */
static char    Status    = NORMAL; /* processing status          */

static char    CurDir[MAXFNM];     /* Directory called from      */
static char    Error[MAXLINE] = "";/* Error saved for later printing */

static int     RunCnt    = 0;      /* how many tasks running     */
static int     MemSize   = 256;    /* default task memory size   */
static int     ReDirLen  = 0;      /* length of redirection file */













static QNODE   *MkQueue  = NULL;   /* queue of items to make     */
static QNODE   *TskQueue = NULL;   /* queue of tasks to run      */
static QNODE   *OutQueue = NULL;   /* queue of files to output   */

static ulong   TskQueueLock;       /* semaphore for TskQueue     */
static ulong   OutQueueLock;       /* semaphore for OutQueue     */
static ulong   AllocLock;          /* semaphore for malloc/free  */
static ulong   MainWin;            /* handle of Main Window      */
static ulong   MenuTsk;            /* handle of Menu Task        */
static ulong   MakeTsk;            /* handle of Make Task        */

static PIF     Pif;                /* default Prog Info File     */
static int     Lpif;               /* length of the PIF          */

/*----------------------------------------------- ERROR ROUTINES ----------*/

void err( char *msg, ... )
{
    /* print the message and optional parameter and either
     * stop immediately if we haven't started up parallel tasks,
     * or just set our status to ABORT and stop later
     */

    static char temp[MAXLINE];
    va_list argptr;

    /*  Print the error message, if we haven't already, and abort */
    if (!strlen(Error))
    {
        /* print the location of the error in the mkfile */
        sprintf(Error,"Mk (%s line %d): ", MAKEFILE, Inputline );

        va_start(argptr,msg);
        vsprintf(temp,msg,argptr);    /* print the error message */
        va_end(argptr);

        strcat(Error,temp);           /* and concatenate the two */
    }

    if (Parallel)                     /* are we multitasking? */
    {
        /*  notify everyone that there are problems, but don't stop yet */
        api_beginc();
        Status = ABORT;
        api_endc();
    }
    else                              /* not multitasking, so we can STOP */
    {
        fprintf(stderr,"%s",Error);   /* print the error message */

        mal_free(TskQueueLock);       /* get rid of our semaphores */
        mal_free(OutQueueLock);
        mal_free(AllocLock);














        api_exit();                   /* tell DESQview we're done */
        exit(1);                      /* and STOP */
    }
}

/*----------------------------------------------- MEMORY ROUTINES ---------*/

void *gmem( int numbytes )
{
   /*     Get numbytes from malloc. Print an error message and
    *     abort if malloc fails, otherwise return a pointer to
    *     the memory.  Uses semaphores because malloc() not re-entrant.
    */

    void *p;
    extern void *malloc();

    mal_lock(AllocLock);              /* get access to heap  */
    p = malloc(numbytes);             /* grab some memory    */
    mal_unlock(AllocLock);            /* free access to heap */

    if (p == NULL)                    /* were we successful  */
        err("Out of memory");

    return( p );
}

void fmem( void *ptr )
{
    /* Frees memory pointed to by ptr. */

    extern void free();

    mal_lock(AllocLock);   /* get access to heap */
    if (ptr) free(ptr);    /* free the memory if ptr not NULL */
    mal_unlock(AllocLock); /* free access to heap */
}

/*----------------------------------------------- QUEUE ROUTINES ----------*/

void enqueue(QNODE **queue,void *item)  /* add item to the queue */
{
    QNODE *qptr;

    /* get memory for new node */
    if ( (qptr = (QNODE *) gmem(sizeof(QNODE))) == NULL )
        err("Out of memory");
    else
    {
        qptr->item = item;    /* add the item */
        qptr->next = *queue;  /* point to the next item in the queue, if any
*/
        *queue = qptr;        /* point to the new front of the queue */
    }












}

void *dequeue(QNODE **queue)  /* return an item from the queue */
{
    QNODE *qptr,*qptr2 = NULL;
    void *iptr;

    if (*queue == NULL)       /* is the queue empty? */
        return( NULL );
    else
    {
        /* find the end of the queue */
        for (qptr = *queue; qptr->next != NULL; qptr = qptr->next)
            qptr2 = qptr;     /* qptr2 points to qptr's predecessor */

        iptr = qptr->item;    /* get the item to return */

        fmem( qptr );         /* remove the last item from the queue */
        if (qptr2 != NULL)
            qptr2->next = NULL;
        else
            *queue = NULL;    /* nothing left in queue */

        return( iptr );       /* return a pointer to the item removed */
    }
}

int inMkQueue(char *being_made)
{
    /* see if "being_made" item is already in the MkQueue */
    QNODE *qptr;

    if (MkQueue != NULL)       /* is the queue empty? */
        for (qptr = MkQueue; qptr != NULL; qptr = qptr->next)
            if (strcmp(((TNODE *)qptr->item)->being_made,being_made) == 0)
                return(1);     /* being_made is in the queue */

    return( 0 );               /* MkQueue is empty or being_made not in it */
}

/*----------------------------------------------- INITIALIZE PIF ----------*/

void init_pif(PIF *pif,int *lenpif,char *title,int rows,int cols)

    /* Initialize the Program Information File to start the new application.
     * By default it is just a non-swappable dos window with 256K.
     */
{
    *lenpif = sizeof(PIF);

    /* set defaults now, and particulars later */
    memset(pif->reserved1,0,2);
    memset(pif->prog_title,' ',30); /* blank filled */
    strncpy(pif->prog_title,title,strlen(title));












    pif->maxmem = MemSize;          /* memory required for app in k-bytes */
    pif->minmem = MemSize;
    strcpy(pif->program,"");        /* command to start program, 0-terminated
*/
    pif->def_drive = CurDir[0];     /* ' ', 'A', 'B', ... */
    strcpy(pif->def_dir,CurDir+2);  /* default directory, 0-terminated */
    strcpy(pif->params,"");         /* parameters, 0-terminated */
    pif->init_screen = 0x7F;        /* screen mode (0-7) (??) */
    pif->text_pages = 1;            /* # of text pages used */
    pif->first_intr = 0;            /* # of first interrupt vector to save */
    pif->last_intr = 255;           /* # of last interrupt */
    pif->logical_rows = rows;       /* logical size of window buffer */
    pif->logical_cols = cols;
    pif->init_row = 0;              /* initial row to display window */
    pif->init_col = 0;
    pif->system_mem = 0;            /* system memory in k-bytes */
    pif->shared_prog[0] = 0;        /* shared program file name, 0-terminated
*/
    pif->shared_data[0] = 0;        /* shared program data, 0-terminated */
    pif->control_byte1 = 0x20;      /* control byte 1, encoded as follows:
                                         80H - writes direct to screen
                                         40H - foreground onlay
                                         20H - uses math coprocessor
                                         10H - accesses system keyboard buffer
                                         01H - swappable */
    pif->control_byte2 = 0x40|0x20; /* control byte 2, encoded as follows:
                                         40H - uses command line parameters
                                         20H - swaps interrupt vectors */
    memset(pif->open_keys,' ',2);   /* keys to use for Open Window menu */
    pif->script_size = 256;         /* size of script buffer in bytes */
    pif->auto_pause = 0;            /* pause after this many tests for input
                                       during one clock tick (normally 0) */
    pif->color_mapping = 0;         /* non-zero to disable color mapping */
    pif->swappable = 0;             /* non-zero if application is swappable */
    memset(pif->reserved2,0,3);     /* should be zero */
    pif->auto_close = 0;            /* non-zero to close on program exit */
    pif->disk_reqd = 0;             /* non-zero if diskette required */
    pif->reserved3 = 1;             /* MUST HAVE VALUE OF 1 */
    pif->shared_mem = 0;            /* non-zero if prog uses shared memory */
    pif->physical_rows = 0;         /* initial size of physical window */
    pif->physical_cols = 0;         /*   0's allow DV to set */
    pif->max_expanded_mem = 65535u; /* max amt of expanded mem avail to app */
    pif->control_byte3 = 0x80|0x10; /* control byte 3, encoded as follows:
                                         80H - automatically assigns position
                                         20H - honor maximum memory value
                                         10H - disallow Close command
                                         08H - foreground-only when in
graphics
                                         04H - don't virtualize */
    pif->key_conflict = 0;          /* keyboard conflict (0-4, usually 0) */
    pif->graphics_pages = 0;        /* # graphics pages used */
    pif->system_mem2 = 0;           /* system memory - overrides system_mem */
    pif->init_mode = 0xFF;          /* initial screen mode, normally 0FFH */
    memset(pif->reserved4,0,22);












}

/*----------------------------------------------- GENERATE TEMP FILE NAME --*/

char *gen_name(int tsknum,int cmdnum)
{
    /*    Generate a new output file name, d:\dir\DVMKxxyy.$$$, where
          d:\dir\ is the directory in which the "dvmake" was started,
          xx = task number, and yy = command number (both in hex).
          Returns a pointer to the newly allocated file name.
     */

    char *new_name;                   /* generated name */
    char tsk_cmd[5];                  /* task/command number string */

    if ( (new_name = (char *) gmem(MAXFNM)) == NULL )
        err("Out of memory");
    else
    {
        strcpy(new_name,CurDir);      /* directory name */

        if (CurDir[strlen(CurDir)-1] != '\\')
            strcat(new_name,"\\");    /* add backslash to dir */

        strcat(new_name,"DVMK");      /* first 4 chars of name */

        sprintf(tsk_cmd,"%02x%02x",tsknum,cmdnum);
        strcat(new_name,tsk_cmd);     /* last 4 chars of name */

        strcat(new_name,".$$$");      /* add an extension */
    }
    return( new_name );
}

/*----------------------------------------------- MENU TASK ---------------*/

int dvmenu( void )
{
    /*    display a menu to control the status of the make, and don't
     *    quit until someone wakes me up with tsk_post(MenuTsk)
     */

    ulong kbd,win;  /* handles for keyboard, window */
    ulong whichobj; /* handle of object that has input */
    char *kbuf;     /* message buffer */
    int  klen,      /* message length */
         state;     /* state of selected item */

/* this string defines the contents of the menu */
static char mkmenu[] = "\
 Access DV  A \
 Quit       Q ";

static char mkmenutbl[] =












{
 ftab(2,FTH_KEYSELECT+FTH_MODIFIED+FTH_AUTORESET,0,0,9,2),
      0,0,0,13,FTE_SELECT,'A',1,0,
      1,0,1,13,FTE_SELECT,'Q',1,0,
};

    win = win_new("DVMAKE",6,2,14);   /* get a new window */
    win_logattr(win,1);               /* and set its logical attributes */
    win_attr(win,1);
    win_disallow(win,ALW_HSIZE);      /* do not allow resizing menu */
    win_disallow(win,ALW_VSIZE);
    win_swrite(win,mkmenu);           /* write the contents and */
    win_stream(win,mkmenutbl);        /* field table to the menu window */
    fld_marker(win,175);              /* set the selected field marker */

    kbd = key_new();                  /* get a keyboard for the menu */
    key_open(kbd,win);
    key_addto(kbd,KBF_FIELD);         /* and put it into field mode */

    /* put and display the menu in the top right corner of the main window */
    win_poswin(win,MainWin,PSW_LEFT,0,PSW_RIGHT,0,0);
    win_unhide(win);
    win_top (win);                    /* make sure it's the one on top */

    /* go until someone wakes me up with tsk_post(MenuTsk) */
    for (whichobj = 0; whichobj != tsk_me(); )
    {
        /* wait for something to show up in our object queue */
        if ((whichobj = obq_read()) == kbd)
        {
            key_read(kbd,&kbuf,&klen);/* see what field was selected */
            state = qry_type(win,(int) *kbuf); /* is it ON or OFF    */

            if ((int) *kbuf == 1 &&   /* was "Access DV" toggled?    */
                Status != ABORT)
            {
                api_beginc();         /* make sure err() hasn't aborted */
                if (Status != ABORT)  /*   in the interim */
                    Status = (state == FLT_SELECT ? ACCESSDV : NORMAL);
                api_endc();
            }
            else                      /* selected "Quit"             */
            {
                Status = ABORT;
                fld_reset(win);       /* show only Quit as selected  */
                fld_type(win,2,FLT_SELECT);
            }
        }
    }

    /* get rid of menu window, keyboard */
    key_free(kbd);
    win_free(win);
}













/*----------------------------------------------- GET TIME ROUTINE --------*/

ulong   gtime( char *file )
{
    /*     Return the time and date for file, or if the file
     *     does not exist, assume it is very old
     *
     *     The DOS time and date are concatanated to form one
     *     large number.
     *     THIS ROUTINE IS NOT PORTABLE (because it assumes a 32
     *     bit ulong to provide for the time functions).
     */

    short        handle;            /* Place to remember file handle */
    struct ftime time;              /* date/time structure           */
    ulong        utime = 0;         /* use to convert time to ulong  */
    char       xtern *searchpath(); /* search PATH for the file,     */
                                    /*   defined in TURBO C's dir.h  */

    if ((handle = open(searchpath(file),O_RDONLY)) == -1)
    {
        /* File doesn't exist. Return a very old date & time */
        return( OLDTIME );
    }
    else
    {
        /* File exists, so get the time */
        if ( getftime(handle,&time) )
            err("DOS returned error from date/time request");

        if ( close(handle) )
            err("DOS returned error from file close request");

        /* pack the time into an unsigned long for comparisons */
        utime |= (ulong) time.ft_year  << 25;
        utime |= (ulong) time.ft_month << 21;
        utime |= (ulong) time.ft_day   << 16;
        utime |= (ulong) time.ft_hour  << 11;
        utime |= (ulong) time.ft_min   << 5;
        utime |= (ulong) time.ft_tsec;

        return( utime );
    }
}

/*----------------------------------------------- CHAR STORAGE ------------*/

char   **stov( char *str, int maxvect )
{
   /*     "str" is a string of words separated from each other by
    *     white space. Stov returns an argv-like array of pointers
    *     to character pointers, one to each word in the original
    *     string. The white space in the original string is replaced












    *     with nulls. The array of pointers is null-terminated.
    *     "Maxvect" is the number of vectors in the returned
    *     array. The program is aborted if it can't get memory.
    */

    char   **vect, **vp;

    vp = vect = (char **)  gmem( (maxvect + 1) * sizeof(str) );
    while ( *str && --maxvect >= 0 )
    {
        skipwhite(str);
        *vp++ = str;
        skipnonwhite(str);
        if ( *str )
            *str++ = 0;
    }

    *vp = 0;
    return( vect );
}

/*----------------------------------------------------------------------*/

char *getline( int maxline, FILE *fp )
{
    /*     Get a line from the stream pointed to by fp.
     *     "Maxline" is the maximum input line size (including the
     *     terminating null. A \ at the end of line is
     *     recognized as a line continuation, (the lines
     *     are concatanated). Buffer space is gotten from gmem().
     *     If a line is longer than maxline it is truncated (i.e.
     *     all characters from the maxlineth until a \n or EOF is
     *     encountered are discarded.
     *
     *     Returns: NULL on a malloc failure or end of file.
     *              A pointer to the malloced buffer on success.
     */

    static  char    *buf;
    char    *bp;
    int     c, lastc;

    /* Two buffers are used. Here, we are getting a worst-case buffer
     * that will hold the longest possible line. Later on we'll copy
     * the string into a buffer that's the correct size.
     */

    if ( (bp = buf = (char *) gmem(maxline)) == NULL )
        return( NULL );

    while(1)
    {
         /* Get the line from fp. Terminate after maxline
          * characters and ignore \n following a \.












          */

        Inputline++;                  /* Update input line number */

        for ( lastc=0; (c = fgetc(fp)) != EOF && c!='\n'; lastc=c)
            if ( --maxline > 0 )
                *bp++ = c;

        if ( !( c == '\n' && lastc == '\\') )
            break;

        else if ( maxline > 0 )    /* erase the \ */
            --bp;
    }
    *bp = 0;

    if ( (c == EOF && bp == buf) ||
         (bp = (char *) gmem((int) (bp-buf)+1)) == NULL )
    {
         /*     If EOF was the first character on the line or
          *     malloc fails when we try to get a buffer, quit/
          */

        fmem(buf);
        return( NULL );
    }

    strcpy( bp, buf ); /* Copy the worst-case buffer to the one  */
                       /* that is the correct size and ...       */
    fmem( buf );       /* free the original, worst-case buffer,  */
    return( bp  );     /* returning a pointer to the copy.       */
}

/*----------------------------------------------------------------------*/

char **getblock( FILE *fp )
{
    /* Get a block from standard input. A block is a sequence of
     * lines terminated by a blank line. The block is returned as
     * an array of pointers to strings. At most MAXBLOCK lines can
     * be in a block. Leading white space is stripped.
     */

    char *p, *lines[MAXBLOCK], **blockv = lines;
    int  blockc = 0;

    do {
        if ( (p = getline(MAXLINE,fp)) == NULL)
            break;

        skipwhite(p);

        if ( ++blockc <= MAXBLOCK )
            *blockv++ = p;












        else
            err("action too long (max = %d lines)",MAXBLOCK);

    } while ( *p );

    /*     Copy the blockv array into a safe place. Since the array
     *     returned by getblock is NULL terminated, we need to
     *     increment blockc first.
     */

    blockv = (char **) gmem( (blockc + 1) * sizeof(blockv[0]) );
    movmem( lines, blockv, blockc * sizeof(blockv[0]) );
    blockv[blockc] = NULL;

    return( blockv );
}

/*----------------------------------------------------------------------*/

TNODE *makenode( void )
{
    /* Create a TNODE, filling it from the mkfile, and return a
     * pointer to it.  Return NULL if there are no more objects
     * in the makefile.
     */

    char     *line, *lp;
    TNODE    *nodep;

    /*     First, skip past any blank lines or comment lines.
     *     Return NULL if we reach end of file.
     */

    do {
        if ( (line = getline(MAXLINE,Makefile)) == NULL )
            return( NULL );

    } while ( *line == 0 || *line == COMMENT );

    /*     At this point we've gotten what should be the dependency
     *     line. Position lp to point at the colon.
     */

    for ( lp = line; *lp && *lp != ':'; lp++ )
        ;

    /*     If we find the colon position, lp to point at the first
     *     non-white character following the colon.
     */

    if ( *lp != ':' )
        err( "missing ':'");          /* This will abort the program */
    else
        for ( *lp++ = 0; iswhite(*lp); lp++ )












            ;

    /*  Allocate and initialize the TNODE */

    nodep             = (TNODE *) gmem( sizeof(TNODE) );
    nodep->lnode      = NULL;
    nodep->rnode      = NULL;
    nodep->being_made = line;
    nodep->time       = gtime( line );
    nodep->depends_on = stov( lp, MAXDEP );
    nodep->do_this    = getblock( Makefile );
    nodep->made       = 1; /* assume has already been made, but later change
*/
    nodep->apphan     = 0;
    nodep->tsknum     = 0;

    return( nodep );
}

/*----------------------------------------------- TREE ROUTINES -----------*/

TNODE *find( char *key, TNODE *root )
{
    /* If key is in the tree pointed to by root, return a pointer
     * to it, else return 0.
     */

    int   notequal;

    if ( !root )
         return( 0 );

    if ( (notequal = strcmp(root->being_made,key)) == 0 )
         return( root );

    return( find( key, (notequal > 0) ? root->lnode : root->rnode) );
}

/*----------------------------------------------------------------------*/

int tree( TNODE *node, TNODE **rootp )
{
    /* If node's key is in the tree pointed to by rootp, return 0
     * else put it into the tree and return 1.
     */

    int   notequal;

    if ( *rootp == NULL )
    {
        *rootp = node;
        return( 1 );
    }













    if ( (notequal = strcmp( (*rootp)->being_made, node->being_made)) == 0 )
        return( 0 );

    return( tree( node, notequal > 0 ? &(*rootp)->lnode : &(*rootp)->rnode)
);
}

/*----------------------------------------------------------------------*/

int dependencies( void )
{
    /* Manufacture the binary tree of objects to make. First
     * is a pointer to the first target file listed in the
     * makefile (ie. the one to make if one isn't explicitly
     * given on the command line. Root is the tree's root pointer.
     */

    TNODE   *node;

    if ( (node = makenode()) != NULL )
    {
        /* has First been assigned a value yet? */
        if (First == NULL)
            First = node->being_made;

        if ( !tree(node, &Root) )
            err("Can't insert first node into tree !!!\n");

        while ( (node = makenode()) != NULL )
            if ( !tree( node, &Root ) )
                fmem( node );
        return( 1 );
    }

    return( 0 );
}

/*----------------------------------------------- CREATE MAKE QUEUE -------*/

void make_queue( char *what )
{
    /*     Simulate a sequential make, building up a queue of items to make.
     *     The dependency tree is descended recursively.
     */

    TNODE       *snode;               /* Source file node pointer     */
    TNODE       *dnode;               /* dependent file node pointer  */
    int         doaction = 0;         /* If true do the action        */
    static char *zero    = (char *)0;
    char        **linev  = &zero;

    if ( (snode = find(what, Root)) == NULL )
         err("Don't know how to make <%s>\n", what );













    if ( !*(linev = snode->depends_on)) /* If no dependencies      */
         ++doaction;                    /* always do the action     */

    for ( ; *linev; linev++ )           /* Process each dependency  */
    {
        make_queue( *linev );

        if ( (dnode = find(*linev, Root)) == NULL )
            err("Don't know how to make <%s>\n", *linev );

        if ( snode->time <= dnode->time )
        {
            /* If dependent node is more recent (time is greater)
             * than the source node, do something. If the times
             * are equal, assume that neither file exists but that
             * the action will create them, and do the action.
             */
            ++doaction;
        }
    }

    if ( doaction )    /* are we going to do anything */
    {
        /* are there any commands, and is this node not in MkQueue */
        if ( snode->do_this && *snode->do_this && **snode->do_this &&
             !inMkQueue(snode->being_made) )
        {
            snode->time = NEWTIME;    /* Assume the time will change     */
            snode->made = 0;          /* This item has not been made yet */
            enqueue(&MkQueue,snode);  /* Add to list of things to make   */
        }
    }
}

/*----------------------------------------------- DISPATCH TASK -----------*/

int dispatch( void )
{
    /*    Grab a job from the TskQueue, send all of the commands to that
     *    application through its keyboard, close the app, and return.
     */

    TNODE       *snode;               /* Source file node pointer */
    char        **linev;              /* Command to execute */
    char        *outfnm;              /* file to redirect output to */
    int         cmdcnt;               /* how many commands were redirected */
    ulong       keyhan;               /* handle for keyboard */
    static int  taskcnt = 0;          /* give each task a unique identifier */
    void        send_keys(ulong keyhan, char *keys);

    mal_lock(TskQueueLock);           /* grab the task queue */
    snode = (TNODE *) dequeue(&TskQueue); /* get a task from the queue */
    mal_unlock(TskQueueLock);         /* let go of the task queue */













    keyhan = key_of(snode->apphan);   /* get keyboard handle */

    api_beginc();
    snode->tsknum = taskcnt++;        /* assign a new task number */
    api_endc();

    /* get window to "escape out of" any commands that creeped in at startup
*/
    send_keys(keyhan,"\x1B");    /* "\x1B" is the Escape key character */

    for (linev = snode->do_this, cmdcnt = 0;
         *linev && **linev && Status != ABORT; linev++)
    {
        send_keys(keyhan,*linev);     /* send the command */

        /* if the command doesn't already redirect output, do so */
        if (strchr(*linev,'>') == NULL && strlen(*linev)+ReDirLen < MAXLINE)
        {
            /* get a new output file */
            if ((outfnm = gen_name((int) snode->tsknum,cmdcnt++)) != NULL)
            {
                send_keys(keyhan," > ");  /* send a redirect command */
                send_keys(keyhan,outfnm);
                fmem(outfnm);             /* free up the temp file name */
            }
            else
                --cmdcnt;             /* we couldn't redirect anything */
        }

        send_keys(keyhan,"\r");       /* send return key to run the command */
    }

    send_keys(keyhan,"exit\r");       /* make window exit itself when done  */
    waitwhile(api_isobj(snode->apphan));  /* and wait for task to go away   */

    snode->made = 1;                  /* show that this item was made */

    if (cmdcnt > 0)                   /* were any commands redirected? */
    {
        mal_lock(OutQueueLock);       /* get access to output queue */
        enqueue(&OutQueue,snode);     /* add to output list */
        mal_unlock(OutQueueLock);     /* free access to queue */
    }

    api_beginc();                     /* this task is done, so */
    --RunCnt;                         /*   update # of running tasks */
    api_endc();
}

void send_keys(ulong keyhan, char *keys)
{
    /* send string to keyboard, pausing every 5 characters to avoid
     * overflowing any buffers.  You could try different values, but
     * 5 seems to be a reasonable compromise between speed and making












     * sure no keys are lost.
     */

    int keyctr;

    if (keys != NULL)
        while (*keys)
        {
            waitwhile(key_sizeof(keyhan));    /* wait for keyboard space */
            for (keyctr = 0; *keys && keyctr < 5; keyctr++)
                key_write(keyhan,keys++,1,0); /* send a single keystroke */
        }
}

/*----------------------------------------------- MAKE TASK ---------------*/

int make( void )
{
    /*     Actually execute the commands.  Items are removed from the
     *     MkQueue queue, and placed back on the queue if its dependents
     *     haven't been made yet.  This task runs in parallel with others.
     */

    TNODE *snode;        /* Source file node pointer */
    char  **linev;       /* Command to execute */
    char  doaction;      /* Should we do anything? */
    char  *stack;        /* stack for dispatch() tasks */

    /* while there are still items to make */
    while ( (snode = (TNODE *) dequeue(&MkQueue)) != NULL && Status != ABORT )
    {
        /* make sure all dependents have been made before acting */
        for (linev = snode->depends_on, doaction = 1; *linev; linev++ )
            if (!(find(*linev,Root)->made))
                doaction = 0;

        if (!doaction)
        {
            enqueue(&MkQueue,snode);   /* put the item back on the queue */
            api_pause();               /* and give up our time slice */
        }
        else
        {
            /* put the item on the task queue, and start up its dispatch */
            mal_lock(TskQueueLock);    /* grab the task queue */
            enqueue(&TskQueue,snode);  /* put a task on the queue */
            mal_unlock(TskQueueLock);  /* let go of the task queue */

            /* get stack space for the command dispatcher */
            if ( (stack = (char *) gmem(STKSIZE)) == NULL )
                err("Out of memory");

            /* keep trying to start a new application
             * unless user aborts or wants to access DESQview menu












             */
            while ( Status != NORMAL ||
                   (snode->apphan = app_start((char *) &Pif,Lpif)) == 0 )
            {
                if (Status == ABORT)   /* get out now! */
                    break;
                else if (RunCnt == 0 && /* can't we even get 1 running? */
                         Status == NORMAL && snode->apphan == 0)
                    err("Cannot start a single process");
                else
                    api_pause();       /* just give up our time slice */
            }

            if (snode->apphan != 0)
            {
                /* either hide or put the application in the background */
                if (ShowWin)
                    app_goback(snode->apphan);
                else
                    app_hide(snode->apphan);

                /* start up another command dispatcher, with no window */
                tsk_new(dispatch,stack,STKSIZE,"",0,0,0);

                api_beginc();
                ++RunCnt;              /* we've started another task */
                api_endc();
            }
        }
    }

    waitwhile(RunCnt > 0);            /* wait for all tasks to finish */
}

/*----------------------------------------------- OUTPUT TASK -------------*/

void output( void )
{
    /*     Send files (created by redirecting output to DVMKxxyy.$$$) to
     *     standard output in same order they were created, keeping all
     *     output for a given dependency together.
     */
    TNODE *onode;      /* Output node pointer          */
    FILE  *infile;     /* file to read input from      */
    char  *infnm;      /* file name of input           */
    char  **linev;     /* pointer to commands          */
    int   ch,counter;

    /* loop until everything is finished */
    while ((api_isobj(MakeTsk) || OutQueue != NULL) && Status != ABORT)
    {
        mal_lock(OutQueueLock);       /* get access to output queue */

        if ( (onode = (TNODE *) dequeue(&OutQueue)) == NULL )












        {
            mal_unlock(OutQueueLock); /* free access to queue */
            api_pause();              /* and give up our time slice */
        }                             /* because there's nothing to output */
        else
        {
            mal_unlock(OutQueueLock); /* free access to queue */

            for ( linev = onode->do_this, counter = 0;
                 *linev && **linev; linev++ )
            {
                printf("\n%s\n",*linev); /* print the command executed */

                /* make sure dvmake was able to redirect output */
                if (strchr(*linev,'>') == NULL &&
                    strlen(*linev)+ReDirLen < MAXLINE)
                {
                    /* get the file name and open the file */
                    infnm = gen_name((int) onode->tsknum,counter++);

                    /* lock access to memory (fopen() gets memory) */
                    mal_lock(AllocLock);

                    if (infnm == NULL || (infile = fopen(infnm,"r")) == NULL)
                    {
                        mal_unlock(AllocLock); /* free access to memory */

                        /* not a drastic error, but tell user */
                        printf("Can't open %s\n",infnm);
                    }
                    else /* we successfully opened the temporary file */
                    {
                        mal_unlock(AllocLock); /* free access to memory */

                        /* copy the file to stdout */
                        while ((ch = fgetc(infile)) != EOF)
                            putchar(ch);

                        /* get access to memory (fclose() releases memory) */
                        mal_lock(AllocLock);

                        fclose(infile); /* close, and */
                        remove(infnm);  /* erase the temporary file */

                        mal_unlock(AllocLock);  /* free access to memory */
                    }

                    fmem(infnm);
                }
            }
        }
    }

    /* if user aborts, get rid of remaining temporary files */












    if (Status == ABORT)
    {
        waitwhile(api_isobj(MakeTsk));/* wait for make() to stop first */

        mal_lock(OutQueueLock);       /* gain access to queue */

        while ( (onode = (TNODE *) dequeue(&OutQueue)) != NULL )
           for ( linev = onode->do_this, counter = 0;
                 *linev && **linev; linev++ )
              if (strchr(*linev,'>') == NULL &&   /* could we redirect? */
                  strlen(*linev)+ReDirLen < MAXLINE)
                 if ((infnm = gen_name((int) onode->tsknum,counter++)) !=
NULL)
                 {
                    remove(infnm);    /* erase the temporary file */
                    fmem(infnm);      /* and free up the memory   */
                 }

        mal_unlock(OutQueueLock);     /* free access to queue */
    }
}

/*----------------------------------------------- INITIALIZE ROUTINES -----*/

int controlbrk( void )   /* handles control-break interrupts */
{
    return( 1 );   /* return non-zero to continue running */
}

/*-------------------------------------------------------------------------*/

void getoptions(int argc,char *argv[])
{
    int i;
    char *getcwd();   /* defined in dir.h */
    void ctrlbrk();   /* defined in dos.h */

    TskQueueLock = mal_new();         /* semaphore for TskQueue     */
    OutQueueLock = mal_new();         /* semaphore for OutQueue     */
    AllocLock    = mal_new();         /* semaphore for malloc/free  */

    /* get the current directory, used to save output files */
    if (getcwd(CurDir,MAXFNM) == NULL)
        err("Cannot get current directory");

    ReDirLen = strlen(CurDir)+16;     /* length of redirection file name */

    /* initialize the control-break handler to call controlbrk()  */
    ctrlbrk(controlbrk);

    for (i = 1; i < argc; i++)        /* get the command line switches */
    {
        /* windows switch */
        if (strcmp(argv[i],"-w") == 0 || strcmp(argv[i],"-W") == 0)












            ShowWin = 1;

        /* memory size switch */
        else if (strcmp(argv[i],"-k") == 0 || strcmp(argv[i],"-K") == 0)
        {
            if (i < argc - 1)
            {
                if ((MemSize = atoi(argv[++i])) <= 0)
                    err("Invalid memory size parameter for -k switch");
            }
            else
                err("Missing memory size parameter for -k switch");
        }

        /* help switch */
        else if (strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"-H") == 0)
        {
            printf("dvmake [-w] [-k nnn] [-h] [target]\n");
            printf("  -w switch displays windows \n");
            printf("  -k switch sets task memory to nnn K-bytes \n");
            printf("  -h switch displays help message \n");
        }

        /* anything else with - or / must be mistake */
        else if (argv[i][0] == '-' || argv[i][0] == '/')
            err("Invalid command switch: %s",argv[i]);

        /* anything else must be the first item to make */
        else
        {
            if ( (First = (char *) gmem(strlen(argv[i])+1)) == NULL)
                err("Out of memory");
            strcpy(First,argv[i]);
        }
    }
}

/*----------------------------------------------- START PARALLEL TASKS ----*/

void startup( void )
{
    char *mkstack,        /* stacks for various tasks */
         *menustack;

    MainWin = win_me();   /* get handle of main window  */

    /* initialize a PIF buffer with appropriate window size */
    if (ShowWin)
        init_pif(&Pif,&Lpif," DVMAKE TASK ",25,80);
    else
        init_pif(&Pif,&Lpif," DVMAKE TASK ",1,1);

    /* get stack space for the parallel tasks */
    if ( (mkstack   = (char *) gmem(STKSIZE)) == NULL ||












         (menustack = (char *) gmem(STKSIZE)) == NULL )
        err("Out of memory");

    /* we are now in Parallel mode */
    Parallel = 1;

    /* start a task to track a menu, with no title or window */
    MenuTsk = tsk_new(dvmenu,menustack,STKSIZE,"",0,0,0);

    /* start a task to make the items, with no title or window */
    MakeTsk = tsk_new(make,mkstack,STKSIZE,"",0,0,0);
}

/*----------------------------------------------- STOP PARALLEL TASKS -----*/

void finishup( void )
{
    /* get dvmenu() to stop by posting its Object Queue */
    tsk_post(MenuTsk);

    /* wait for everything to really finish */
    waitwhile(api_isobj(MakeTsk) || api_isobj(MenuTsk));
}

/*----------------------------------------------- MAIN PROGRAM ------------*/

main( int argc, char *argv[] )
{
    /* if DESQview is not running or version is too low, display a message */
    if (api_init() < DV_VER)
    {
        printf ("dvmake requires DESQview version %d.%02d or later\n",
                           DV_VER/256,DV_VER%256);
        return( 1 );
    }
    else
    {
        api_level(DV_VER);            /* tell DV what extensions to enable */

        getoptions( argc, argv );     /* get command line arguments */

        if ( (Makefile = fopen(MAKEFILE, "r")) == NULL )
             err("can't open %s\n", MAKEFILE );

        if ( !dependencies() )        /* is there anything in the mkfile */
        {
             fclose(Makefile);
             err("Nothing to make");
        }
        else
        {
             fclose(Makefile);
             make_queue( First );     /* simulate the sequential make */













             if (MkQueue != NULL)     /* does anything need to be made? */
             {
                 startup();           /* start parallel tasks */
                 output();            /* display output from tasks */
                 finishup();          /* stop parallel tasks */
             }

             mal_free(TskQueueLock);  /* get rid of our semaphores */
             mal_free(OutQueueLock);
             mal_free(AllocLock);

             if (Status == ABORT)     /* print the error message */
                 fprintf(stderr,"%s",Error);

             api_exit();              /* tell DESQview we're done */
             return( Status == ABORT ? 1 : 0 );
        }
    }
}












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.