3514 lines
114 KiB
C++
Executable File
3514 lines
114 KiB
C++
Executable File
#define ROOT_DIR "..\\"
|
|
//
|
|
// BUILD 4.1A
|
|
//
|
|
// Copyright (c) 1994, 1995 Greenleaf Software, Inc. All Rights Reserved
|
|
//
|
|
// June 8, 1995 4.0C
|
|
//
|
|
// Modified the get first/get next stuff to support native mode OS/2
|
|
// compilation of BUILD.CPP
|
|
//
|
|
// January 31, 1995 4.0B
|
|
//
|
|
// One last change. This version of BUILD supports batch mode compilation.
|
|
// If your compiler can handle a CMD file with a list of a zillion files,
|
|
// we can supply it. This often speeds up the build process, because
|
|
// the compiler is only loaded once, instead of once per file.
|
|
//
|
|
// Batch mode operation has a taint of kludgeness about it. There
|
|
// are three hardcoded compilation passes: C, CPP, and ASM. During the
|
|
// stage where we build config files for the compilation, BUILD pays
|
|
// special attention to any config files that have the string "*.c",
|
|
// "*.cpp", or "*.asm" in their contents. If it sees those strings,
|
|
// it does two things. First, it expands the line with "*.xxx" on it,
|
|
// adding the line to the config file once per file. Second, it switches
|
|
// the appropriate compiler command from ONE_AT_A_TIME to BATCH.
|
|
//
|
|
// When it comes time to compile, any command that was switched to batch
|
|
// mode will be executed once and only once, with no file names added
|
|
// to the processing line. A typical command might be: BCC @CLIB.CMD.
|
|
//
|
|
// The batch flag is in the item class, which means even non-command keys
|
|
// such as DeleteFiles and Description are set up to run one at a time
|
|
// or batch mode. This indicates a lack of rigor in the class design,
|
|
// mea culpa.
|
|
//
|
|
// January 26, 1995 4.0
|
|
//
|
|
// Yet another major personality change for BUILD.EXE. I targeted
|
|
// this release with simplifying the program. In release 3.0, I
|
|
// modified BUILD to create EXEs and LIBraries, but it added quite
|
|
// a bit of complexity to the program. This release still has both
|
|
// of those capabilities, but the implementation has changed quite
|
|
// a bit.
|
|
//
|
|
// BUILD now operates like two completely different programs that
|
|
// share a common infrastructore. If you are building libraries,
|
|
// as indicated by the presence of "-lib" on the command line,
|
|
// BUILD first compiles all of your source code, then builds
|
|
// your library and/or DLL. Compilation is done by running
|
|
// the command lines specified by the CPP, C, and ASM key values.
|
|
// Libraries are built by the LIB key values. This means we no longer
|
|
// have separate key values for the linker and the librarion, the are
|
|
// all combined into LIB. This release of BUILD allows you to specify
|
|
// multiple commands for a specific tag, so when building a DLL you
|
|
// might have LIB broken into three or four steps.
|
|
//
|
|
// If you are building an executable, presumably a demo program, build
|
|
// takes a completely different approach. It doesn't do any precompilation
|
|
// on the file list. Instead, it executes all the EXE tag values for
|
|
// each file. DOS programs may only use 1 EXE line, but building a Windows
|
|
// application might take three or four lines.
|
|
//
|
|
// The biggest change to this program is in the area of configuration
|
|
// files. BUILD used to automatically build config files, using names
|
|
// like CPP.CMD to hold the options from the CppOptions key value. Now,
|
|
// BUILD requires you to specify exactly which CMD files you want to BUILD.
|
|
// When you are building libraries, it will create all CMD files that are
|
|
// defined using a key value that looks like this: *LIB.CMD. When building
|
|
// EXE files, it will create all CMD files that are defined using a key
|
|
// value that looks like this: *EXE.CMD.
|
|
//
|
|
// Config files for libraries are built once, before any compilation takes
|
|
// place. This means that you can't include the name of a specific file
|
|
// that you are compiling in the config file, it has to be listed on the
|
|
// command line. Config files can have multiple lines, just by creating
|
|
// multiple lines with the same key in the INI file.
|
|
//
|
|
// The most exciting thing about config files built for libraries is the
|
|
// ability to put all your file names into one config file dynamically.
|
|
// As BUILD is scanning the input lines for the config file, it looks for
|
|
// %file*% strings inside the file. If it finds it, it creates a line
|
|
// for every single file in the input list. This is how we create LIB.CMD,
|
|
// which is used to feed file names to the librarian.
|
|
//
|
|
// When building CONFIG files for example programs, this file name
|
|
// expansion isn't done. Instead, a config file is build for each
|
|
// demo program being built, and file name substitutions are performed
|
|
// just as they would be for any other arguments.
|
|
//
|
|
// The final item in the new BUILD is the argument substitution syntax.
|
|
// There are a bunch of arguments in INI files that get substituted
|
|
// when executing command lines or when building config files. In
|
|
// BUILD 3.0 these were a bunch of single letter codes escaped with
|
|
// a '%' symbol. In build 4.0, I have modified these to be actual
|
|
// strings enclosed in a pair of '%' symbols. The goal is to make the
|
|
// INI file more readable. Don't know if I succeeded or not. For the
|
|
// record, the current crop of arguments that will be substituted in
|
|
// command lines are:
|
|
//
|
|
// %file% : The complete name of the source file. For example,
|
|
// when building libraries, this string will be turned
|
|
// into a name something like: ..\C_W16\TW_W16.C
|
|
//
|
|
// %file.path% : The drive and path of the file, with a guaranteed
|
|
// '\' character. In the above example, this would
|
|
// yield: "..\C_W16\". If the file is in the current
|
|
// directory, you get ".\".
|
|
//
|
|
// %file.name% : The name component of the source file, minus an
|
|
// extension. If my source file is "EXAMP00.C", this
|
|
// argument will be converted to "EXAMP00". We use
|
|
// this argument a lot for commands passed to the
|
|
// librarian or linker.
|
|
//
|
|
// %model% : This string yields the model name, as defined in the
|
|
// "[]" string in the INI file. For example, a small
|
|
// model build will return the "S" string. The commlib
|
|
// 6.0 DLL will probably return "600". This may or
|
|
// may not have anything to do with the actual memory
|
|
// model in use by the compiler. By convention, normal
|
|
// models will often use the same first initial that
|
|
// is used to pass the memory model to the compiler.
|
|
//
|
|
// %model.lower% : Borland usually takes the model specification for
|
|
// the compiler with a lower case letter, generally
|
|
// chosen from s, m, c, l, h. This expression will
|
|
// yield those letters by taken the first letter from
|
|
// the model name, and converting it to lower case.
|
|
//
|
|
// %model.upper% : Same deal, but converts to upper case instead.
|
|
// Microsoft usually wants model letters in upper
|
|
// case. No accounting for taste.
|
|
//
|
|
// %model.asm% : This creates a string suitable for passing to
|
|
// MASM or TASM with the /DGF_MODEL=xxx option.
|
|
// Note that it figures this out based on the
|
|
// first character of the model name. Values
|
|
// other than s, m, c, l, and h give a bad result,
|
|
// because we can't figure out what the memory
|
|
// model should be.
|
|
//
|
|
// %nl% : Inserts a new line into the output stream,
|
|
// literally a '\n';
|
|
//
|
|
// %% : Inserts the literal '%' character.
|
|
//
|
|
// %comspec% : Inserts the name of the current command processor.
|
|
// We figure this out by looking at the COMSPEC
|
|
// environment variable. If this variable isn't
|
|
// defined, "COMMAND.COM" is used. On my system,
|
|
// the substitution yields "C:\4DOS40\4DOS.COM".
|
|
//
|
|
// %continue% : This means the character immediately following
|
|
// the %continue% is a continuation character. If
|
|
// there are more files coming after this one, we
|
|
// output the continuation character. Otherwise,
|
|
// it gets eaten.
|
|
//
|
|
// To add new tags to the INI file substitutions, just edit the process()
|
|
// function. It's easy.
|
|
//
|
|
// June 29, 1994 2.1
|
|
//
|
|
// Lots of changes to BUILD.CPP. First, I added the template
|
|
// capability. You can now define a library template, which is
|
|
// shared among multiple libraries. For example, to build DOS
|
|
// libraries, I have a single DOS template which is shared among
|
|
// S, SD, C, ...etc. The include the template by using the
|
|
// the new identifier Template=xxx. Template definitions start
|
|
// usually have a "." right before the name, which prevents them
|
|
// from being displayed when sections are being dumped.
|
|
//
|
|
// The second major change in this release of BUILD is the addition
|
|
// of format specifiers to the keywords. It used to be that you
|
|
// could put %s in a command line, and BUILD would substitute the
|
|
// filename. It still does that, but now you can put %m for the model
|
|
// letter in lower case, %M for upper case, %p for the file path and
|
|
// drive, %n for the name, and %l for the library name.
|
|
//
|
|
// The third major change is the addition of the BUILDDEMO capability.
|
|
// Each library now has a Demo identifier, which allows BUILD
|
|
// to create demo programs using that library.
|
|
//
|
|
// A minor feature change is that most commands can now appear multiple
|
|
// times in a section. Each multiple appearance of a command is
|
|
// tacked on to the previous ones. When it comes time to execute the
|
|
// commands, they are done one at a time. This is real useful for
|
|
// building demos that require a compile/rc/link sequence. See the INI
|
|
// file for details on how this works.
|
|
//
|
|
// The Linker line now accepts multiple commands, which need
|
|
// to be separated by the caret character: ^. Because of this,
|
|
// all BUILD.INI files will need to be modified to perform
|
|
// an explicit IMPLIB.
|
|
//
|
|
// May 18, 1994 2.0
|
|
//
|
|
// This is a completely new version of the Greenleaf Build program.
|
|
// It has the same syntax as the previous version, and works mostly
|
|
// the same way. The major changes are:
|
|
//
|
|
// o This version of BUILD will create DLLs and Import Libraries
|
|
//
|
|
// o Instead of hardcoding the information in BUILD.C, this version
|
|
// of BUILD reads all of its information out of BUILD.INI.
|
|
//
|
|
// One of the major constraints on BUILD 1.0 was that it was required
|
|
// to have as small a memory footprint as possible. That is why so
|
|
// much of the program was hardcoded instead of flexible. Now, all of
|
|
// our compilers use DOS Extenders, so we don't care any more how
|
|
// much memory we take up. This version of BUILD is the result.
|
|
//
|
|
// Much of the functionality in this version of BUILD is found into
|
|
// the bstring [Build String] class. Some day we may be able to use a
|
|
// standard ANSI string class, but that does not exist yet.
|
|
|
|
//
|
|
// The rules: All of the information needed by the linker is stored
|
|
// in BUILD.INI. The data in BUILD.INI is used to generate
|
|
// CPPLIB.CMD and LINKLIB.CMD (and others). LIB.CMD is generated
|
|
// using a list of file names.
|
|
//
|
|
// "BUILD ?" will create the CMD files, and not delete them
|
|
// afterwards. You have to delete them yourself.
|
|
//
|
|
// "BUILD -keep" will leave the objects around even if the library
|
|
// builds properly.
|
|
//
|
|
// "BUILD -lib" builds libraries
|
|
//
|
|
// "BUILD -exe" builds demos and examples
|
|
//
|
|
// "BUILD -cmd " performs a dry run, showing you the commands without
|
|
// actually executing any of them.
|
|
//
|
|
#include <iostream.h>
|
|
#include <iomanip.h>
|
|
#include <fstream.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <dos.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <process.h>
|
|
#include <errno.h>
|
|
#include <conio.h>
|
|
#if defined( __WATCOMC__ )
|
|
#include <io.h> // Had to get unlink from here...
|
|
#endif
|
|
#include <ctype.h>
|
|
#include "build.h"
|
|
//#include "c:/tools/mc30/memcheck.h"
|
|
//
|
|
// int main( int argc, char *argv[] )
|
|
//
|
|
// ARGUMENTS
|
|
//
|
|
// argc : The number of arguments being passed on the command line.
|
|
//
|
|
// argv : The strings containing the arguments.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if all files were built, and everything else ran without error.
|
|
// 1 if anything bad happened along the way.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The main routine just dispatches the functions that do all the work
|
|
// for the build. It checks the return codes from all the functions
|
|
// to see if an error has occurredd. An error at any point will
|
|
// stop the process.
|
|
//
|
|
// Note that this build program really operates in two completely
|
|
// different modes. If it is building LIBs, it takes one path. If
|
|
// it is building EXEs, it takes a completely different approach.
|
|
//
|
|
// The process goes like this:
|
|
//
|
|
// Read in build parameters
|
|
// Display the arguments on the screen.
|
|
//
|
|
// If building EXE files:
|
|
// Expand wild cards to make the list of files to process
|
|
// Build EXE files using all the EXE lines in the INI file
|
|
//
|
|
// If building LIB files:
|
|
// Expand wild cards to make the list of files to process
|
|
// Build all the config files to be used by the compiler/linker/etc.
|
|
// Compile all eligible C, CPP, and ASM files in batch mode.
|
|
// Compile all eligible C, CPP, and ASM files in one-at-a-time mode.
|
|
// Build the library using all the LIB lines in the INI file.
|
|
//
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 18, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1955 3.0A : New release with more capabilities
|
|
//
|
|
// January 26, 1995 4.0A : Major changes to simplify the code, and add
|
|
// to its capabilities.
|
|
//
|
|
|
|
int main( int argc, char *argv[] )
|
|
{
|
|
//
|
|
// These to auto variables are important. files will contain a list of all
|
|
// the files to be processed during the build. The algorithm that chooses
|
|
// which files go in the list is different depending on whether you are
|
|
// building EXEs or LIBs.
|
|
//
|
|
// opts holds all the option lines from the INI file, as well as some of the
|
|
// info regarding command line parameters. The list of predefined options
|
|
// that are passed to the constructor are the *only* options that can be
|
|
// read into an INI file, with two exceptions. The exceptions are the
|
|
// *LIB.CMD and *EXE.CMD lines. You can add as many of those as you care
|
|
// too, as long as the names conform to one of these two schemes.
|
|
//
|
|
file_list files;
|
|
options opts( "Description",
|
|
"Directories",
|
|
"Template",
|
|
"Cpp",
|
|
"C",
|
|
"Asm",
|
|
"Lib",
|
|
"Exe",
|
|
"DeleteFiles",
|
|
"Opts",
|
|
"" );
|
|
|
|
// Symantec defaults to buffered output on cout and cerr. Yuk.
|
|
|
|
cout.rdbuf()->setbuf( (char*) 0, 0 );
|
|
cerr.rdbuf()->setbuf( (char*) 0, 0 );
|
|
//
|
|
// These two functions are used to parse the command line, then read in the
|
|
// INI file. Everything of importance that comes out of this process is
|
|
// stored in the opts object.
|
|
//
|
|
if ( parse_args( argc, argv, opts ) == 0 )
|
|
return 1;
|
|
if ( read_ini_file( opts ) == 0 )
|
|
return 1;
|
|
dump_options( opts );
|
|
//
|
|
// If I am being asked to build demo programs, I first expand
|
|
// the list of files on the command line. This is done using
|
|
// simple wild card expansion, I don't do any funny directory
|
|
// path manipulation. This means that if you want to compile
|
|
// files from the examples directory, you have to type:
|
|
//
|
|
// BUILD -exe L ..\ex_dos\ex*.c
|
|
//
|
|
if ( opts.target == options::EXE ) {
|
|
if ( build_exe_file_list( argc, argv, files ) == 0 )
|
|
return 1;
|
|
if ( build_exe_files( opts, files ) == 0 )
|
|
return 1;
|
|
}
|
|
//
|
|
// Things are completely different when building libraries. Building
|
|
// the file list is done by looking in the targeted directories for
|
|
// each file type. For example, if you want to build -lib s *.C, we
|
|
// might look in ..\C_DOS, ..\C_GSCI, ..\C_**, and ..\C_ALL for all
|
|
// of the *.C files.
|
|
//
|
|
// Building libraries also differs in that the config files are only created
|
|
// once, so we do it here. In addition, we have two steps to build the
|
|
// library. The first step involves compiling or assembling every one of
|
|
// the files in the list. The second step involves turning those OBJs
|
|
// into a library, using all of the command lines specified in the
|
|
// various LIB= lines in the INI file.
|
|
//
|
|
if ( opts.target == options::LIB ) {
|
|
if ( build_lib_file_list( argc, argv, files, opts ) == 0 )
|
|
return 1;
|
|
build_lib_config_files( opts, files );
|
|
if ( lib_batch_compiles( opts ) == 0 )
|
|
return 1;
|
|
if ( compile_lib_files( opts, files ) == 0 )
|
|
return 1;
|
|
if ( build_lib( opts ) == 0 )
|
|
return 1;
|
|
}
|
|
if ( !opts.keep_files )
|
|
cleanup( files, opts );
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// int parse_args( int &argc, char **&argv, options &opts )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// argc : A reference to the argument count passed on the
|
|
// command line. One nice bonus of using C++ is that
|
|
// using a reference argument makes it easy for the
|
|
// parse routine to modify argc as it reads arguments,
|
|
// then pass the modified value back to the calling
|
|
// routine.
|
|
//
|
|
// argv : A reference to the pointer to the array of command
|
|
// line arguments. Like argc, this value gets incremented
|
|
// as we work our way through the command line.
|
|
//
|
|
// opts : A reference to the options structure used by this program.
|
|
// This routine has the job of initializing four members:
|
|
// dry_run, keep_files, target, and model.
|
|
//
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 1 if it is okay to keep going, 0 if a crummy command line got passed.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is called to initialize four of the options used in the
|
|
// build process. dry_run is set if the user specifies -cmd. keep_files
|
|
// is set if -keep is found, and target is set to EXE if -exe is specified.
|
|
// The library model is the next argument, it gets stored in a member of
|
|
// opts, after being converted to upper case.
|
|
//
|
|
// Note also that I strip any "." character from the name of a model.
|
|
// The "." character in the first position just prevents the model
|
|
// name from being displayed when the dump_sections() routine is called.
|
|
//
|
|
// After parsing the one or two arguments, argc and argv will be updated
|
|
// so the file name parsing can start right up.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Added support for the -exe option.
|
|
//
|
|
// January 26, 1995 4.0A : Added support for the -lib option. I think to
|
|
// be downward compatible I need to make this
|
|
// guy use -lib as the default, instead of
|
|
// requiring it.
|
|
//
|
|
|
|
int parse_args( int &argc, char **&argv, options &opts )
|
|
{
|
|
opts.dry_run = 0;
|
|
opts.keep_files = 0;
|
|
opts.ignore = 0;
|
|
opts.target = options::LIB;
|
|
//
|
|
// Instead of blindly looking in BUILD.INI for the INI file, I look
|
|
// for an INI file that has the same name as this executable. This
|
|
// makes it easy to have BUILDB31.EXE look for BUILDB31.INI, which
|
|
// helps keep our patch program happy.
|
|
//
|
|
opts.ini_file = *argv;
|
|
int i = opts.ini_file.last( '.' );
|
|
if ( i != -1 )
|
|
opts.ini_file.remove_after( i - 1 );
|
|
opts.ini_file = opts.ini_file + ".INI";
|
|
argc--; // go past argv[ 0 ], which is the program name
|
|
argv++;
|
|
//
|
|
// Now I strip any of the command line arguments, setting option flags
|
|
// accordingly.
|
|
//
|
|
while ( argc >= 1 ) {
|
|
if ( stricmp( *argv, "-cmd" ) == 0 ) {
|
|
opts.dry_run = 1;
|
|
argc--;
|
|
argv++;
|
|
} else if ( stricmp( *argv, "-keep" ) == 0 ) {
|
|
opts.keep_files = 1;
|
|
argc--;
|
|
argv++;
|
|
} else if ( stricmp( *argv, "-ignore" ) == 0 ) {
|
|
opts.ignore = 1;
|
|
argc--;
|
|
argv++;
|
|
} else if ( stricmp( *argv, "-exe" ) == 0 ) {
|
|
opts.target = options::EXE;
|
|
argc--;
|
|
argv++;
|
|
} else if ( stricmp( *argv, "-lib" ) == 0 ) {
|
|
opts.target = options::LIB;
|
|
argc--;
|
|
argv++;
|
|
} else if ( *argv[0]=='-' || *argv[0]=='/' ) {
|
|
bstring *stored_value = opts.find_value( "Opts" );
|
|
if ( stored_value ) {
|
|
if ( strlen( *stored_value ) > 0 )
|
|
*stored_value= *stored_value + " ";
|
|
*stored_value = *stored_value +*argv;
|
|
}
|
|
argc--;
|
|
argv++;
|
|
} else
|
|
break;
|
|
}
|
|
//
|
|
// Print the command line format if you didn't get anything good on the
|
|
// command line. At a minimum, I expect to get a memory model, which
|
|
// would mean that argc would have to be 1 or more.
|
|
//
|
|
if ( argc < 1 || opts.target == options::UNKNOWN ) {
|
|
cerr << "\nBuild Version 4.1B\n";
|
|
cerr << "Copyright (c) 1994, 1995 Greenleaf Software, Inc.\n";
|
|
cerr << "Usage: Build -exe | -lib [-cmd] [-keep] model [file(s)]\n\n";
|
|
cerr << "Options:\n";
|
|
cerr << "-cmd Create CMD files and quit\n";
|
|
cerr << "-keep Keep OBJ and CMD files after build\n";
|
|
cerr << "-lib Build a library or DLL\n";
|
|
cerr << "-exe Make an example or a demo, not a library\n";
|
|
cerr << "-ignore Ignore EXE and OBJ files that already exist\n";
|
|
cerr << "\n";
|
|
cerr << "Any other command switches get added to the opts variable.\n";
|
|
cerr << "\n";
|
|
cerr << "Note: If you are building a library, you shouldn't\n";
|
|
cerr << " include path names for your source files. The\n";
|
|
cerr << " BUILD program will look in the appropriate\n";
|
|
cerr << " directories for the memory model you are building.\n";
|
|
cerr << " Building demo files requires a path to the file.\n";
|
|
cerr << "\nIni file = " << opts.ini_file << "\n";
|
|
cerr << "\nHit any key to continue...";
|
|
getch();
|
|
cerr << "\nModels:\n";
|
|
dump_sections( opts );
|
|
return 0;
|
|
}
|
|
//
|
|
// The next argument should be the memory model.
|
|
//
|
|
opts.model = *argv;
|
|
opts.model.upper();
|
|
argc--;
|
|
argv++;
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// void dump_sections( options &opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : A reference to the options object.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function is called when parse_args() determines that that user
|
|
// didn't put anything on the command line. All it does is blow through
|
|
// the INI file, printing the name of each section it encounters
|
|
// along the way. Hidden sections are denoted by a leading ".". They
|
|
// don't get displayed, but they are treated as a normal section
|
|
// at all times.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Added hidden sections
|
|
//
|
|
|
|
void dump_sections( options &opts )
|
|
{
|
|
bstring current_description;
|
|
bstring section;
|
|
|
|
#ifdef __SC__
|
|
fstream config_file( opts.ini_file, ios::in );
|
|
#else
|
|
fstream config_file( opts.ini_file, ios::in | ios::binary );
|
|
#endif
|
|
if ( config_file.fail() ) {
|
|
cerr << "\nError - Could not open "
|
|
<< opts.ini_file
|
|
<< "!\n";
|
|
return;
|
|
}
|
|
int first_in_section = 1;
|
|
for ( ; ; ) {
|
|
switch ( get_next_section( section, config_file ) ) {
|
|
case NONE :
|
|
cerr << endl; //For the last section
|
|
return;
|
|
case HIDDEN :
|
|
break;
|
|
case NORMAL :
|
|
read_options( opts, config_file, "" );
|
|
bstring *d = opts.find_value( "Description" );
|
|
bstring new_description = *d;
|
|
int index = new_description.first( ',' );
|
|
if ( index >= 0 )
|
|
new_description.remove_after( index - 1 );
|
|
if ( !( new_description == current_description ) ) {
|
|
cerr << endl;
|
|
cerr.width( 29 );
|
|
// I don't know why, but with Symantec I have to do more than
|
|
// just set left, I have to clear right!!
|
|
cerr.setf( ios::left );
|
|
cerr.unsetf( ios::right );
|
|
cerr << new_description;
|
|
cerr << ": ";
|
|
current_description = new_description;
|
|
first_in_section = 1;
|
|
}
|
|
if ( !first_in_section )
|
|
cerr << ", ";
|
|
cerr << section;
|
|
first_in_section = 0;
|
|
opts.erase_values();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// int read_ini_file( options &opts )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : A reference to the options parameter that is used
|
|
// throughout the program. The options stucture has
|
|
// a linked list of elements, each of which a tag
|
|
// name and a value. This routine reads those elemeents
|
|
// out of BUILD.INI.
|
|
// RETURNS
|
|
//
|
|
// 1 if it is okay to keep going, 0 if a crummy command line got passed.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine opens up BUILD???.INI, then scans until it finds a section
|
|
// with the same name as the memory model specified on the command line.
|
|
// If it doesn't find that section, it is considered an error, and a 0
|
|
// is immediately returned to main().
|
|
//
|
|
// If the correction section is found, read_options() is then called to
|
|
// read all of the options into the options array. read_options() takes
|
|
// care of expanding any templates that are encounterd in the options
|
|
// file.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Updated to support templates
|
|
//
|
|
// January 26, 1995 4.0A : Removed template support from this section,
|
|
// since it is now handled in the read_options()
|
|
// routine.
|
|
//
|
|
|
|
int read_ini_file( options &opts )
|
|
{
|
|
bstring section;
|
|
|
|
#ifdef __SC__ // Symantec doesn't define binary, don't know how they do it, don't care
|
|
fstream config_file( opts.ini_file, ios::in );
|
|
#else
|
|
fstream config_file( opts.ini_file, ios::in | ios::binary );
|
|
#endif
|
|
if ( config_file.fail() ) {
|
|
cerr << "\nError - Could not open "
|
|
<< opts.ini_file
|
|
<< "!\n";
|
|
return 0;
|
|
}
|
|
if ( !read_options( opts, config_file, opts.model ) )
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// SECTION_TYPE get_next_section( bstring §ion, fstream &file )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// section : The string where we return the name of the next section
|
|
// after discovering it.
|
|
//
|
|
// file : A reference to the open fstream object.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Returns a SECTION_TYPE enum: NONE, HIDDEN, or NORMAL, depending
|
|
// on what type of section we found. Note that a section becomes
|
|
// a hidden section if the first character of its name is ".". If
|
|
// that is the case, we strip the leading period here, so it looks
|
|
// to the rest of the world as if it has a normal name. In other words,
|
|
// when I read in section [.DOS], I return HIDDEN, but switch the
|
|
// name to DOS. (The brackets get stripped too!)
|
|
//
|
|
// Note that the important thing about this routine is that it seeks
|
|
// to the appropriate position in the file, leaving the input pointer
|
|
// pointing directly to the first key value to be read in.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This utility function is used by read_ini_file() and dump_sections().
|
|
// All it does is advance through the file until it reaches the next
|
|
// section. A section in BUILD???.INI is defined as a line that contains
|
|
// nothing except "[string]".
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Added support for hidden sections
|
|
//
|
|
|
|
SECTION_TYPE get_next_section( bstring §ion, fstream &file )
|
|
{
|
|
char buffer[ 129 ];
|
|
for ( ; ; ) {
|
|
file.getline( buffer, 129 );
|
|
if ( file.fail() || file.eof() ) {
|
|
section = "";
|
|
file.clear( 0 );
|
|
return NONE;
|
|
}
|
|
section = buffer;
|
|
//
|
|
// I had to open this file in binary mode because of some problems
|
|
// with Microsoft C++. Since it is in binary mode, I get an extra '\r'
|
|
// which needs to be nuked.
|
|
//
|
|
int index = section.first( '\r' );
|
|
if ( index >= 0 && index == ( section.length() - 1 ) )
|
|
section.remove_after( index - 1 );
|
|
if ( section.first( '[' ) != 0 )
|
|
continue;
|
|
if ( section.last( ']' ) != ( section.length() - 1 ) )
|
|
continue;
|
|
section.remove_before( 1 );
|
|
section.remove_after( section.length() - 2 );
|
|
//
|
|
// Detect and mung a hidden section
|
|
//
|
|
if ( section[ 0 ] == '.' ) {
|
|
section.remove_before( 1 );
|
|
return HIDDEN;
|
|
}
|
|
return NORMAL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// int read_options( options &opts, fstream &file, const bstring& section )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : A reference to the options parameter that is used
|
|
// throughout the program. The options stucture has
|
|
// a linked list of elements, each of which a tag
|
|
// name and a value. This routine reads those elements
|
|
// out of BUILD.INI.
|
|
//
|
|
// file : A reference to an open fstream connected to BUILD.INI.
|
|
// This routine is called by read_ini_file(), and it is
|
|
// that function's job to make sure that the input pointer
|
|
// is sitting just at the right spot. In this case the
|
|
// spot should be right at the start of the first line
|
|
// of text immediately following the desired section.
|
|
//
|
|
// section : A reference to the section whose options I am supposed
|
|
// to read. If this is a blank string, it means I should
|
|
// just read in the options starting at the current location
|
|
// in the file. This is how it would work, for example, when
|
|
// I have just called get_next_section(), and I know that I
|
|
// am at the right place. If a valid section name is here,
|
|
// I instead seek back to the start of the file and start
|
|
// hunting for the appropriate section. This is what happens
|
|
// when parse_options() is trying to expand a template.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 1 if it is okay to keep going, 0 if a crummy command line got passed.
|
|
// Note that when this function returns after succesfully reading in a list
|
|
// of options, it will leave the input file pointer exactly where it was
|
|
// when it got called.
|
|
//
|
|
// This business of leaving things right were they were is very important,
|
|
// because this function may call itself recursively. When it reads in
|
|
// a line of text and passes it to parse_option(), it may be a Template=
|
|
// key line. If this is the case parse_option() will recursively call
|
|
// read_options() to read in the options for *that* section. When it
|
|
// finally returns, parse_option() will come back here leaving me in the
|
|
// dark as to all that really went on. Since I'm not aware that anything
|
|
// funny happened, I better not have to deal with an input file pointer
|
|
// that has moved around.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function is called by read_ini_file() during the startup of
|
|
// BUILD. Its job is read in all of the options defined for the current
|
|
// section. It doesn't attempt to find the correct section, that
|
|
// has to be done by read_ini_file() before this guy gets called.
|
|
//
|
|
// This function is actually a little lazy also. It reads in lines
|
|
// from the INI file, checks to make sure they aren't comments or the
|
|
// start of a new section, then calls parse_option() to do the
|
|
// real work.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Unchanged in this release. Normally this would
|
|
// pass without comment, but so much other stuff
|
|
// changed that I thought I should mention it.
|
|
//
|
|
// January 26, 1995 4.0A : Modified to save and restore the file pointer
|
|
// settings when called. This lets me be
|
|
// called recursively when expanding templates.
|
|
|
|
int read_options( options &opts,
|
|
fstream &file,
|
|
const bstring &target_section )
|
|
{
|
|
bstring line;
|
|
bstring section;
|
|
char buffer[ 257 ];
|
|
|
|
//
|
|
// Save the current location in the file, so I can go back to it on exit.
|
|
//
|
|
long saved_spot;
|
|
saved_spot = file.tellg();
|
|
//
|
|
// If I am looking for a specific section, I go back to the start of
|
|
// the file. If I wasn't asked for a specific section, I just assume
|
|
// I can start reading options right where I am at this moment, in which
|
|
// case I don't have to do any hunting.
|
|
//
|
|
if ( target_section != "" ) {
|
|
file.rdbuf()->seekoff( 0, ios::beg, ios::in );
|
|
for ( ; ; ) {
|
|
//
|
|
// I repeatedly call get_next_section() until it returns a match to the
|
|
// section I am looking for. If I get to the end of the file and
|
|
// haven't found it, flag this as an error.
|
|
//
|
|
if ( get_next_section( section, file ) == NONE ) {
|
|
cerr << "\nError - Couldn't find the section for model "
|
|
<< target_section
|
|
<< " in "
|
|
<< opts.ini_file
|
|
<< "!\n";
|
|
return 0;
|
|
} else if ( section == target_section )
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Now that I am at the correct section, I can just sit in a loop,
|
|
// reading in lines, and processing them.
|
|
//
|
|
for ( ; ; ) {
|
|
file.getline( buffer, 257 );
|
|
if ( file.fail() || file.eof() ) {
|
|
file.clear();
|
|
file.rdbuf()->seekoff( saved_spot, ios::beg, ios::in );
|
|
return 1;
|
|
}
|
|
line = buffer;
|
|
//
|
|
// I had to open this file in binary mode because of some problems
|
|
// with Microsoft C++. Since it is in binary mode, I get an extra '\r'
|
|
// at the end of the line which needs to be nuked.
|
|
//
|
|
int index = line.first( '\r' );
|
|
if ( index >= 0 && index == ( line.length() - 1 ) )
|
|
line.remove_after( index - 1 );
|
|
//
|
|
// If this is the start of a new section, I have read in all the
|
|
// options I am going to get, so I can quit.
|
|
//
|
|
if ( line[ 0 ] == '[' ) {
|
|
file.rdbuf()->seekoff( saved_spot, ios::beg, ios::in );
|
|
return 1;
|
|
}
|
|
// A comment or a blank line?
|
|
if ( line[ 0 ] == ';' || line[ 0 ] == '\0' )
|
|
continue;
|
|
// This line looks good, I call somebody else to do the parsing.
|
|
if ( parse_option( opts, line, file ) == 0 )
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// int parse_option( options &opts, bstring &line )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : A reference to the options parameter that is used
|
|
// throughout the program. The options stucture has
|
|
// a linked list of elements, each of which a tag
|
|
// name and a value. This routine sets up one of those
|
|
// elements based on what it finds in the command line
|
|
// passed to it by read_options().
|
|
//
|
|
// line : An actual line of input that has been read in from the
|
|
// INI file. It better be of the form "tag=value", or this
|
|
// routine is going to complain and return an error.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if the input line looked bad, 1 if it looked okay. Note that if
|
|
// this guy doesn't like an input line, it will return a value of
|
|
// 0, and the whole process will shut down.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is a specialist at one thing only. It takes a line that
|
|
// somebody else took the trouble of reading in from the INI file, and
|
|
// divides it into a tag and value. It then inserts that tag and value
|
|
// into the opts structure, so that anyone who wants to can take a gander
|
|
// at it later in the program.
|
|
//
|
|
// This function makes use of many member functions from the bstring
|
|
// and options classes to do its work. If they look a little strange,
|
|
// you can view the class descriptions in BUILD.H.
|
|
//
|
|
// In release 2.0B, I added support for multiple lines in a single command.
|
|
// This was done to give me more control over the Link/Implib process
|
|
// in CommLib. You put multiple commands together on one line, separated
|
|
// by a single caret ('^'). In this release, some of my Demo lines
|
|
// were getting really long, and unreadable. So in 3.0, I added
|
|
// a new way to enter multiple lines. You can now include multiple
|
|
// lines by repeating definitions of the the same value in a section.
|
|
// As each new value is encountered,
|
|
// it gets appended to the existing value, with a caret to separate them.
|
|
// Now multiple lines appear in the order to are going to execute them.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : New release supports multiple lines per command.
|
|
//
|
|
// January 26, 1995 4.0A : Added the ability to recursively call
|
|
// read_options() when I encounter a template
|
|
// definition. Note that it doesn't do the
|
|
// recursive call if opts.model == "", which
|
|
// means we are just displaying the options
|
|
// on the screen, since the user didn't specify
|
|
// a memory model.
|
|
//
|
|
|
|
int parse_option( options &opts, bstring &line, fstream& file )
|
|
{
|
|
//
|
|
// This code divides the line into two parts, a key and a value.
|
|
// The '=' is the dividing point. If the line doesn't divide
|
|
// up properly, I return an error message.
|
|
//
|
|
int divide = line.first( '=' );
|
|
if ( divide <= 0 ) {
|
|
cerr << "\nError - Badly formed option line in "
|
|
<< opts.ini_file
|
|
<< ": "
|
|
<< line
|
|
<< "\n";
|
|
return 0;
|
|
}
|
|
bstring key = line;
|
|
key.remove_after( divide - 1 );
|
|
bstring value = line;
|
|
value.remove_before( divide + 1 );
|
|
//
|
|
// This is a new piece of code. If it turns out that the key is "Template",
|
|
// it means I now need to read in all the values for that template. The
|
|
// name of the template is stored in the value, so I just pass it to
|
|
// read_options(). This is a recursive call, so everyone needs to be
|
|
// careful!!!
|
|
//
|
|
if ( key == "Template" && opts.model != "" )
|
|
return read_options( opts, file, value );
|
|
//
|
|
// Now that I have a new value, I need to store it in the options argument.
|
|
// I have to check to see if there is a stored_value already for my key.
|
|
// I'm not allowed to add new keys to the options unless they are CMD file
|
|
// definitions. So when I check on the stored value, I better get something
|
|
// back, even if it is an empty string. If I get a null pointer, it means
|
|
// that the key isn't in the opts container. If this is the case, the
|
|
// key better be "*LIB.CMD or "*EXE.CMD", or I flag an error.
|
|
//
|
|
bstring *stored_value = opts.find_value( key );
|
|
if ( stored_value == 0 ) {
|
|
if ( key.first( "LIB.CMD" ) < 0 && key.first( "EXE.CMD" ) < 0 ) {
|
|
cerr << "\nError - Unknown option found in "
|
|
<< opts.ini_file
|
|
<< ": "
|
|
<< line
|
|
<< "\n";
|
|
return 0;
|
|
} else {
|
|
opts.add_option( key, "" );
|
|
stored_value = opts.find_value( key );
|
|
}
|
|
}
|
|
//
|
|
// If I already have this description on file, I am going to append it
|
|
// to the existing line. I use the "^" symbol to separate lines
|
|
//
|
|
if ( *stored_value != "" )
|
|
*stored_value = *stored_value + "^" + value;
|
|
else
|
|
*stored_value = value;
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int build_lib_file_list( int argc,
|
|
// char **argv,
|
|
// file_list &files,
|
|
// options& opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// argc : A count of the arguments left after the command
|
|
// line has been partially parsed.
|
|
//
|
|
// argv : A pointer to the array of strings that are the
|
|
// arguments passed on the command line.
|
|
//
|
|
// files : This is a reference to the structure that contains
|
|
// the list of files that are going to be compiled.
|
|
//
|
|
// opts : A reference to the options structure for this build.
|
|
// By the time this function gets called, the entire
|
|
// option list will have been filed out. I need to know
|
|
// about it here because it contains the directory list.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if no files were added to the file list. 1 if some were. main()
|
|
// considers a value of 0 to be a good reason to abort.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is called by main() after it has finished parsing the
|
|
// INI file. Its job is to go through the list of files on the command
|
|
// line and add them all to the file list object. It does this by
|
|
// taking each of the file name arguments (which may or may not contain
|
|
// wildcards), and checking each of the defined directories for the
|
|
// presence of files that match the spec. The actual expansion of the
|
|
// wildcard and its addition to the file list is accomplished in
|
|
// add_wild_cards().
|
|
//
|
|
// Note that if the user doesn't specify *any* file names on the command
|
|
// line, we go ahead and parse "*.CPP", "*.C", and "*.ASM". This is so
|
|
// the build program can be run with even less thinking than before.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int build_lib_file_list( int argc, char **argv, file_list &files, options& opts )
|
|
{
|
|
char *default_files[ 10 ];
|
|
bstring default_directories( "ALL" );
|
|
bstring *dirs = opts.find_value( "Directories" );
|
|
if ( dirs == 0 )
|
|
dirs = &default_directories;
|
|
if ( argc == 0 ) {
|
|
bstring *s;
|
|
s = opts.find_value( "Cpp" );
|
|
if ( s != 0 && strlen( *s ) > 0 )
|
|
default_files[ argc++ ] = "*.CPP";
|
|
s = opts.find_value( "C" );
|
|
if ( s != 0 && strlen( *s ) > 0 )
|
|
default_files[ argc++ ] = "*.C";
|
|
s = opts.find_value( "Asm" );
|
|
if ( s != 0 && strlen( *s ) > 0 )
|
|
default_files[ argc++ ] = "*.ASM";
|
|
argv = default_files;
|
|
}
|
|
//
|
|
// This loop is where I expand wildcards for each of the specified files,
|
|
// in each of the specified directories.
|
|
//
|
|
for ( int i = 0 ; argc ; i++ ) {
|
|
bstring ext = bstring( *argv ).ext();
|
|
ext.remove_before( 1 );
|
|
bstring search_dirs = *dirs;
|
|
bstring dir;
|
|
for ( dir = search_dirs.get_first_token("^ ");
|
|
dir != "";
|
|
dir = search_dirs.get_next_token("^ ") ) {
|
|
//
|
|
// Microsoft 8.0 chokes on this constructor, had to replace it with the
|
|
// alternate one below it.
|
|
//
|
|
// bstring wild_guy( bstring( ROOT_DIR ) + ext + "_" + dir + "\\" + *argv );
|
|
bstring wild_guy = bstring( ROOT_DIR ) + ext + "_" + dir + "\\" + *argv;
|
|
add_wild_cards( files, wild_guy );
|
|
}
|
|
argv++;
|
|
argc--;
|
|
}
|
|
if ( files.get_first_file() == 0 ) {
|
|
cerr << "\nError - No files found to build!\n";
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int build_exe_file_list( int argc,
|
|
// char **argv,
|
|
// file_list &files )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// argc : A count of the arguments left after the command
|
|
// line has been partially parsed.
|
|
//
|
|
// argv : A pointer to the array of strings that are the
|
|
// arguments passed on the command line.
|
|
//
|
|
// files : This is a reference to the structure that contains
|
|
// the list of files that are going to be compiled.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if no files were added to the file list. 1 if some were. main()
|
|
// considers a value of 0 to be a good reason to abort.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// In release 3.0, we added the ability to build demo programs. When
|
|
// building libraries, we have a function just like this that is used
|
|
// to build a list of files. Unfortunately, we have to have a specialized
|
|
// version of this function for building demos, because we specify files
|
|
// in a slightly different way. When building libraries, we always
|
|
// specify files without a path name, like "STORCMP.CPP", and let BUILD
|
|
// figure out which directories to search. With demos it is different,
|
|
// because we give a full path name. So I have to perform a simpler
|
|
// version of the wild card expansion in this routine.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 5, 1995 3.0A : First release
|
|
//
|
|
|
|
int build_exe_file_list( int argc, char **argv, file_list &files )
|
|
{
|
|
if ( argc == 0 ) {
|
|
cerr << "\nError - No files found to build!\n";
|
|
return 0;
|
|
}
|
|
//
|
|
// This loop is where I expand wildcards for each of the specified files.
|
|
//
|
|
for ( int i = 0 ; argc ; i++ ) {
|
|
bstring wild_guy = *argv;
|
|
add_wild_cards( files, wild_guy );
|
|
argv++;
|
|
argc--;
|
|
}
|
|
//
|
|
// After it is all done, I check to see if I really got any files.
|
|
//
|
|
if ( files.get_first_file() == 0 ) {
|
|
cerr << "\nError - No files found to build!\n";
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// void add_wild_cards( file_list &files, const bstring &wild_spec )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// files : A reference to a file list structure. The file list
|
|
// is simply a linked list of file names. My job here
|
|
// is to expand the wild card I get passed and add the
|
|
// names to this guy.
|
|
//
|
|
// wild_spec : This is the wild card specification, although it is not
|
|
// necessarily a wild card, it might just be a name.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The user passes a list of file names on the command line, after
|
|
// the model. The names can be things like *.CPP and BOB.C. The
|
|
// routine that builds this file list takes each of those names,
|
|
// and prepends each of the directories to it. Then it calls this
|
|
// guy to expand the resulting wild card specification and add it to
|
|
// the file list structure. Adding to the structure is done via
|
|
// a nice simple member function.
|
|
//
|
|
// So if the guy types BUILD -lib S *.CPP, this function might get called
|
|
// three times, with the names "..\CPP_ALL\*.CPP", "..\CPP_DOS\*.CPP"
|
|
// and "..\CPP_FOO\*.CPP". I could add anywhere from zero to a zillion
|
|
// file names to the list as a result;
|
|
//
|
|
// Note that this function is called by both the LIB thread of execution
|
|
// and the EXE path. In the case of the EXE path, no path names are
|
|
// prepended to the file names, so we get a straightforward expansion.
|
|
//
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 18, 1994 2.0A : First release
|
|
//
|
|
|
|
#ifdef __OS2__
|
|
#define INCL_DOSFILEMGR
|
|
#include <os2.h>
|
|
|
|
void add_wild_cards( file_list &files, const bstring &wild_spec )
|
|
{
|
|
APIRET rc;
|
|
HDIR handle;
|
|
FILEFINDBUF3 buffer = { 0 };
|
|
ULONG count;
|
|
|
|
handle = 1;
|
|
count = 1;
|
|
rc = DosFindFirst( wild_spec,
|
|
&handle,
|
|
0,
|
|
(PVOID) &buffer,
|
|
sizeof( buffer ),
|
|
&count,
|
|
FIL_STANDARD );
|
|
bstring drive_and_path = wild_spec.drive() + wild_spec.path();
|
|
while ( rc == 0 ) {
|
|
bstring new_name = drive_and_path + buffer.achName;
|
|
files.add_file( new_name ); //Handy member function of class file_list
|
|
count = 1;
|
|
rc = DosFindNext( handle,
|
|
(PVOID) &buffer,
|
|
sizeof( buffer ),
|
|
&count );
|
|
}
|
|
}
|
|
#else
|
|
void add_wild_cards( file_list &files, const bstring &wild_spec )
|
|
{
|
|
struct find_t ffblk;
|
|
unsigned int result;
|
|
//
|
|
// Because of the way DOS expands names I need to break out the
|
|
// drive and path separately. So, if I am expanding C:\BOB\*.CPP,
|
|
// I want to keep a copy here of C:\BOB\. Then, as I expand the
|
|
// *.CPP part, I pack the resuling 8.3 name to the drive and path
|
|
// and voila, I have a complete filespec.
|
|
//
|
|
bstring drive_and_path = wild_spec.drive() + wild_spec.path();
|
|
|
|
// The funny cast of wild_spec is to accomodate Symantec C. They
|
|
// foolishly use char * instead of const char * for this arg.
|
|
|
|
result = _dos_findfirst( (char *) (const char *) wild_spec, 0, &ffblk );
|
|
while ( result == 0 ) {
|
|
bstring new_name = drive_and_path + ffblk.name;
|
|
files.add_file( new_name ); //Handy member function of class file_list
|
|
result = _dos_findnext( &ffblk );
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// void process( bstring &result,
|
|
// const char *command_string,
|
|
// const bstring &file_name,
|
|
// options &opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// result : A reference to an output string. Since the dorky
|
|
// little string class I built for this program doesn't
|
|
// have good functions for adding characters one at at
|
|
// time, I build this result in a temporary buffer, then
|
|
// copy it over before returning. Warning! the output
|
|
// buffer is big, so I made it static!
|
|
//
|
|
// command_string : A pointer to the command string. This is the string
|
|
// that was actually read out of the INI file. If there
|
|
// were multiple commands in one line, this guy is just
|
|
// one of them that has been separated out. This command
|
|
// line should be chock full of special formatting
|
|
// arguments such as %file%, %model%, etc.
|
|
//
|
|
// file_name : The name of the source file being affected by this
|
|
// command. This argument is not always meaningful.
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need to look at these
|
|
// because I need to get the option value for this particular
|
|
// CMD file out of this structure.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine takes a command line and processes it. Processing means
|
|
// that it takes all the special formatting arguments and then
|
|
// substitutes the appropriate values in the output string. The command
|
|
// is built up and stored in an output string. The following formatting
|
|
// codes are supported: (see top of file for more detail)
|
|
//
|
|
// %file% : The complete name of the source file.
|
|
//
|
|
// %file.path% : The drive and path of the file, with a guaranteed
|
|
// '\' character.
|
|
//
|
|
// %file.name% : The name component of the source file, minus an
|
|
// extension.
|
|
//
|
|
// %model% : The model/library name, as found inside the "[]".
|
|
//
|
|
// %model.lower% : The first letter of the model name, in lower case.
|
|
//
|
|
// %model.upper% : The first letter of the model name, in upper case.
|
|
//
|
|
// %model.asm% : A greenleaf model name for ASM files.
|
|
//
|
|
// %nl% : Inserts a new line into the output stream,
|
|
// literally a '\n';
|
|
//
|
|
// %% : Inserts the literal '%' character.
|
|
//
|
|
// %comspec% : Inserts the name of the current command processor.
|
|
//
|
|
// %continue% : This means the character immediately following
|
|
// the %continue% is a continuation character. If
|
|
// there are more files coming after this one, we
|
|
// output the continuation character. Otherwise,
|
|
// it gets eaten.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 5, 1995
|
|
//
|
|
|
|
void process( bstring &result,
|
|
const char *command_string,
|
|
const bstring &file_name,
|
|
options &opts,
|
|
int more_to_come )
|
|
{
|
|
const char *p;
|
|
static char _temp[ 1024 ];
|
|
char *buffer = _temp;
|
|
bstring path = file_name.drive() + file_name.path();
|
|
bstring name = file_name.name();
|
|
|
|
while ( *command_string ) {
|
|
if ( *command_string == '%' ) {
|
|
command_string++;
|
|
bstring tag = command_string;
|
|
p = strchr( command_string, '%' );
|
|
if ( !p ) {
|
|
cerr << "Missing closing '%' in command line: "
|
|
<< command_string
|
|
<< endl;
|
|
exit( -1 );
|
|
}
|
|
tag.remove_after( p - command_string - 1 );
|
|
command_string = p + 1;
|
|
if ( tag == "file" || tag == "*.c" || tag == "*.cpp" || tag == "*.asm" )
|
|
for ( p = file_name ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
else if ( tag == "file.path" )
|
|
for ( p = path ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
else if ( tag == "file.name" )
|
|
for ( p = name ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
else if ( tag == "model" )
|
|
for ( p = opts.model ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
else if ( tag == "model.upper" )
|
|
*buffer++ = toupper( opts.model[ 0 ] );
|
|
else if ( tag == "model.lower" )
|
|
*buffer++ = tolower( opts.model[ 0 ] );
|
|
else if ( tag == "nl" )
|
|
*buffer++ = '\n';
|
|
else if ( tag == "" )
|
|
*buffer++ = '%';
|
|
else if ( tag == "comspec" ) {
|
|
p = getenv( "COMSPEC" );
|
|
if ( !p )
|
|
p = "COMMAND.COM";
|
|
for ( ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
} else if ( tag == "model.asm" ) {
|
|
switch( tolower( opts.model[ 0 ] ) ) {
|
|
case 's' : p = "SMALL"; break;
|
|
case 'c' : p = "COMPACT"; break;
|
|
case 'm' : p = "MEDIUM"; break;
|
|
case 'l' : p = "LARGE"; break;
|
|
case 'h' : p = "HUGE"; break;
|
|
default : p = "UNKNOWN"; break;
|
|
}
|
|
for ( ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
} else if ( tag == "continue" ) {
|
|
if ( !more_to_come )
|
|
command_string++;
|
|
} else if ( tag == "Opts" ) {
|
|
bstring *def_value = opts.find_value( "Opts" );
|
|
for ( p = *def_value ; *p != 0 ; )
|
|
*buffer++ = *p++;
|
|
} else {
|
|
cerr << "Unrecognized tag in INI file: "
|
|
<< tag
|
|
<< endl;
|
|
exit( 1 );
|
|
}
|
|
} else
|
|
*buffer++ = *command_string++;
|
|
|
|
}
|
|
*buffer = '\0';
|
|
result = _temp;
|
|
}
|
|
|
|
//
|
|
// void build_lib_config_files( options &opts, file_list &files )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need to look at these
|
|
// options so I can figure out which CMD files need to be
|
|
// built.
|
|
//
|
|
// files : A list of all the files that are going to get compiled.
|
|
// I need this list so that I can build LIB.CMD, as well as
|
|
// any compiler response files that get wild cards expanded.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Sometimes compiler or link commands get too big for the command line.
|
|
// When this happens, we have to put options into a response file, and
|
|
// execute something like this: CC blah blah @CCLIB.CMD file.c.
|
|
//
|
|
// The stuff that goes into the CMD file is stored in BUILD.INI. It
|
|
// is always stored in a tag with a name like xxxLIB.CMD. This routine
|
|
// finds every option tag whose name ends with "LIB.CMD". It then dumps
|
|
// the value of that tag into a file called "xxxLIB.CMD.
|
|
//
|
|
// There is one real tricky bit in here. If one of the lines in the CMD
|
|
// file contains a %filexxxx% keyword, I repeatedly output that line for
|
|
// each and every file in the file list. This is how the LIB.CMD file
|
|
// gets built, for example. If the CMD file instead contains "*.ASM",
|
|
// "*.C", or "*.CPP", we expand just those guys.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 18, 1994 2.0A : First release
|
|
//
|
|
// January 31, 1195 4.0B : Modified to expand wild cards like "*.C".
|
|
// Since these files are only built once at
|
|
// the start of a compile cycle, this wasn't a
|
|
// a useful feature up until the time I addes
|
|
// support for batch compilation.
|
|
//
|
|
|
|
|
|
void build_lib_config_files( options &opts, file_list &files )
|
|
{
|
|
for ( item *option = opts.get_first_item();
|
|
option != 0;
|
|
option = option->get_next_item() ) {
|
|
if ( option->name.first( "LIB.CMD" ) >= 0 ) {
|
|
fstream config_file( option->name, ios::out );
|
|
bstring command_string;
|
|
command_string = option->value.get_first_token( "^" );
|
|
while ( command_string != "" ) {
|
|
bstring parsed_string;
|
|
if ( command_string.first( "%*." ) >= 0 )
|
|
build_wild_card_line( opts, files, config_file, command_string );
|
|
else if ( command_string.first( "%file" ) >= 0 ) {
|
|
for ( bstring *file = files.get_first_file() ;
|
|
file != 0; ) {
|
|
bstring *temp = file;
|
|
file = files.get_next_file();
|
|
process( parsed_string, command_string, *temp, opts, file != 0 );
|
|
config_file << parsed_string << endl;
|
|
}
|
|
} else {
|
|
process( parsed_string, command_string, "XXXXXXXX.XXX", opts, 0 );
|
|
config_file << parsed_string << endl;
|
|
}
|
|
command_string = option->value.get_next_token( "^" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// void build_wild_card_line( options &opts,
|
|
// file_list &files,
|
|
// fstream &config_file,
|
|
// bstring &command_string )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need to look at these
|
|
// options so I can figure out how to build this cmd file.
|
|
//
|
|
// files : A list of all the files that are going to get compiled.
|
|
// I need to search through this list to find the files that
|
|
// match the wild card specification, and output a parsed line
|
|
// to the config file for each one.
|
|
//
|
|
// config_file : The open config file. I will be writing out to this file.
|
|
//
|
|
// command_string : The string that contains the wild card. I have to process
|
|
// this string and write it out once per matching file.
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Sometimes I want to perform an assembly or compilation in batch mode.
|
|
// When this is the case, instead of calling BCC once for every file,
|
|
// I just call it once with a line like this: BCC blah blah @CLIB.CMD.
|
|
// This means that CLIB.CMD has to have a list of files in it. To put
|
|
// a list of files in a CMD file, you could just put "%file.name%" or
|
|
// one of the other "%file%" tages. But that would put *all* the files
|
|
// in their, and I just want the "*.c", or "*.asm", or whatever.
|
|
//
|
|
// So instead, I put exactly that wildcard specification in the definition
|
|
// for my CMD file: "*.CPP", or "*.C", or "*.ASM". When a line like this
|
|
// is encountered, build_lib_config_files() calls this routine to expand
|
|
// the line and write it out to the output file.
|
|
//
|
|
// This routine then has to go through the list, and select every file that
|
|
// matches the extension of the wild card. Each of those files gets
|
|
// processed into the output line, and added to the config file.
|
|
//
|
|
// If even one file gets added to the config file, the compiler for that
|
|
// type of file gets set to run in BATCH mode.
|
|
//
|
|
// Note that this guy also checks to see if any objects exist for each
|
|
// particular file, preventing us from performing compilations that
|
|
// don't need to be done.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release.
|
|
//
|
|
|
|
void build_wild_card_line( options &opts,
|
|
file_list &files,
|
|
fstream &config_file,
|
|
bstring &command_string )
|
|
{
|
|
//
|
|
// First I need to isolate the wild card extension. I remove the leading
|
|
// "*.", then the trailing index. When that is all done, I should have
|
|
// a file extension, as in "C", "CPP", or "ASM". I put that into bstring
|
|
// extension for clarity, and use it throughout this routine.
|
|
//
|
|
bstring wild_card = command_string;
|
|
int index = command_string.first( "%*." );
|
|
wild_card.remove_before( index + 2 ); // delete % and *
|
|
index = wild_card.first( "%" );
|
|
if ( index < 0 ) {
|
|
cerr << "Failed to find matching % in config file definition!" << endl;
|
|
exit( 1 );
|
|
} else
|
|
wild_card.remove_after( index - 1 );
|
|
bstring extension = wild_card;
|
|
extension.remove_before( 1 );
|
|
//
|
|
// I need to find the command for this extension. If I match up any files
|
|
// with the wildcard, I am going to switch the command to BATCH mode. If I
|
|
// can't find a command to match this wildcard, something is wrong!
|
|
//
|
|
item *option = opts.find_item( extension );
|
|
if ( option == 0 ) {
|
|
cerr << "No commands defined for " << wild_card << endl;
|
|
exit( 1 );
|
|
}
|
|
//
|
|
// Now I just sit in a loop and get files that have the same extension as
|
|
// the wildcard. Each one I find is going to get stuffed out into the CMD
|
|
// file via the process() routine, unless an object already exists. If
|
|
// even one file gets added to the config file, I will set the compiler to
|
|
// run in batch mode.
|
|
//
|
|
for ( bstring *file = files.get_first_file(); file != 0; ) {
|
|
bstring parsed_string;
|
|
bstring *temp = file;
|
|
file = files.get_next_file();
|
|
if ( temp->ext() == wild_card ) {
|
|
if ( !opts.ignore && target_file_exists( *temp, ".OBJ" ) ) {
|
|
cerr << "Skipping " << *temp << endl;
|
|
} else {
|
|
process( parsed_string, command_string, *temp, opts, file != 0 );
|
|
config_file << parsed_string << endl;
|
|
option->run_mode = item::BATCH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// void build_exe_config_files( options &opts, bstring &file )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need to look at these
|
|
// options so I can figure out which CMD files need to be
|
|
// built.
|
|
//
|
|
// file : The name of the file that is going to be turned into an
|
|
// exe. Note that this is different from building config
|
|
// files for libraries. This is because I build a new config
|
|
// file for each and every EXE I build. With libraries, I
|
|
// only build a single set of config files one time. .
|
|
//
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Sometimes compiler or link commands get too big for the command line.
|
|
// When this happens, we have to put options into a response file, and
|
|
// execute something like this: CC blah blah @CCEXE.CMD file.c.
|
|
//
|
|
// The stuff that goes into the CMD file is stored in BUILD.INI. It
|
|
// is always stored in a tag with a name like "xxxxEXE.CMD". This routine
|
|
// finds every option tag whose name ends with "EXE.CMD". It then dumps
|
|
// the value of that tag into a file called xxxEXE.CMD.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 26, 1995 4.0A : Brand new!
|
|
|
|
void build_exe_config_files( options &opts, bstring &file )
|
|
{
|
|
for ( item *option = opts.get_first_item();
|
|
option != 0;
|
|
option = option->get_next_item() ) {
|
|
if ( option->name.first( "EXE.CMD" ) >= 0 ) {
|
|
fstream config_file( option->name, ios::out );
|
|
bstring command_string;
|
|
command_string = option->value.get_first_token( "^" );
|
|
while ( command_string != "" ) {
|
|
bstring parsed_string;
|
|
process( parsed_string, command_string, file, opts, 0 );
|
|
config_file << parsed_string << endl;
|
|
command_string = option->value.get_next_token( "^" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// void dump_options( options &opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. They are what gets
|
|
// printed out in this routine.
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is called by main() to display all of the current options
|
|
// to the user before starting to compile. All of the options are stored
|
|
// in the opts array, and I just print them one at a time.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Modified the code so that I don't spill
|
|
// over the end of the line if an option line
|
|
// is really long. Instead, I just print some
|
|
// dots at the end of the line.
|
|
//
|
|
|
|
void dump_options( options &opts )
|
|
{
|
|
cout.setf( ios::left );
|
|
//
|
|
// Symantec C++ does really funny things if you don't clear ios::right
|
|
//
|
|
cout.unsetf( ios::right );
|
|
cout.width( 15 );
|
|
cout << "Model" << " : " << opts.model << "\n";
|
|
for ( item *citem = opts.get_first_item() ;
|
|
citem != 0;
|
|
citem = citem->get_next_item() ){
|
|
bstring token = citem->value.get_first_token( "^" );
|
|
int i = 0;
|
|
while ( token != "" ) {
|
|
cout.width( 15 );
|
|
if ( i++ == 0 )
|
|
cout << citem->name;
|
|
else
|
|
cout << "";
|
|
cout << " : ";
|
|
if ( strlen( token ) > 59 ) {
|
|
token.remove_after( 57 );
|
|
token = token + "...";
|
|
}
|
|
cout << token << "\n";
|
|
token = citem->value.get_next_token( "^" );
|
|
}
|
|
}
|
|
cout << "\n";
|
|
}
|
|
|
|
//
|
|
// int compile_lib_files( options &opts, file_list &files )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need them here to
|
|
// figure out how to compile each file.
|
|
//
|
|
// files : The massive list of files.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if any error occurs when compiling, 1 if every single file compiled
|
|
// without error.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is called by main() to compile all the files in the file
|
|
// list. The term "compiler" is used kind of loosely, because we don't
|
|
// really care whether it is a compiler, assembler or what.
|
|
//
|
|
// The way it works is that we step through the file list, processing files
|
|
// one at a time. Each file has a compile command, which is in the tag
|
|
// specified by the file extension. So, for example, if I want to compile
|
|
// ..\CPP_ALL\KERMIT.CPP, I look up the tag CPP, and execute the command
|
|
// stored there. There are a few helper functions that help me accomplish
|
|
// this. One of the most important ones is execute(), because it has to
|
|
// break the command down into tokens and stuff them into an argv[] array
|
|
// before callling spawnv().
|
|
//
|
|
// With the addition of BATCH compiles in 4.0, I have one additional
|
|
// requirement before building a file. As I process each file, I have
|
|
// to check to see if the compiler is set to BATCH mode, or ONE_AT_A_TIME
|
|
// mode. If it is set to BATCH mode, we aren't going to do anything here,
|
|
// so we just skip. It is kind of inefficient, because we have to check
|
|
// this for every file, not just once per compiler.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Added the call to process() to provide complete
|
|
// substitution for formatting commands. I used
|
|
// to use sprintf() with the file name, so that
|
|
// limited the substitutions. Now a compiler line
|
|
// can include anything!
|
|
//
|
|
|
|
int compile_lib_files( options &opts, file_list &files )
|
|
{
|
|
//
|
|
// I iterate through the loop, once per file name. Note that the first
|
|
// thing I do is check to see if the object file exists. If it does, we
|
|
// skip the compile step. This is kind of dangerous, because we might have
|
|
// an object left over from a different memory model, but it saves a lot of
|
|
// time when building is interrupted by an error.
|
|
//
|
|
bstring *file = files.get_first_file();
|
|
while ( file ) {
|
|
bstring extension = file->ext();
|
|
extension.remove_before( 1 );
|
|
item *compile_item = opts.find_item( extension );
|
|
if ( compile_item == 0 ) {
|
|
cerr << "Error compiling " << *file << ". No compile command defined.\n";
|
|
return 0;
|
|
}
|
|
if ( compile_item->run_mode == item::ONE_AT_A_TIME ) {
|
|
if ( !opts.ignore && target_file_exists( *file, ".OBJ" ) ) {
|
|
cerr << "Skipping " << *file << "\n";
|
|
} else {
|
|
//
|
|
// Once I have the file, I loop up the compile string, and the exec file.
|
|
// I use sprintf() to format a command line, then call the execute routine
|
|
// to make it all happen.
|
|
//
|
|
bstring buffer;
|
|
process( buffer, compile_item->value, *file, opts, 0 );
|
|
if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
|
|
return 0;
|
|
}
|
|
} // object_exists
|
|
file = files.get_next_file();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int lib_batch_compiles( options &opts )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need them here to
|
|
// figure out how to run the compilers.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if any error occurs when compiling, 1 if the compiler ran okay.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// In release 4.0, I added the ability to perform batch compilations.
|
|
// This routine performs the compilations. If any one of the three
|
|
// compiler types has its option set to BATCH, we run the batch compiler
|
|
// instead of the one at a time compiler. This routine does it.
|
|
//
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release.
|
|
//
|
|
|
|
int lib_batch_compiles( options &opts )
|
|
{
|
|
char *exts[]={ "C", "CPP", "ASM", "" };
|
|
|
|
for ( int i = 0 ; exts[ i ][ 0 ] != '\0'; i++ ) {
|
|
item *compile_item = opts.find_item( exts[ i ] );
|
|
if ( compile_item->run_mode != item::BATCH )
|
|
continue;
|
|
cerr << "Building " << exts[ i ] << " files in batch mode" << endl;
|
|
bstring buffer;
|
|
process( buffer, compile_item->value, "XXXXXXXX.XXX", opts, 0 );
|
|
if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
|
|
return 0;;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int build_exe_files( options &opts, file_list &files )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : This structure contains all of the options that have
|
|
// been read out of the INI file. I need them here to
|
|
// figure out how to compile each file.
|
|
//
|
|
// files : The massive list of files.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if any error occurs when compiling, 1 if every single file compiled
|
|
// without error.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is the routine I call when the program is in demo building mode.
|
|
// It is similar to the previous version used to compiler programs, but
|
|
// has enough differences to justify a separate routine. The first big
|
|
// difference is that this guy will handle compound statements. I can
|
|
// use four or five different lines to build a demo. Second, in the
|
|
// previous function, I picked a command based on the extension of the
|
|
// file. In this routine, I only use the Demo command. I think
|
|
// that covers it.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 5, 1995 3.0A : First release.
|
|
//
|
|
// January 5, 1995 3.0A : Added the call to process() to provide complete
|
|
// substitution for formatting commands. I used
|
|
// to use sprintf() with the file name, so that
|
|
// limited the substitutions. Now a compiler line
|
|
// can include anything!
|
|
//
|
|
// January 26, 1995 4.0A : Added the call to build_exe_config_files()
|
|
// before the start of each file's execution.
|
|
// Previously demo files were restricted to
|
|
// a single hardcode DEMO.CMD file name, now
|
|
// they can have any number of files, as long
|
|
// as they all fit the pattern of *EXE.CMD
|
|
//
|
|
|
|
int build_exe_files( options &opts, file_list &files )
|
|
{
|
|
bstring *compile_string = opts.find_value( "EXE" );
|
|
if ( compile_string == 0 || *compile_string == "" ) {
|
|
cerr << "Error in build of demo. No EXE command(s) defined.\n";
|
|
return 0;
|
|
}
|
|
|
|
bstring *file = files.get_first_file();
|
|
while ( file ) {
|
|
build_exe_config_files( opts, *file );
|
|
bstring command_string = compile_string->get_first_token( "^" );
|
|
if ( !opts.ignore && target_file_exists( *file, ".EXE" ) ) {
|
|
cout << "Skipping " << *file << endl;
|
|
} else
|
|
while ( command_string != "" ) {
|
|
bstring buffer;
|
|
process( buffer, command_string, *file, opts, 0 );
|
|
if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
|
|
return 0;
|
|
command_string = compile_string->get_next_token( "^" );
|
|
}
|
|
file = files.get_next_file();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int target_file_exists( const bstring &file, const bstring &ext )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// file : The name of a source file. This can contain a full
|
|
// pathname and an extension. Normally, this parameter
|
|
// will be something akin to "..\\CPP_WIN\FOO.ASM".
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 1 if an object file exists, 0 if not.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This routine is called by compile_lib_files(). It doesn't want to
|
|
// compile a file if a good object for that file already exists.
|
|
// This routine just strips the path and extension, then checks to see if
|
|
// an OBJ file exists in the current directory. If so, it returns
|
|
// a 1.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int target_file_exists( const bstring &file, const bstring &ext )
|
|
{
|
|
FILE *p = fopen ( file.name() + ext, "r" );
|
|
if ( !p )
|
|
return 0;
|
|
fclose( p );
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int execute( bstring &command, int dry_run )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// command : This is the command string, just as you would type it at
|
|
// the DOS prompt, e.g. "cl /I..\H /AL /c foo.cpp".
|
|
//
|
|
// dry_run : If this flag is set, it means the user has passed the
|
|
// -cmd on the command line, meaning I don't really want
|
|
// to execute the command, instead I just want to display
|
|
// the commands that would have been executed.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The return code from the spawn() command. If things went okay it
|
|
// will be a 0. If the command didn't run it will be 0xffff. If it
|
|
// is greater than 0, we assume the application ran and returned some
|
|
// sort of error code. If the user hits the escape key I return 1.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is the function that actually executes a command. We need a
|
|
// completely separate function because of the way the spawn() functoin
|
|
// works. Instead of passing spawnv() a nicely built command line, we
|
|
// have to break it apart into tokens, and pass an array of pointers
|
|
// to those tokens. What a pain! Not only that, the tokens have to
|
|
// be nice little char * objects, which means I have to allocate them
|
|
// and then free them up after the command.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// June 29, 1994 2.1A : Added the ability to cancel by hitting the ESC key.
|
|
//
|
|
// January 5, 1995 3.0A : Changed the token separator characters. I was
|
|
// having a little problem with TLINK from Borland.
|
|
// it seems to want *all* of its arguments piled
|
|
// up in a single character. When I separated them
|
|
// base on the comma character, I was getting lots
|
|
// of problems. So, comma is no longer a separator.
|
|
//
|
|
// January 26, 1995 4.0A : I used to search the path for the file to
|
|
// execute, which was a lot of extra work. I did
|
|
// it because I figured that if I searched the
|
|
// path once, then cached the result, I could save
|
|
// time the next time I invoked the same command.
|
|
// Now I'm not so sure I believe that.
|
|
//
|
|
|
|
int execute( bstring &command, int dry_run )
|
|
{
|
|
const int COUNT = 20;
|
|
char *args[ COUNT ];
|
|
|
|
int i = 0;
|
|
while ( kbhit() ) {
|
|
int c = getch();
|
|
if ( c == 0x1b ) {
|
|
cerr << "User abort!\n";
|
|
return 1;
|
|
}
|
|
}
|
|
cerr << command << "\n";
|
|
for ( bstring token = command.get_first_token( " " );
|
|
token.length() != 0;
|
|
token = command.get_next_token( " " ) ) {
|
|
if ( i >= ( COUNT - 3 ) ) {
|
|
cerr << "\nError - Too many arguments in command!\n";
|
|
return 1;
|
|
}
|
|
args[ i++ ] = strdup( token );
|
|
}
|
|
args[ i ] = strdup( "" );
|
|
args[ i + 1 ] = 0;
|
|
int ret_code = 0;
|
|
if ( !dry_run ) {
|
|
#if defined( __TURBOC__ ) || defined( __WATCOMC__ )
|
|
ret_code = spawnvp( P_WAIT, args[ 0 ], args );
|
|
#else
|
|
ret_code = spawnvp( P_WAIT, args[ 0 ], (const char * const *) args );
|
|
#endif
|
|
if ( ret_code != 0 ) {
|
|
cerr << "Error code "
|
|
<< ret_code
|
|
<< " executing: <"
|
|
<< command
|
|
<< ">\n";
|
|
}
|
|
}
|
|
for ( i = 0 ; i < COUNT ; i++ )
|
|
if ( args[ i ] == 0 )
|
|
break;
|
|
else
|
|
free( args[ i ] );
|
|
return ret_code;
|
|
}
|
|
|
|
//
|
|
// int build_lib( options &opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// opts : The options object. I need this because it holds
|
|
// the linker command.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if things didn't go well, otherwise a 1.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// After all of the files for the library have been built, we call
|
|
// this routine. It's job is to take all the OBJ files and turn it
|
|
// into a useful library. This might mean just running the librarian,
|
|
// but it also might mean running lib, then the linker, then implib.
|
|
// Either way, it happens here. I just execute all of the LIB commands
|
|
// from the INI file, one after another.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 5, 1995 3.0A : Added the call to process(), so that linker
|
|
// command gets full format processing.
|
|
//
|
|
// January 26, 1995 4.0A : The librarian work used to be divided up
|
|
// between this function, and another one that
|
|
// took care of linking. Now it all happens
|
|
// here. All of the commands should be grouped
|
|
// under the LIB command.
|
|
//
|
|
|
|
int build_lib( options &opts )
|
|
{
|
|
bstring *linker_string = opts.find_value( "Lib" );
|
|
if ( linker_string == 0 || *linker_string == "" )
|
|
return 1;
|
|
|
|
bstring command_string;
|
|
command_string = linker_string->get_first_token( "^" );
|
|
while ( command_string != "" ) {
|
|
bstring buffer;
|
|
process( buffer, command_string, "", opts, 0 );
|
|
if ( execute( bstring( buffer ), opts.dry_run ) != 0 )
|
|
return 0;
|
|
command_string = linker_string->get_next_token( "^" );
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int cleanup( file_list &files, options &opts )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// files : A list of the files that were processed. This function
|
|
// needs the list in order to delete all of the object
|
|
// files.
|
|
//
|
|
// opts : The options object. I need this because it contains
|
|
// the DeleteFiles options, which tells me the names of
|
|
// the other files to delete.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Always returns a 1, indicating success.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function is called by main() after all the steps in the build
|
|
// have completed. Its job is to delete all the objects that have been
|
|
// left around, then any other files the BUILD.INI parameters may have
|
|
// specified. Deleting the objects is simply a matter of churning through
|
|
// the list of files, deleting them one by one. The list of files
|
|
// to delete specified in DeleteFiles is pretty easy too, except I
|
|
// expand them first for wildcards.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int cleanup( file_list &files, options &opts )
|
|
{
|
|
//
|
|
// The first half of this routine is dedicated to deleting all
|
|
// the files specified in the DeleteFiles option of BUILD.INI
|
|
//
|
|
bstring *delete_files = opts.find_value( "DeleteFiles" );
|
|
if ( delete_files ) {
|
|
for ( bstring wild_file = delete_files->get_first_token( "^ " );
|
|
wild_file != "";
|
|
wild_file = delete_files->get_next_token( "^ " ) ) {
|
|
//
|
|
// The funny cast of wild_spec is to accomodate Symantec C. They
|
|
// foolishly use char * instead of const char * for this arg.
|
|
//
|
|
#if __OS2__
|
|
APIRET rc;
|
|
HDIR handle;
|
|
FILEFINDBUF3 buffer = { 0 };
|
|
ULONG count;
|
|
handle = 1;
|
|
count = 1;
|
|
rc = DosFindFirst( wild_file,
|
|
&handle,
|
|
0,
|
|
(PVOID) &buffer,
|
|
sizeof( buffer ),
|
|
&count,
|
|
FIL_STANDARD );
|
|
while ( rc == 0 ) {
|
|
cerr << "DEL " << buffer.achName;
|
|
if ( opts.keep_files )
|
|
cerr << "... Kept!";
|
|
else
|
|
unlink( buffer.achName );
|
|
cerr << "\n";
|
|
count = 1;
|
|
rc = DosFindNext( handle,
|
|
(PVOID) &buffer,
|
|
sizeof( buffer ),
|
|
&count );
|
|
}
|
|
#else
|
|
struct find_t ffblk;
|
|
unsigned int result;
|
|
result = _dos_findfirst( (char *) (const char *) wild_file, 0, &ffblk );
|
|
while ( result == 0 ) {
|
|
cerr << "DEL " << ffblk.name;
|
|
if ( opts.keep_files )
|
|
cerr << "... Kept!";
|
|
else
|
|
unlink( ffblk.name );
|
|
cerr << "\n";
|
|
result = _dos_findnext( &ffblk );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
//
|
|
// The second half of this routine is dedicated to deleting all of the
|
|
// objects left behind after the compile completed.
|
|
//
|
|
bstring *file = files.get_first_file();
|
|
while ( file ) {
|
|
bstring obj_name = file->name() + ".OBJ";
|
|
cerr << "DEL " << obj_name;
|
|
if ( opts.keep_files )
|
|
cerr << "... Kept!";
|
|
else
|
|
unlink( obj_name );
|
|
cerr << "\n";
|
|
file = files.get_next_file();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// bstring::bstring( const char *init = "" )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// init : The character string to initialize the string to.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// No returns.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of the constructors for my string class. It initializes
|
|
// the tokenizer pointer to 0 (since we don't need a tokenizer copy
|
|
// until somebody calls get_first_token()). It allocates space for
|
|
// the initial string, then copies it.
|
|
//
|
|
// Note that this guy generates an assertion error if the memory
|
|
// allocation fails. There should never be a time when a valid bstring
|
|
// object has a null pointer in the string member. Doing things this
|
|
// way simplifies my life a little.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring::bstring( const char *init )
|
|
{
|
|
if ( init == 0 )
|
|
init = "";
|
|
string = new char[ strlen( init ) + 1 ];
|
|
assert( string );
|
|
strcpy( string, init );
|
|
tokenizer = 0;
|
|
}
|
|
|
|
//
|
|
// bstring::bstring( const bstring& rhs )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// rhs : The bstring object being used to construct this object.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// No returns from constructors.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of the constructors for my string class. It initializes
|
|
// the tokenizer pointer to 0 (since we don't need a tokenizer copy
|
|
// until somebody calls get_first_token()). It allocates space for
|
|
// the initial string, then copies it.
|
|
//
|
|
// Note that this guy generates an assertion error if the memory
|
|
// allocation fails. There should never be a time when a valid bstring
|
|
// object has a null pointer in the string member. Doing things this
|
|
// way simplifies my life a little.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring::bstring( const bstring& rhs )
|
|
{
|
|
const char *p = rhs;
|
|
string = new char[ strlen( p ) + 1 ];
|
|
assert( string );
|
|
strcpy( string, p );
|
|
tokenizer = 0;
|
|
}
|
|
|
|
//
|
|
// bstring::~bstring()
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// None.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The string destructor just has to free memory. There will always
|
|
// be memory allocated for the "string" member. There may or may not
|
|
// be memory allocated in the tokenizer member.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring::~bstring()
|
|
{
|
|
delete[] string;
|
|
if ( tokenizer )
|
|
delete[] tokenizer;
|
|
}
|
|
|
|
//
|
|
// bstring& bstring::operator = ( const bstring& rhs )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// rhs : Another string object that is going to be copied into this.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A reference to this.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This operator is used to copy one bstring object to another. Note
|
|
// once again the assertion error that occurs in the event of an
|
|
// allocation failure.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring& bstring::operator = ( const bstring& rhs )
|
|
{
|
|
if ( (const char *) rhs == string )
|
|
return *this;
|
|
delete[] string;
|
|
string = new char[ strlen( rhs ) + 1 ];
|
|
assert( string );
|
|
strcpy( string, rhs );
|
|
return *this;
|
|
}
|
|
|
|
//
|
|
// bstring& bstring::operator = ( const char *s )
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : The character string to copy into this.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A reference to this.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This copies a character string into a bstring.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring& bstring::operator = ( const char *s )
|
|
{
|
|
if ( string == s )
|
|
return *this;
|
|
delete[] string;
|
|
string = new char[ strlen( s ) + 1 ];
|
|
assert( string );
|
|
strcpy( string, s );
|
|
return *this;
|
|
}
|
|
|
|
//
|
|
// bstring& bstring::upper()
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A reference to this.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Converts a string to all upper case.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 5, 1995 3.0A : First release.
|
|
//
|
|
|
|
bstring& bstring::upper()
|
|
{
|
|
for ( int i = 0 ; string[ i ] ; i++ )
|
|
string[ i ] = toupper( string[ i ] );
|
|
return *this;
|
|
}
|
|
|
|
//
|
|
// bstring bstring::operator + ( const char *s )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : A pointer to a character string.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A bstring that results from adding s to this. "Add" means concatenate.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This operator adds a character string to a bstring, creating a
|
|
// new bstring. This is very useful in a program like this, where
|
|
// we are adding strings together all the time. Note the assertion
|
|
// error that takes place if a memory allocation fails.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::operator + ( const char *s )
|
|
{
|
|
assert( s );
|
|
char *p = new char[ strlen( string ) + strlen( s ) + 1 ];
|
|
assert( p );
|
|
strcpy( p, string );
|
|
strcat( p, s );
|
|
bstring new_guy( p );
|
|
delete[] p;
|
|
return new_guy;
|
|
}
|
|
|
|
//
|
|
// bstring bstring::operator + ( const bstring &rhs )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// rhs : A bstring that is going to be added to this.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A bstring that results from adding s to this. "Add" means concatenate.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This operator adds a bstring to a bstring, creating a
|
|
// new bstring. This is very useful in a program like this, where
|
|
// we are adding strings together all the time. Note the assertion
|
|
// error that takes place if a memory allocation fails.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::operator + ( const bstring &rhs )
|
|
{
|
|
char *p = new char[ strlen( string ) + strlen( rhs ) + 1 ];
|
|
assert( p );
|
|
strcpy( p, string );
|
|
strcat( p, rhs );
|
|
bstring new_guy( p );
|
|
delete[] p;
|
|
return new_guy;
|
|
}
|
|
|
|
//
|
|
// int bstring::operator != ( const char *s ) const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : Pointer to a character string.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// True or false.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is a wonderful function that we use all the time. It lets
|
|
// us compare bstrings using the != operator. One major deal to note
|
|
// is that string comaprisons are case insensitive. If you don't know
|
|
// this things aren't going to work quite like you expect.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::operator != ( const char *s ) const
|
|
{
|
|
return stricmp( string, s );
|
|
}
|
|
|
|
//
|
|
// int bstring::operator == ( const char *s )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : Pointer to a character string.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// True or false.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is a another function that we use all the time. It lets
|
|
// us compare bstrings using the == operator. One major deal to note
|
|
// is that string comaprisons are case insensitive. If you don't know
|
|
// this things aren't going to work quite like you expect.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::operator == ( const char *s )
|
|
{
|
|
return !stricmp( string, s );
|
|
}
|
|
|
|
//
|
|
// int bstring::operator == ( const bstring &rhs )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// rhs : Reference to the bstring argument being compared to this.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// True or false.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is a another function that we use all the time. It lets
|
|
// us compare bstrings using the == operator. One major deal to note
|
|
// is that string comaprisons are case insensitive. If you don't know
|
|
// this things aren't going to work quite like you expect.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::operator == ( const bstring& rhs )
|
|
{
|
|
return !stricmp( string, rhs );
|
|
}
|
|
|
|
|
|
//
|
|
// int bstring::first( char c )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// c : The character to search for.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// An index, ranging from 0 to ( strlen( this ) -1 ) if the character
|
|
// is found, or -1 if it isn't found.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function finds the first appearance of character c in bstring this.
|
|
// It returns the index of the character.
|
|
//
|
|
// One really bad deal here is that this search *is* case sensitive, which
|
|
// really differs from the rest of the library.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::first( char c )
|
|
{
|
|
char *p = strchr( string, c );
|
|
if ( p == 0 )
|
|
return -1;
|
|
else
|
|
return (int) ( p - string );
|
|
}
|
|
|
|
//
|
|
// int bstring::first( const char *s )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : The string to search for.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// An index, ranging from 0 to ( strlen( this ) -1 ) if the substring
|
|
// is found, or -1 if it isn't found.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function finds the first appearance of substring s in bstring this.
|
|
// It returns the index of the first character of the substring.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
// January 26, 1995 4.0A : Modified so this guy would be case insensitive.
|
|
// This routine could use some work!
|
|
//
|
|
|
|
int bstring::first( const char *s )
|
|
{
|
|
char *x = strdup( string );
|
|
char *y = strdup( s );
|
|
assert( x );
|
|
assert( y );
|
|
for ( int i = 0 ; i < strlen( x ) ; i++ )
|
|
x[ i ] = toupper( x[ i ] );
|
|
for ( i = 0 ; i < strlen( y ) ; i++ )
|
|
y[ i ] = toupper( y[ i ] );
|
|
char *p = strstr( x, y );
|
|
free( x );
|
|
free( y );
|
|
if ( p == 0 )
|
|
return -1;
|
|
else
|
|
return (int) ( p - x );
|
|
}
|
|
|
|
//
|
|
// int bstring::last( char c )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// c : The character to search for.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// An index, ranging from 0 to ( strlen( this ) -1 ) if the character
|
|
// is found, or -1 if it isn't found.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function finds the last appearance of character c in bstring this.
|
|
// It returns the index of the character.
|
|
//
|
|
// One really bad deal here is that this search *is* case sensitive, which
|
|
// really differs from the rest of the library.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::last( char c )
|
|
{
|
|
char *p = strrchr( string, c );
|
|
if ( p == 0 )
|
|
return -1;
|
|
else
|
|
return (int) ( p - string );
|
|
|
|
}
|
|
|
|
//
|
|
// int bstring::remove_after( int index )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// index : The point in the string which will now be the last
|
|
// character.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if the index is no good, otherwise 1.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function lets you chop off the end of a bstring. It deletes
|
|
// everything after a specific index.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::remove_after( int index )
|
|
{
|
|
if ( index >= (int) strlen( string ) )
|
|
return 0;
|
|
if ( index < -1 )
|
|
return 0;
|
|
string[ index + 1 ] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int bstring::remove_before( int index )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// index : The point in the string which will now be the first
|
|
// character.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// 0 if the index is no good, otherwise 1.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function lets you chop off the start of a bstring. It deletes
|
|
// everything before a specific index.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::remove_before( int index )
|
|
{
|
|
if ( index > (int) strlen( string ) )
|
|
return 0;
|
|
if ( index < 0 )
|
|
return 0;
|
|
char *dest = string;
|
|
char *source = string + index;
|
|
while ( ( *dest++ = *source++ ) != '\0' )
|
|
;
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// int bstring::operator[]( int index ) const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// index : This indexing operator lets you safely get
|
|
// a character out of a bstring. You can pass a no
|
|
// good index and this function will deal with it.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The character, or a -1 if the index is no good.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This indexing operator returns a character from the bstring. If
|
|
// for some reason the index is no good, it returns a -1.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::operator[]( int index ) const
|
|
{
|
|
if ( index < 0 )
|
|
return 0;
|
|
if ( index >= (int) strlen( string ) )
|
|
return 0;
|
|
return string[ index ];
|
|
}
|
|
|
|
//
|
|
// int bstring::index_from_end( int index ) const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// index : The index into the string.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Either the character from the string, or a -1 if the index is no good.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Sometimes I want to get a character that is a certain distance from the
|
|
// end of the string. This is just like the [] operator, except it
|
|
// operates in revers.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
int bstring::index_from_end( int index ) const
|
|
{
|
|
index = strlen( string ) - index;
|
|
if ( index < 0 )
|
|
return 0;
|
|
if ( index >= (int) strlen( string ) )
|
|
return 0;
|
|
return string[ index ];
|
|
}
|
|
|
|
//
|
|
// bstring bstring::drive() const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The drive component of a file name. If the drive component
|
|
// is not present in the file name, and empty string is returned.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of four bstring functions that help to support
|
|
// separation of filenames into components. If the filename has
|
|
// a drive component, this guy will strip it out, put it in a
|
|
// new string, and return it.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::drive() const
|
|
{
|
|
bstring drive_name = *this;
|
|
if ( drive_name != "" ) {
|
|
int i = drive_name.first( ':' );
|
|
if ( i == -1 )
|
|
drive_name = "";
|
|
else
|
|
drive_name.remove_after( i );
|
|
}
|
|
return drive_name;
|
|
}
|
|
|
|
//
|
|
// bstring bstring::path() const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The path component of a file name. If the path component
|
|
// is not present in the file name, we return sort of a default
|
|
// path, "..\\";
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of four bstring functions that help to support
|
|
// separation of filenames into components. This function sort
|
|
// of tries to second guess the best thing to return for the path
|
|
// component of a file name. I'm not sure this is as good as it
|
|
// should be, but it works for BUILD.CPP.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::path() const
|
|
{
|
|
bstring path = *this;
|
|
int i = path.first( ':' );
|
|
if ( i != -1 )
|
|
path.remove_before( i + 1 );
|
|
//
|
|
// Now we might have :
|
|
// \\comp1\\comp2\\name which should return "\\comp1\\comp2\\"
|
|
// name which should return ".\\"
|
|
// \\name return which should return "\\"
|
|
// \\name\\ which should return "\\name""
|
|
// comp1\\comp2\\name which should return "comp1\\comp2\\"
|
|
//
|
|
i = path.last( '\\' );
|
|
if ( i != -1 ) {
|
|
path.remove_after( i );
|
|
if ( path.first( '\\' ) == 0 )
|
|
path = bstring( ".\\" ) + path;
|
|
return path;
|
|
}
|
|
//
|
|
// Now we might have :
|
|
// name which should return ".\\"
|
|
//
|
|
return path = ".\\";
|
|
}
|
|
|
|
//
|
|
// bstring bstring::name() const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The name component of a file name. Note that in an 8.3 system,
|
|
// this function returns the 8, but no decimal point. The point
|
|
// gets returned with the extension.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of four bstring functions that help to support
|
|
// separation of filenames into components. This function returns
|
|
// the name portion of the file.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::name() const
|
|
{
|
|
bstring name = *this;
|
|
int i = name.last( '\\' );
|
|
if ( i >= 0 )
|
|
name.remove_before( i + 1 );
|
|
else {
|
|
i = name.first( ':' );
|
|
if ( i >= 0 )
|
|
name.remove_before( i + 1 );
|
|
}
|
|
i = name.first( '.' );
|
|
if ( i >= 0 )
|
|
name.remove_after( i - 1 );
|
|
return name;
|
|
}
|
|
|
|
//
|
|
// bstring bstring::ext() const
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// The extension component of a file name. Note that in an 8.3 system,
|
|
// this function returns the 3 letter extension, along with the
|
|
// decimal point. A lot of times you will want to strip that
|
|
// decimal point. In fact, I think I just about always strip it.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is one of four bstring functions that help to support
|
|
// separation of filenames into components. This function returns
|
|
// the extension portion of the file.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::ext() const
|
|
{
|
|
bstring ext = *this;
|
|
int i = ext.last( '.' );
|
|
if ( i >= 0 )
|
|
ext.remove_before( i );
|
|
else
|
|
ext = ".";
|
|
return ext;
|
|
}
|
|
|
|
//
|
|
// bstring bstring::get_first_token( const char *s = ", " )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : A string containing all the token delimiters.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A string containing the first token from the string. If there
|
|
// is no token you get an empty string.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The get_first_token() / get_next_token() interface is really useful.
|
|
// It lets you suck tokens out of a string, using an arbitrary set of
|
|
// delimiters. Usually we are just looking for tokens separated by
|
|
// spaces, but when parsing the dos PATH, we look for tokens separated
|
|
// by semicolons.
|
|
//
|
|
// To make this work properly, the guy makes a little copy of the string,
|
|
// and mangles it as we go along. When we are done with it it gets
|
|
// deleted. It might be just as easy to just use a pointer into the
|
|
// string, but this is what I ended up with. It works.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::get_first_token( const char *s /* = " ," */ )
|
|
{
|
|
if ( tokenizer )
|
|
delete[] tokenizer;
|
|
tokenizer = new char[ strlen( string ) + 1 ];
|
|
assert( tokenizer );
|
|
strcpy( tokenizer, string );
|
|
return get_next_token(s );
|
|
}
|
|
|
|
//
|
|
// bstring bstring::get_next_token( const char *delimiters = " ," )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// s : A string containing all the token delimiters.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A string containing the next token from the string. If there
|
|
// is no token you get an empty string.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The get_first_token() / get_next_token() interface is really useful.
|
|
// It lets you suck tokens out of a string, using an arbitrary set of
|
|
// delimiters. Usually we are just looking for tokens separated by
|
|
// spaces, but when parsing the dos PATH, we look for tokens separated
|
|
// by semicolons.
|
|
//
|
|
// To make this work properly, the guy makes a little copy of the string,
|
|
// and mangles it as we go along. When we are done with it it gets
|
|
// deleted. It might be just as easy to just use a pointer into the
|
|
// string, but this is what I ended up with. It works.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring bstring::get_next_token( const char *delimiters /* = " ," */ )
|
|
{
|
|
assert( tokenizer );
|
|
char *temp_src = tokenizer + strspn( tokenizer, delimiters );
|
|
for ( char *temp_dest = tokenizer ;
|
|
*temp_src != '\0';
|
|
*temp_dest++ = *temp_src++ )
|
|
;
|
|
*temp_dest = '\0';
|
|
if ( *tokenizer == '\0' ) {
|
|
delete[] tokenizer;
|
|
tokenizer = 0;
|
|
return "";
|
|
}
|
|
temp_src = tokenizer + strcspn( tokenizer, delimiters );
|
|
char temp = *temp_src;
|
|
*temp_src = '\0';
|
|
bstring result = tokenizer;
|
|
*temp_src = temp;
|
|
for ( temp_dest = tokenizer;
|
|
*temp_src != '\0';
|
|
*temp_dest++ = *temp_src++ )
|
|
;
|
|
*temp_dest = '\0';
|
|
return result;
|
|
}
|
|
|
|
//
|
|
// options::options( char *first, ... )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// first : The name of the first option
|
|
//
|
|
// ... : A list of character strings containing the names of
|
|
// any other options we expect to find. The list needs
|
|
// to be terminated with a NULL pointer or an empty string.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Constructor, no returns
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The options class holds all the options we need to use in order
|
|
// to build a library. The bulk of the information in the options
|
|
// structure consists of tag names and values that are read out of
|
|
// BUILD.INI. A line in BUILD.INI looks like this:
|
|
//
|
|
// tag=value
|
|
//
|
|
// This implementation of the options class inists that we define all
|
|
// the tag names in advance, here in the constructor. It would be
|
|
// easy to change the class so that new names encountered in BUILD.INI
|
|
// could be added dynamically, but I think it would then be harder to
|
|
// track down typos in the INI file, don't you.
|
|
//
|
|
// This constructor is in charge of setting up the tag items with blank
|
|
// values. When BUILD.EXE is busy processing the files, it will assigne
|
|
// the values to each of the tags as it encounters them.
|
|
//
|
|
// There are two additional data members in options that don't get set up
|
|
// here (although they probably should). One is the memory model read in
|
|
// from the command line. The other is the dry_run member, used to
|
|
// tell the program to go through the motions of building a library,
|
|
// but not to really do it.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 20, 1994 2.0A : First release
|
|
//
|
|
|
|
options::options( char *first, ... )
|
|
{
|
|
va_list ap;
|
|
char *arg;
|
|
|
|
va_start( ap, first );
|
|
first_item = 0;
|
|
arg = first;
|
|
while ( arg != 0 && strlen( arg ) > 0 ) {
|
|
item *new_item = new item( arg );
|
|
assert( new_item );
|
|
new_item->next_item = first_item;
|
|
first_item = new_item;
|
|
arg = va_arg( ap, char * );
|
|
}
|
|
}
|
|
|
|
//
|
|
// void options::add_option( const bstring &name, const bstring &value )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// name : The name of the new option to be added.
|
|
//
|
|
// value : The initial value of the new options.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The deal used to be that you didn't get to add any new options to
|
|
// the opts structure. The available options were defined by the ctor,
|
|
// and if you had something new, that was tough luck.
|
|
//
|
|
// In 4.0, I added the ability to create as man CMD files as you wanted
|
|
// to create. I had to have a way to keep track of the CMD files without
|
|
// predefining them, so I bit the bullet and changed the behavior of options.
|
|
// Now you can add new keys to the options any time you want.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release.
|
|
//
|
|
|
|
void options::add_option( const bstring &name, const bstring &value )
|
|
{
|
|
item *new_item = new item( (const char *) name );
|
|
assert( new_item );
|
|
new_item->next_item = first_item;
|
|
first_item = new_item;
|
|
new_item->value = value;
|
|
}
|
|
|
|
//
|
|
// bstring *options::find_value( char const *tag )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// tag : The name of the tag whose value we want.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A bstring pointer, which points to the actual tag in the list opt option
|
|
// items. If the tag could not be found, it returns a null pointer.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is the function I use to look up a tag value. It returns a bstring
|
|
// directly, so it is really easy to use. It might be even better to try
|
|
// to overload the [] operator with a string, but I don't know if you can
|
|
// actually do that.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring *options::find_value( char const *tag )
|
|
{
|
|
item *current_item;
|
|
for ( current_item = first_item ; ; current_item = current_item->next_item ) {
|
|
if ( current_item == 0 )
|
|
return 0;
|
|
if ( current_item->name != "" && current_item->name == tag )
|
|
return ¤t_item->value;
|
|
}
|
|
}
|
|
|
|
//
|
|
// item *options::find_item( char const *tag )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// tag : The name of the tag we are searching for.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// An item pointer, which points to the actual item in the option object.
|
|
// If the tag could not be found, it returns a null pointer.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// I need to be able to look up the item pointer for a given keyword so
|
|
// I can modify the run type from ONE_AT_A_TIME to BATCH.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release.
|
|
//
|
|
|
|
item *options::find_item( char const *tag )
|
|
{
|
|
item *current_item;
|
|
for ( current_item = first_item ; ; current_item = current_item->next_item ) {
|
|
if ( current_item == 0 )
|
|
return 0;
|
|
if ( current_item->name != "" && current_item->name == tag )
|
|
return current_item;
|
|
}
|
|
}
|
|
|
|
//
|
|
// options::~options()
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// The destructor deletes all of the items so there aren't any memory leaks.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release
|
|
//
|
|
|
|
options::~options()
|
|
{
|
|
item *current_item;
|
|
item *next_item;
|
|
current_item = first_item;
|
|
for ( ; ; ) {
|
|
if ( current_item == 0 )
|
|
break;
|
|
next_item = current_item->next_item;
|
|
delete current_item;
|
|
current_item = next_item;
|
|
}
|
|
}
|
|
|
|
//
|
|
// void options::erase_values()
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This function goes through the options list and sets each tag items'
|
|
// value to the empty string. I do this when I am scanning the INI
|
|
// file in dump_sections(). Normally, when you read in an item for a
|
|
// second time, you would generate an error.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 20, 1994 2.0A : First release
|
|
//
|
|
|
|
void options::erase_values()
|
|
{
|
|
item *current_item;
|
|
for ( current_item = first_item ; ; current_item = current_item->next_item ) {
|
|
if ( current_item == 0 )
|
|
return;
|
|
current_item->value = "";
|
|
}
|
|
}
|
|
|
|
//
|
|
// bstring *file_list::get_first_file()
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// A pointer to a bstring. When there are no more files to return,
|
|
// we return a null pointer.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// I keep all of the files I am going to compile in a file_list object.
|
|
// When iterating through the list, I use the get_first_file()
|
|
// get_next_file() functions. This is the get_first_file(). Note that
|
|
// I keep track of where I am in the list using the internal protected
|
|
// member current_item. This value gets set up here for the first
|
|
// time so that subsequent calls to get_next_file() will know where
|
|
// to continue the search.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring *file_list::get_first_file()
|
|
{
|
|
current_item = first_file;
|
|
if ( current_item )
|
|
return ¤t_item->file_name;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// file_list::~file_list()
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// I added the destructor so I could clean up after myself an not have
|
|
// any memory leaks.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// January 31, 1995 4.0B : First release.
|
|
//
|
|
|
|
file_list::~file_list()
|
|
{
|
|
file_name_item *current_file;
|
|
file_name_item *next_file;
|
|
current_file = first_file;
|
|
for ( ; ; ) {
|
|
if ( current_file == 0 )
|
|
break;
|
|
next_file = current_file->next_item;
|
|
delete current_file;
|
|
current_file = next_file;
|
|
}
|
|
}
|
|
|
|
//
|
|
// bstring *file_list::get_next_file()
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// None.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// If another file is available in the list, we return a pointer to
|
|
// it. If we reached the end of the list, we return a null pointer.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This is the companion function to get_first_file().
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 20, 1994 2.0A : First release
|
|
//
|
|
|
|
bstring *file_list::get_next_file()
|
|
{
|
|
if ( current_item )
|
|
current_item = current_item->next_item;
|
|
if ( current_item )
|
|
return ¤t_item->file_name;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// void file_list::add_file( const char *name )
|
|
//
|
|
//
|
|
// ARGUMENTS:
|
|
//
|
|
// name : The name of the file to add to the list.
|
|
//
|
|
// RETURNS
|
|
//
|
|
// Nothing.
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// During the startup of this program, we add files to the file list.
|
|
// This is the function that does it. It creates a new item, and
|
|
// assigns it the appropriate name. Note that any memory allocation
|
|
// failure causes an assertion error and the whole deal goes to sleep.
|
|
//
|
|
// If I had bothered to put an end_of_list pointer in the file_list
|
|
// class, this function would be a lot more efficient, at a very small
|
|
// price. Oh well.
|
|
//
|
|
// REVISION HISTORY
|
|
//
|
|
// May 19, 1994 2.0A : First release
|
|
//
|
|
|
|
void file_list::add_file( const char *name )
|
|
{
|
|
//
|
|
// Let's add the new guy to the end of the list!
|
|
//
|
|
file_name_item *new_guy = new file_name_item( name );
|
|
assert( new_guy );
|
|
file_name_item **list = &first_file;
|
|
while ( *list != 0 )
|
|
list = &(*list)->next_item;
|
|
*list = new_guy;
|
|
new_guy->next_item = 0;
|
|
}
|