#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 #include #include #include #include #include #include #include #include #include #include #include #if defined( __WATCOMC__ ) #include // Had to get unlink from here... #endif #include #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 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; }