/* ImproveCascade.cpp Copyright 2000, 2003 by Conrad J. Poelman. http://www.stellarscience.com You are free to copy, modify, and distribute this file as long as you retain the above copyright notice. I'd appreciate receiving copies of any improvements that you make to the code so I can provide everyone with the latest version. CREATED BY: Conrad J. Poelman, Stellar Science Ltd Co, Albuquerque NM CREATED ON: 8 Dec 2000 LAST UPDATE: 11 Apr 2003 OVERVIEW: This program performs some simple parsing and modification of the Open Cascade source tree. This must be run after downloading and extracting the source code but before compiling it. The improvements that it provides at this time are: - reorganizes the files in the massive 'inc' directory into subdirectories - updates the code to use ANSI C++ standard streams, strings, and io - modifies the source code to use a precompiled header file Each improvement can be controlled individually via command-line arguments. BACKGROUND: I wondered why my Open Cascade programs took so long to compile. Eventually I realized that the bulk of the the compile time was spend looking for header files - not compiling them, mind you, but just *looking* for them! This is because Open Cascade puts all of the header files in one huge directory, over 11,000 files! Every time the compiler finds a #include statement, it has to search through that gigantic directory! The huge inc directoy causes other problems too. My virus scanner takes forever just looking at that one massive directory full of files. So I got the idea for this program (after floundering with bash & sed scripts for a while.) Open Cascade follows a fairly nice naming convention using underscores ('_') as separators in its file names. I decided to just replace the underscores in the header file names with slashes ('/' or '\') so that instead of one huge directory, the files would be organized into subdirectories and could be located faster. In 2003, I found that Open Cascade would not compile with the MSVC .NET 2003 compiler, because they no longer support old-style iostreams and strings. So I extended this code to make the appropriate header file and "std::" substitutions to the Open Cascade source code. That's when I also tried out the precompiled header business, but it doesn't seem to improve compilation speeds at all. RESULTS: It works. Open Cascade compiles significantly faster now, and works with MSVC .NET 2003! Should work for other standards-compliant compilers as well. CAVEATS: This file has only been tested using the Microsoft Visual C++ compiler. I started out using only standard C to make it as portable as possible, but at this point some C++ has probably crept in. I have added some #ifdefs that should make it pretty close to compiling on UNIX, but I have not tested it so it will probably take a bit of debugging. */ #include #include #include #include #define MAX_PATH 1024 /* Max characters in a file name - should be in limits.h??? */ #define MAX_LINE 1024 /* Maximum line length in any file we will be processing */ #ifdef _MSC_VER /* Windows */ # include /* Windows, for _findfirst, _findnext, _findclose */ # include /* Windows, for _mkdir */ #else /* UNIX */ # include /* UNIX, untested, for mkdir */ # include /* UNIX, untested, for DIRENT */ #include /* UNIX, untested, for opendir, readdir, closedir */ #endif /* The directory separator character to use on the system. */ #ifdef _MSC_VER const char sysDirSep = '\\'; /* Windows prefers backslashes between directories */ #else const char sysDirSep = '/'; /* UNIX requires forward slashes between directories */ #endif; /* The directory separator character to use in C/C++ source files. for use with #include statements. */ const char cDirSep = '/'; /* Characters to treat as white space in C++ code.*/ const char* cSeparatorChars = " \t!@#$%^&*()-+={}|[p]\\\'\";:<>,.?\n"; /* Make a directory, printing an error and exiting on failure. */ void make_dir( const char* new_dir ) { int status = #ifdef _MSC_VER _mkdir( new_dir ); /* Windows */ #else mkdir( new_dir, 0x775 ); /* UNIX, untested */ #endif if ( status < 0 && errno != EEXIST ) { perror( "Failed to create output directory!\n" ); fprintf( stderr, "(Directory name was %s)\n", new_dir ); exit( status ); } } struct Settings { bool reorg_inc_dir; bool use_std; bool use_pch; Settings( bool in_reorg_inc_dir, bool in_use_std, bool in_use_pch ) : reorg_inc_dir( in_reorg_inc_dir ), use_std( in_use_std ), use_pch( in_use_pch ) {}; }; /* A few simple functions to make the code easier to understand... */ /* Check whether two strings are exactly identical. */ bool string_equals( const char* c1, const char* c2 ) { return ( strcmp( c1, c2 ) == 0 ); } /* Returns true of c1 contains substring anywhere in the string. */ bool string_contains( const char* c1, const char* substring ) { return ( strstr( c1, substring ) != 0 ); } /* Returns true if the c1 and c2 are identical up to the first strlen(c2) characters. */ bool front_of_string_equals( const char* c1, const char* c2 ) { return ( strncmp( c1, c2, strlen( c2 ) ) == 0 ); } /* Returns true if the given includeFile should be reorganized into subdirectories. It is fine for includeFile to contain some leading or trailing junk like " or >. */ bool should_reorg_path( const char* includeFile ) { return !( /*============================ SPECIAL HACKS ============================*/ /* Special Hacks - WNT is preprocessor symbol so wont work! */ string_contains( includeFile, "WNT" ) || /* Strange, MFT is #define'd as a null macro in WNT_DDriver.cxx, yet has an actual C++ class definition in MFT.hxx. How do they keep them straight?!?! Anyway, the #define'd definition causes problems with */ string_contains( includeFile, "MFT" ) || /* Preprocessor macro names #defined in BlendTool.hxx, would cause same problems */ string_contains( includeFile, "HCurve2d" ) || string_contains( includeFile, "HSurface" ) || string_contains( includeFile, "HCurve2dTool" ) || string_contains( includeFile, "HSurfaceTool" ) || string_contains( includeFile, "HVertex" ) /*========================== END SPECIAL HACKS ==========================*/ ); } /* Pos points to a discovered instance of replace_token in buf. Returns true if we should replace the text at pos in inFile with its suggested replacement string. */ bool should_replace_string( const char* inFile, const char* buf, const char* pos, const char* replace_token, int replace_token_len ) { /* We have a match, make sure it's preceeded and followed by whitespace */ bool white_before( pos == buf || strchr( cSeparatorChars, pos[-1] ) != 0 ); bool white_after( pos[ replace_token_len ] == 0 || strchr( cSeparatorChars, pos[ replace_token_len ] ) != 0 ); return ( white_before && white_after && /*============================ SPECIAL HACKS ============================*/ /* Don't replace someStream.flush() with someStream.std::flush()!*/ !( string_equals( replace_token, "flush" ) && pos != buf && pos[-1] == '.' ) && /* Don't replace local variable "dec" with std::dec! Look for << somewhere on line as well.*/ !( string_equals( replace_token, "dec" ) && string_contains( buf, "<<" ) == 0 ) && /* inc/Data_f2c.h contains a struct with a local variable named "cerr" - evidently variable is never used???*/ !( string_equals( replace_token, "cerr" ) && string_contains( inFile, "f2c.h" ) ) /*========================== END SPECIAL HACKS ==========================*/ ); } /* Translate the input file to the output file, making the changes needed to allow for precompiled headers, modify #include lines to reorganize the "inc" include directory, or switch to ANSI C++ standard iostreams and strings, depending on the options specified by settings. */ void translate_file( const char* inFile, const char* outFile, Settings& settings ) { static const char* replace_tokens[] = { "iostream.h", "iostream", "istream.h", "istream", "ostream.h", "ostream", "iomanip.h", "iomanip", "fstream.h", "fstream", "strstrea.h", "strstream", "ostream", "std::ostream", "istream", "std::istream", "ofstream", "std::ofstream", "ifstream", "std::ifstream", "fstream", "std::fstream", "strstream", "std::strstream", "ostrstream", "std::ostrstream", "cout = &ff", "std::cout.rdbuf( &ff )", /* std::ostream has no operator=( filebuf ) */ "cin = &ff2", "std::cin.rdbuf( &ff2 )", /* std::istream has no operator=( filebuf ) */ "cout", "std::cout", "cerr", "std::cerr", "cin", "std::cin", "endl", "std::endl", "ends", "std::ends", "hex", "std::hex", "dec", "std::dec", "setw", "std::setw", "ios", "std::ios", "flush", "std::flush", "streambuf", "std::streambuf", "setprecision", "std::setprecision", "filebuf ff", "boost::fdoutbuf ff", /* no std::filebuf( fd ) constructor.*/ "filebuf ff2", "boost::fdinbuf ff2", /* no std::filebuf( fd ) constructor.*/ "filebuf", "std::filebuf", /* "cout = &ff", "cin = &ff2", and "filebuf ff" changes above are needed in: - AISViewer.cxx - src/TTOPOLOGY/TTOPOLOGY.cxx - src/XSEXEMain/XSEXEMain.cxx - src/TCAF/TCAF.cxx where there was logic to redirect cout and stdout to a file that was opened using a method that returns a simple integer file descriptor. Older non-std iostreams used to support that directly but support was dropped from the newer std versions of those libraries, so we use a free implementation downloaded from boost. */ /*============================ SPECIAL HACKS ============================*/ /* These changes only apply to one or two instances of code in Open */ /* Cascade that need to be changed to work with std:: stuff. These may */ /* stop working or become insufficient in the future, but they should be */ /* easy to fix manually if these don't work. */ /* For Standard_TypeDef.hxx - std::ostream is now a typedef, not a class!*/ "class Standard_OStream;", "#ifndef _Standard_OStream_HeaderFile\n" /*TODO: "/" or "_" depending on reorg_inc_dir*/ "#include \n" "#endif", /* For math_Memory.hxx - custom memmove clashes with the standard one */ "inline void *memmove", "inline void *memmove_dontuse", /*========================== END SPECIAL HACKS ==========================*/ /* Marks end of list */ 0 }; static int replace_tokens_len[256]; int i; FILE* fin; FILE* fout; char buf[MAX_LINE+1]; fin = fopen( inFile, "r" ); if ( !fin ) { perror( "Failed to open input file!\n" ); exit( -1 ); } fout = fopen( outFile, "w" ); if ( !fout ) { perror( "Failed to open output file!\n" ); exit( -1 ); } /* Calculate length of replace_tokens to save time later */ for ( i = 0; replace_tokens[i]; i++ ) { replace_tokens_len[i] = strlen( replace_tokens[i] ); } /* Use precompiled header if requested */ if ( settings.use_pch ) { fprintf( fout, "/* Use precompiled header file automatically generated by ImproveCascade.\n" " Don't do anything (#include, #define, or place any code) before this point.*/\n" "\n" "#include \n" "\n" ); } if ( settings.use_std && ( string_contains( inFile, "AISViewer.cxx" ) || string_contains( inFile, "TTOPOLOGY.cxx" ) || string_contains( inFile, "XSEXEMain.cxx" ) || string_contains( inFile, "Draw_Main.cxx" ) || string_contains( inFile, "TCAF.cxx" ) ) ) { /* Add required header file for boost::fdoutbuf class used by those files. */ fputs( "#include /* Get from http://www.josuttis.com/cppcode/ */", fout ); } while ( fgets( buf, sizeof( buf ), fin ) ) { char* nwpos = buf; /* First non-whitespace character */ /* Advance past all leading whitespace */ for ( ; *nwpos && ( *nwpos == ' ' || *nwpos == '\t' ); nwpos++ ); if ( settings.reorg_inc_dir && *nwpos == '#' ) { /* We have a preprocessor directive! */ char* pos = nwpos + 1; /* Advance past all whitespace immediately after '#' (usually none!) */ for ( ; *pos && ( *pos == ' ' || *pos == '\t' ); pos++ ); if ( front_of_string_equals( pos, "include" ) ) { /* For #include lines, in the section between the '<' and the '>', replace all '_' with '/' */ for ( ; *pos != 0 && *pos != '<' && *pos != '"'; pos++ ); char* included_start = pos; pos++; /* Get past initial < or " */ if ( should_reorg_path( pos ) && ( string_contains( pos, ".hxx" ) || string_contains( pos, ".h" ) || string_contains( pos, ".gxx" ) || string_contains( pos, ".lxx" ) ) ) { for ( pos++ ; *pos != 0 && *pos != '>' && *pos != '"'; pos++ ) { if ( *pos == '_' ) *pos = cDirSep; } } } else if ( front_of_string_equals( pos, "define" ) ) { /* #defines are harder. First advance to the first hxx, or to the end of the line if there is no hxx. */ char* hxxpos = pos+7; for ( ; *hxxpos && !front_of_string_equals( hxxpos, "hxx" ); hxxpos++ ); /* Now replace all '_' with '/' from here on */ if ( should_reorg_path( hxxpos ) ) { for ( pos = hxxpos; *pos; pos++ ) { if ( *pos == '_' ) *pos = cDirSep; } } } } /* Do the replacements necessary for using std:: versions of libraries */ if ( settings.use_std ) { char* pos = nwpos; for ( ; *pos != 0; pos++ ) { int i; for ( i = 0; replace_tokens[i]; i += 2 ) { if ( strncmp( pos, replace_tokens[i], replace_tokens_len[i] ) == 0 && should_replace_string( inFile, buf, pos, replace_tokens[i], replace_tokens_len[i] ) ) { /* shift characters at pos+oldlen over to leave newlen spaces */ int oldlen = replace_tokens_len[i]; int newlen = replace_tokens_len[i+1]; int movelen = strlen( pos+oldlen )+1; memmove( pos+newlen, pos+oldlen, movelen ); /* insert std::, not followed by trailing 0! */ strncpy( pos, replace_tokens[i+1], newlen ); /* skip the string so we don't replace it again! */ pos += newlen-1; /* Stop searching for tokens and move to next position */ break; } } } } fputs( buf, fout ); } fclose( fin ); fclose( fout ); } /* Forward declaration needed since these two functions call each other. */ int translate_directory( const char* inDir, const char* outDir, bool is_inc_dir, bool recurse, Settings& settings ); void translate_directory_entry( const char* inDir, const char* outDir, bool is_inc_dir, bool recurse, Settings& settings, const char* entry_name, bool entry_is_directory ) { if ( string_equals( entry_name, "." ) || string_equals( entry_name, ".." ) ) { return; } const char* ext = strrchr( entry_name, '.' ); char inFile[MAX_PATH+1]; char outFile[MAX_PATH+1]; sprintf( inFile, "%s%c%s", inDir, sysDirSep, entry_name ); sprintf( outFile, "%s%c%s", outDir, sysDirSep, entry_name ); char* pos = outFile+strlen(outDir); if ( settings.reorg_inc_dir && should_reorg_path( pos ) && is_inc_dir ) { for ( ; *pos; pos++ ) { if ( *pos == '_' ) { /* Clever hack? Temporarily add a null separator in place of the slash and call mkdir to ensure that the directory exists before we proceed any further!*/ *pos = 0; make_dir( outFile ); /* Now make the '_' into a slash */ *pos = sysDirSep; } } } if ( entry_is_directory ) { /* outFile is the path to the subdirectory.*/ if ( recurse ) { make_dir( outFile ); printf("Recursing into directory %s...\n", inFile ); /* Call this function recursively for the new directory. */ translate_directory( inFile, outFile, is_inc_dir, recurse, settings ); } } else if ( ext && ( string_equals( ext, ".hxx" ) || string_equals( ext, ".ixx" ) || string_equals( ext, ".jxx" ) || string_equals( ext, ".gxx" ) || string_equals( ext, ".lxx" ) || string_equals( ext, ".pxx" ) ) ) { /* C++ header files should be copied to new directory, translating inc and std stuff, don't use pch */ translate_file( inFile, outFile, Settings( settings.reorg_inc_dir, settings.use_std, false ) ); } else if ( ext && string_equals( ext, ".h" ) ) { /* C files should be copied to new directory, translating inc but not std stuff, don't use pch */ translate_file( inFile, outFile, Settings( settings.reorg_inc_dir, settings.use_std /*false*/, false ) ); } else if ( ext && string_equals( ext, ".cxx" ) ) { /* C++ implementation files should be copied to new directory */ translate_file( inFile, outFile, settings ); } else if ( ext && string_equals( ext, ".c" ) ) { /* C implementation files should be copied to new directory, translating inc but not std stuff or pch */ translate_file( inFile, outFile, Settings( settings.reorg_inc_dir, false, false ) ); } else { /* Just do a line-by-line copy of old file into new directory? */ translate_file( inFile, outFile, Settings( false, false, false ) ); } } /* Looks for all files in the current directory matching the wildcard string "match." For each file, translates the file to some file in outDir by calling the "translate" function above. If translate_filename is specified, it also translates each matching filename itself by converting each '_' in the filename to a '/' and creating the the necessary intervening directories; otherwise the output filename is the same as the input filename. If recurse is specified, then any directories found in the current directory are also explored and the function is also called on the subdirectories. outDir must already exist! */ int translate_directory( const char* inDir, const char* outDir, bool is_inc_dir, bool recurse, Settings& settings ) { int fileCount = 0; #ifdef _MSC_VER /* Windows implementation */ struct _finddata_t c_file; long hFile; char match[MAX_PATH+1]; sprintf( match, "%s%c*", inDir, sysDirSep ); /* Find first .c file in current directory */ if( (hFile = _findfirst( match, &c_file )) == -1L ) { fprintf( stderr, "Warning: No files found in directory %s!\n", inDir ); } else { do { fileCount++; translate_directory_entry( inDir, outDir, is_inc_dir, recurse, settings, c_file.name, (c_file.attrib & _A_SUBDIR) != 0 ); } while ( _findnext( hFile, &c_file ) == 0 ); _findclose( hFile ); } #else /* TODO: Finish, test, and fix this UNIX implementation. */ DIR *dirp; struct dirent *dp; if ((dirp = opendir(inDir)) == NULL) { fprintf( stderr, "Warning: No files found in directory %s!\n", inDir ); } else { do { fileCount++; if ((dp = readdir(dirp)) != NULL) { translate_directory_entry( inDir, outDir, is_inc_dir, recurse, settings, dp->d_name, ??? /* How can we tell if dp is a directory? try opendir() on it? */ ); } } while ( dp != NULL ); closedir(dirp); } #endif return fileCount; } void generate_pch_header( const char* inDir ) { char filename[MAX_PATH+1]; sprintf( filename, "%s%cOCC.hxx", inDir, sysDirSep ); FILE* fout = fopen( filename, "w" ); if ( !fout ) { perror( "Failed to open output file!\n" ); exit( -1 ); } /* TODO: Should check settings.reorg_inc_dir to determine whether to use "_" or "/" for precompiled header file. */ fprintf( fout, "/* Open Cascade precompiled header file generated by ImproveCascade. The goal\n" " is to speed up compilation by putting commonly used header files here.*/\n" "\n" "#include \n" "#include \n" "#include \n" "#include \n", "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "\n" ); fclose( fout ); } void generate_pch_source( const char* inDir ) { char filename[MAX_PATH+1]; sprintf( filename, "%s%cOCC.cpp", inDir, sysDirSep ); FILE* fout = fopen( filename, "w" ); if ( !fout ) { perror( "Failed to open output file!\n" ); exit( -1 ); } fprintf( fout, "/* This file is only needed when using explicit generation of the precompiled\n" " header file with certain settings in certain compilers.\n" " This file was automatically generated by ImproveCascade.\n" " Don't do anything (#include, #define, or place any code) before this point.*/\n" "\n" "#include \n" "\n" ); fclose( fout ); } int main( int argc, const char* argv[] ) { const char* usage = "Usage: %s [-s] [-i] [-p] cascade-root\n" " cascade-root is Open Cascade directory you wish to improve.\n" " Before running, in cascade-root rename inc to inc-old, src to src-old, drv to drv-old.\n" " -s converts Open Cascade to use C++ standard std::string, std::iostreams, etc.\n" " -i reorganizes the inc directory into subdirectories.\n" " -p sets up the code to use OCC.h as a precompiled header. [doesn't seem any faster...]\n"; const char* inRootDir = 0; const char* outRootDir = 0; char inDir[MAX_PATH+1], outDir[MAX_PATH+1]; int fileCount = 0; Settings settings( false, false, false ); int opt = 1; /* Check for command-line arguments. */ while ( opt < argc && argv[opt][0] == '-' ) { if ( argv[opt][1] == 's' ) { settings.use_std = true; } else if ( argv[opt][1] == 'i' ) { settings.reorg_inc_dir = true; } else if ( argv[opt][1] == 'p' ) { settings.use_pch = true; } else { fprintf( stderr, usage, argv[0] ); exit( -1 ); } opt++; } if ( opt == argc-1 ) { inRootDir = argv[opt]; outRootDir = argv[opt]; } else { fprintf( stderr, usage, argv[0] ); exit( -1 ); } /* Copy/modify inc directory into inc-new */ sprintf( inDir, "%s%cinc-old", inRootDir, sysDirSep ); sprintf( outDir, "%s%cinc", outRootDir, sysDirSep ); make_dir( outDir ); fileCount += translate_directory( inDir, outDir, true, false, settings ); if ( settings.use_pch ) { generate_pch_header( outDir ); } /* Copy/modify drv directory into drv-new */ sprintf( inDir, "%s%cdrv-old", inRootDir, sysDirSep ); sprintf( outDir, "%s%cdrv", outRootDir, sysDirSep ); make_dir( outDir ); fileCount += translate_directory( inDir, outDir, false, true, settings ); /* Copy/modify src directory into src-new */ sprintf( inDir, "%s%csrc-old", inRootDir, sysDirSep ); sprintf( outDir, "%s%csrc", outRootDir, sysDirSep ); make_dir( outDir ); fileCount += translate_directory( inDir, outDir, false, true, settings ); if ( settings.use_pch ) { generate_pch_source( outDir ); } return fileCount; }