campo-sirio/al/misc/build.cpp
alex 714dd74636 Archive Library versione 2.00
git-svn-id: svn://10.65.10.50/trunk@5350 c028cbd2-c16b-5b4b-a496-9718f37d4682
1997-10-09 16:09:54 +00:00

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 &section, 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 &section, 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 &current_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 &current_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 &current_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;
}