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