config/makedep.cpp
author Gervase Markham <gerv@gerv.net>
Mon, 21 May 2012 12:12:37 +0100
changeset 98983 f4157e8c410708d76703f19e4dfb61859bfe32d8
parent 30478 43f2b453b68dcb0badba6e3e3ec420f8199c7ec0
permissions -rw-r--r--
Bug 716478 - update licence to MPL 2.

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Dependency building hack
//

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>
#include <afxcoll.h>
#include <afxtempl.h>

int mainReturn = 0;
BOOL b16 = FALSE;
BOOL bSimple = FALSE;

FILE *pAltFile = stdout;

CStringArray includeDirectories;

// turn a file, relative path or other into an absolute path
// This function copied from MFC 1.52
BOOL PASCAL _AfxFullPath(LPSTR lpszPathOut, LPCSTR lpszFileIn)
        // lpszPathOut = buffer of _MAX_PATH
        // lpszFileIn = file, relative path or absolute path
        // (both in ANSI character set)
{
        OFSTRUCT of;
        if (OpenFile(lpszFileIn, &of, OF_PARSE) != HFILE_ERROR)
        {
                // of.szPathName is in the OEM character set
                OemToAnsi(of.szPathName, lpszPathOut);
                AnsiUpper(lpszPathOut); // paths in upper case just to be sure
                return TRUE;
        }
        else
        {
                TRACE1("Warning: could not parse the path %Fs\n", lpszFileIn);
                lstrcpy(lpszPathOut, lpszFileIn);  // take it literally
                AnsiUpper(lpszPathOut); // paths in upper case just to be sure
                return FALSE;
        }
}

void AddIncludeDirectory( char *pString ){
    CString s = pString;
	int len = s.GetLength();
    if(len > 0 && s[len - 1] != '\\' ){
        s += "\\";
    }
    includeDirectories.Add(s);
}

BOOL FileExists( const char *pString ){
    struct _stat buf;
    int result;

    result = _stat( pString, &buf );
    return (result == 0);
}

void FixPathName(CString& str) {
	str.MakeUpper();		// all upper case

	// now switch all forward slashes to back slashes
	int index;
	while ((index = str.Find('/')) != -1) {
		str.SetAt(index, '\\');
	}
}

void FATName(CString& csLONGName)
{
    //  Only relevant for 16 bits.
    if(b16) {
        //  Convert filename to FAT (8.3) equivalent.
        char aBuffer[2048];

        if(GetShortPathName(csLONGName, aBuffer, 2048)) {
            csLONGName = aBuffer;
        }
    }
}


class CFileRecord {
public:
    CString m_shortName;
    CString m_pathName;
    CPtrArray m_includes;  // pointers to CFileRecords in fileMap
    BOOL m_bVisited;
    BOOL m_bSystem;
    BOOL m_bSource;
    static CMapStringToPtr fileMap;      // contains all allocated CFileRecords
    static CStringArray orderedFileNames;
    static CMapStringToPtr includeMap;   // pointer to CFileRecords in fileMap
    static CMapStringToPtr noDependMap;

    CFileRecord( const char *shortName, const char* pFullName, BOOL bSystem, BOOL bSource):
                m_shortName( shortName ),
                m_pathName(),
                m_includes(),
                m_bVisited(FALSE),
                m_bSource( bSource ),
                m_bSystem(bSystem){

		m_pathName = pFullName;
		FixPathName(m_pathName);				// all upper case for consistency
		ASSERT(FindFileRecord(m_pathName) == NULL);	// make sure it's not already in the map
        fileMap[m_pathName] = this;			// add this to the filemap, using the appropriate name as the key
		if (bSource) {
			orderedFileNames.Add(m_pathName);	// remember the order we saw source files in
		}
    }

    // 
    // open the file and grab all the includes.
    //
    void ProcessFile(){
        FILE *f;
		CString fullName;
        BOOL bSystem;
		DWORD lineCntr = 0;
		char *a = new char[2048];
        memset(a, 0, 2048);
		char srcPath[_MAX_PATH];

		// construct the full path
		if (!_AfxFullPath(srcPath, m_pathName)) {
			strcpy(srcPath, m_pathName);
		}

		// strip off the source filename to end up with just the path
		LPSTR pTemp = strrchr(srcPath, '\\');
		if (pTemp) {
			*(pTemp + 1) = 0;
		}

        f = fopen(m_pathName, "r");
        if(f != NULL && f != (FILE *)-1)  {
			setvbuf(f, NULL, _IOFBF, 32768);		// use a large file buffer
            while(fgets(a, 2047, f)) {
				// if the string "//{{NO_DEPENDENCIES}}" is at the start of one of the 
				// first 10 lines of a file, don't build dependencies on it or any
				// of the files it includes
				if (lineCntr < 10) {
					static char* pDependStr = "//{{NO_DEPENDENCIES}}";
					if (strncmp(a, pDependStr, strlen(pDependStr)) == 0) {
						noDependMap[m_pathName] = 0;	// add it to the noDependMap
						break;							// no need for further processing of this file
					}
				}
				++lineCntr;
				// have to handle a variety of legal syntaxes that we find in our source files:
				//    #include
				// #   include
                // #include
				// if the first non-whitespace char is a '#', consider this line
				pTemp = a;
				pTemp += strspn(pTemp, " \t");			// skip whitespace
				if (*pTemp == '#') {
					++pTemp;							// skip the '#'
					pTemp += strspn(pTemp, " \t");		// skip more whitespace
					if( !strncmp(pTemp, "include", 7) ){
						pTemp += 7;						// skip the "include"
						pTemp += strspn(pTemp, " \t");	// skip more whitespace
						bSystem = (*pTemp == '<');		// mark if it's a system include or not
                        // forget system files -- we just have to search all the paths
                        // every time and never find them! This change alone speeds a full
                        // depend run on my system from 5 minutes to 3:15
						// if (bSystem || (*pTemp == '"')) {
                        if (*pTemp == '"') {
							LPSTR pStart = pTemp + 1;	// mark the start of the string
							pTemp = pStart + strcspn(pStart, ">\" ");	// find the end of the string
							*pTemp = 0;					// terminate the string

							// construct the full pathname from the path part of the 
							// source file and the name listed here
							fullName = srcPath;
							fullName += pStart;
							CFileRecord *pAddMe = AddFile( pStart, fullName, bSystem );
							if (pAddMe) {
								m_includes.Add(pAddMe);
							}
						}
					}
				}
            }
            fclose(f);
        }
        delete [] a;
    }

    void PrintIncludes(){
        int i = 0;
        while( i < m_includes.GetSize() ){
            CFileRecord *pRec = (CFileRecord*) m_includes[i];

            //  Don't write out files that don't exist or are not in the namespace
            //      of the programs using it (netscape_AppletMozillaContext.h doesn't
            //      mix well with 16 bits).
			// Also don't write out files that are in the noDependMap
			void*	lookupJunk;
            if( !pRec->m_bVisited && pRec->m_pathName.GetLength() != 0 && !noDependMap.Lookup(pRec->m_pathName, lookupJunk)) {

				// not supposed to have a file in the list that doesn't exist
				ASSERT(FileExists(pRec->m_pathName));

                CString csOutput;
                csOutput = pRec->m_pathName;
                FATName(csOutput);

				fprintf(pAltFile, "\\\n    %s ", (const char *) csOutput );

				// mark this one as done so we don't do it more than once
                pRec->m_bVisited = TRUE;

                pRec->PrintIncludes();
            }
            i++;
        }
    }

    void PrintDepend(){
        CFileRecord *pRec;
        BOOL bFound;
        POSITION next;
        CString name;

		// clear all the m_bVisisted flags so we can use it to keep track
		// of whether we've already output this file as a dependency
        next = fileMap.GetStartPosition();
        while( next ){
            fileMap.GetNextAssoc( next, name, *(void**)&pRec );
            pRec->m_bVisited = FALSE;
        }

        char fname[_MAX_FNAME];

		if (pRec->m_pathName.GetLength() != 0) {
            if( bSimple ){
    			fprintf(pAltFile, "\n\n\n%s:\t", m_pathName );
            }
            else {
                CString csOutput;
                csOutput = m_pathName;
                FATName(csOutput);

    			_splitpath( csOutput, NULL, NULL, fname, NULL );

    			fprintf(pAltFile, "\n\n\n$(OUTDIR)\\%s.obj: %s ", fname, (const char*) csOutput );
            }
	        m_bVisited = TRUE;		// mark it as done so we won't do it again
	        PrintIncludes();
		}
    }


    static CString NormalizeFileName( const char* pName ){
        return CString(pName);
    }

    static CFileRecord* FindFileRecord( const char *pName ){
		CFileRecord* pRec = NULL;
		CString name(pName);
		FixPathName(name);
		fileMap.Lookup(name, (void*&)pRec);
		return(pRec);
    }
public:
    static CFileRecord* AddFile( const char* pShortName, const char* pFullName, BOOL bSystem = FALSE, 
                BOOL bSource = FALSE ){

		char fullName[_MAX_PATH];
		BOOL bFound = FALSE;
		CString foundName;
		CString fixedShortName;
        CString s;

        // normalize the name
        fixedShortName = pShortName;
        FixPathName(fixedShortName);
        pShortName = fixedShortName;

        // if it is source, we might be getting an obj file.  If we do,
        //  convert it to a c or c++ file.
        if( bSource && (strcmp(GetExt(pShortName),".obj") == 0) ){
            char path_buffer[_MAX_PATH];
            char fname[_MAX_FNAME] = "";
            CString s;

            _splitpath( pShortName, NULL, NULL, fname, NULL );
            if( FileExists( s = CString(fname) + ".cpp") ){
                pShortName = s;
                pFullName = s;
            }
            else if( FileExists( s = CString(fname) + ".c" ) ){
                pShortName = s;
                pFullName = s;
            }
            else {
                return 0;
            }
        }

		// if pFullName was not constructed, construct it here based on the current directory
		if (!pFullName) {
			_AfxFullPath(fullName, pShortName);
			pFullName = fullName;
		}
		
		// first check to see if we already have this exact file
		CFileRecord *pRec = FindFileRecord(pFullName);

        // if not found and not a source file check the header list --
        // all files we've found in include directories are in the includeMap.
        // we can save gobs of time by getting it from there
        if (!pRec && !bSource)
            includeMap.Lookup(fixedShortName, (void*&)pRec);

        if (!pRec) {
            // not in one of our lists, start scrounging on disk

            // check the fullname first
            if (FileExists(pFullName)) {
                foundName = pFullName;
                bFound = TRUE;
            }
            else {
                // if still not found, search the include paths
                int i = 0;
                while( i < includeDirectories.GetSize() ){
                    if( FileExists( includeDirectories[i] + pShortName ) ){
                        foundName = includeDirectories[i] + pShortName;
                        bFound = TRUE;
                        break;
                    }
                    i++;
                }
            }
        }
        else {
            // we found it
            bFound = TRUE;
        }

		// source files are not allowed to be missing
		if (bSource && !pRec && !bFound) {
			fprintf(stderr, "Source file: %s doesn't exist\n", pFullName);
			mainReturn = -1;		// exit with an error, don't write out the results
		}

#ifdef _DEBUG
		if (!pRec && !bFound && !bSystem) {
			fprintf(stderr, "Header not found: %s (%s)\n", pShortName, pFullName);
		}
#endif

		// if none of the above logic found it already in the list, 
        // must be a new file, add it to the list
        if (bFound && (pRec == NULL)) {
            pRec = new CFileRecord( pShortName, foundName, bSystem, bSource);

			// if this one isn't a source file add it to the includeMap
			// for performance reasons (so we can find it there next time rather
			// than having to search the file system again)
			if (!bSource) {
				includeMap[pShortName] = pRec;
			}
        }
        return pRec;
    }


    static void PrintDependancies(){
        CFileRecord *pRec;
        BOOL bFound;
        POSITION next;
        CString name;

		// use orderedFileNames to preserve order
		for (int pos = 0; pos < orderedFileNames.GetSize(); pos++) {
			pRec = FindFileRecord(orderedFileNames[pos]);
            if(pRec && pRec->m_bSource ){
                pRec->PrintDepend();
			}
		}
    }


    void PrintDepend2(){
        CFileRecord *pRec;
        int i;

        if( m_includes.GetSize() != 0 ){
			fprintf(pAltFile, "\n\n\n%s: \\\n",m_pathName );
            i = 0;
            while( i < m_includes.GetSize() ){
                pRec = (CFileRecord*) m_includes[i];
    			fprintf(pAltFile, "\t\t\t%s\t\\\n",pRec->m_pathName );
                i++;
            }
        }
    }

    static void PrintDependancies2(){
        CFileRecord *pRec;
        BOOL bFound;
        POSITION next;
        CString name;

        next = fileMap.GetStartPosition();
        while( next ){
            fileMap.GetNextAssoc( next, name, *(void**)&pRec );
            pRec->PrintDepend2();
        }
    }


    static void PrintTargets(const char *pMacroName, const char *pDelimiter){
        CFileRecord *pRec;
        BOOL bFound;
        POSITION next;
        CString name;

        BOOL bNeedDelimiter = FALSE;
		fprintf(pAltFile, "%s = ", pMacroName);        

		// use orderedFileNames to preserve target order
		for (int pos = 0; pos < orderedFileNames.GetSize(); pos++) {
			pRec = FindFileRecord(orderedFileNames[pos]);
			ASSERT(pRec);

            if( pRec && pRec->m_bSource && pRec->m_pathName.GetLength() != 0){
                char fname[_MAX_FNAME];

                CString csOutput;
                csOutput = pRec->m_pathName;
                FATName(csOutput);

                _splitpath( csOutput, NULL, NULL, fname, NULL );

                if(bNeedDelimiter)  {
                    fprintf(pAltFile, "%s\n", pDelimiter);
                    bNeedDelimiter = FALSE;
                }

				fprintf(pAltFile, "     $(OUTDIR)\\%s.obj   ", fname );
                bNeedDelimiter = TRUE;
            }
        }
		fprintf(pAltFile, "\n\n\n");        
    }

    static CString DirDefine( const char *pPath ){
        char path_buffer[_MAX_PATH];
        char dir[_MAX_DIR] = "";
        char dir2[_MAX_DIR] = "";
        char fname[_MAX_FNAME] = "";
        char ext[_MAX_EXT] = "";
        CString s;

        _splitpath( pPath, 0, dir, 0, ext );

        BOOL bDone = FALSE;

        while( dir && !bDone){
            // remove the trailing slash
            dir[ strlen(dir)-1] = 0;
            _splitpath( dir, 0, dir2, fname, 0 );
            if( strcmp( fname, "SRC" ) == 0 ){
                strcpy( dir, dir2 );
            }
            else {
                bDone = TRUE;
            }
        }
        s = CString(fname) + "_" + (ext+1);
        return s;
    }


    static void PrintSources(){
        int i;
        CString dirName, newDirName;

        for( i=0; i< orderedFileNames.GetSize(); i++ ){
            newDirName= DirDefine( orderedFileNames[i] );
            if( newDirName != dirName ){
                fprintf( pAltFile, "\n\n\nFILES_%s= $(FILES_%s) \\", 
                        (const char*)newDirName, (const char*)newDirName );
                dirName = newDirName;
            }
            fprintf( pAltFile, "\n\t%s^", (const char*)orderedFileNames[i] );
        }
    }

    static CString SourceDirName( const char *pPath, BOOL bFileName){
        char path_buffer[_MAX_PATH];
        char drive[_MAX_DRIVE] = "";
        char dir[_MAX_DIR] = "";
        char fname[_MAX_FNAME] = "";
        char ext[_MAX_EXT] = "";
        CString s;

        _splitpath( pPath, drive, dir, fname, ext );

        s = CString(drive) + dir;
        if( bFileName ){
            s += CString("FNAME") + ext;
        }
        else {
            // remove the trailing slash
            s = s.Left( s.GetLength() - 1 );
        }
        return s;
    }


    static CString GetExt( const char *pPath){
        char ext[_MAX_EXT] = "";

        _splitpath( pPath, 0,0,0, ext );

        CString s = CString(ext);
        s.MakeLower();
        return s;
    }

    static void PrintBuildRules(){
        int i;
        CString dirName;
        
        CMapStringToPtr dirList;

        for( i=0; i< orderedFileNames.GetSize(); i++ ){
            dirList[ SourceDirName(orderedFileNames[i], TRUE) ]= 0;
        }

        POSITION next;
        CString name;
        void *pVal;

        next = dirList.GetStartPosition();
        while( next ){
            dirList.GetNextAssoc( next, name, pVal);
            CString dirDefine = DirDefine( name );
            CString ext = GetExt( name );
            name = SourceDirName( name, FALSE );
            CString response = dirDefine.Left(8);

            fprintf( pAltFile, 
                "\n\n\n{%s}%s{$(OUTDIR)}.obj:\n"
                "\t@rem <<$(OUTDIR)\\%s.cl\n"
                "\t$(CFILEFLAGS)\n"
                "\t$(CFLAGS_%s)\n"
                "<<KEEP\n"
                "\t$(CPP) @$(OUTDIR)\\%s.cl %%s\n",
                (const char*)name,
                (const char*)ext,
                (const char*)response,
                (const char*)dirDefine,
                (const char*)response
            );

            fprintf( pAltFile, 
                "\n\n\nBATCH_%s:\n"
                "\t@rem <<$(OUTDIR)\\%s.cl\n"
                "\t$(CFILEFLAGS)\n"
                "\t$(CFLAGS_%s)\n"
                "\t$(FILES_%s)\n"
                "<<KEEP\n"
                "\t$(TIMESTART)\n"
                "\t$(CPP) @$(OUTDIR)\\%s.cl\n"
                "\t$(TIMESTOP)\n",
                (const char*)dirDefine,
                (const char*)response,
                (const char*)dirDefine,
                (const char*)dirDefine,
                (const char*)response
            );
        }

        //
        // Loop through one more time and build the final batch build
        //  rule
        //
        fprintf( pAltFile, 
            "\n\n\nBATCH_BUILD_OBJECTS:\t\t\\\n");

        next = dirList.GetStartPosition();
        while( next ){
            dirList.GetNextAssoc( next, name, pVal);
            CString dirDefine = DirDefine( name );

            fprintf( pAltFile, 
                "\tBATCH_%s\t\t\\\n", dirDefine );
        }

        fprintf( pAltFile, 
            "\n\n");
    }
        

    static void ProcessFiles(){
        CFileRecord *pRec;
        BOOL bFound;
        POSITION next;
        CString name;

		// search all the files for headers, adding each one to the list when found
		// rather than do it recursively, it simple marks each one it's done
		// and starts over, stopping only when all are marked as done

        next = fileMap.GetStartPosition();
        while( next ){
            fileMap.GetNextAssoc( next, name, *(void**)&pRec );
            if( pRec->m_bVisited == FALSE && pRec->m_bSystem == FALSE ){
				// mark this file as already done so we don't read it again
				// to find its headers
                pRec->m_bVisited = TRUE;
                pRec->ProcessFile();
                // Start searching from the beginning again
				// because ProcessFile may have added new files 
				// and changed the GetNextAssoc order
                next = fileMap.GetStartPosition();       

            }
        }
    }


};

CMapStringToPtr CFileRecord::fileMap;           // contains all allocated CFileRecords
CStringArray CFileRecord::orderedFileNames;
CMapStringToPtr CFileRecord::includeMap;        // pointers to CFileRecords in fileMap
CMapStringToPtr CFileRecord::noDependMap;       // no data, just an index

int main( int argc, char** argv ){
    int i = 1;
    char *pStr;
    static int iRecursion = 0;	//	Track levels of recursion.
	static CString outputFileName;
    
    //	Entering.
    iRecursion++;

    while( i < argc ){
        if( argv[i][0] == '-' || argv[i][0] == '/' ){
            switch( argv[i][1] ){

            case 'i':
            case 'I':
                if( argv[i][2] != 0 ){
                    pStr = &(argv[i][2]);
                }
                else {
                    i++;
                    pStr = argv[i];
                }
                if( pStr == 0 || *pStr == '-' || *pStr == '/' ){
                    goto usage;
                }
                else {
                    AddIncludeDirectory( pStr );
                }
                break;

            case 'f':
            case 'F':
                if( argv[i][2] != 0 ){
                    pStr = &(argv[i][2]);
                }
                else {
                    i++;
                    pStr = argv[i];
                }
                if( pStr == 0 || *pStr == '-' || *pStr == '/'){
                    goto usage;
                }
                else {
                    CStdioFile f;
                    CString s;
                    if( f.Open( pStr, CFile::modeRead ) ){
                        while(f.ReadString(s)){
                            s.TrimLeft();
                            s.TrimRight();
                            if( s.GetLength() ){
                                CFileRecord::AddFile( s, NULL, FALSE, TRUE );
                            }
                        } 
                        f.Close();
                    }
                    else {
                        fprintf(stderr,"makedep: file not found: %s", pStr );
                        exit(-1);
                    }
                }
                break;

            case 'o':
            case 'O':
                if( argv[i][2] != 0 ){
                    pStr = &(argv[i][2]);
                }
                else {
                    i++;
                    pStr = argv[i];
                }
                if( pStr == 0 || *pStr == '-' || *pStr == '/'){
                    goto usage;
                }
                else {
                    CStdioFile f;
                    CString s;
					outputFileName = pStr;
					if(!(pAltFile = fopen(pStr, "w+")))	{
                        fprintf(stderr, "makedep: file not found: %s", pStr );
                        exit(-1);
                    }
                }
                break;

            case '1':
                if( argv[i][2] == '6')  {
                    b16 = TRUE;
                }
                break;

            case 's':
            case 'S':
                bSimple = TRUE;
                break;



            case 'h':
            case 'H':
            case '?':
            usage:
                fprintf(stderr, "usage: makedep -I <dirname> -F <filelist> <filename>\n"
                       "  -I <dirname>    Directory name, can be repeated\n"
                       "  -F <filelist>   List of files to scan, one per line\n"
                       "  -O <outputFile> File to write output, default stdout\n");
                exit(-1);
            }
        }
        else if( argv[i][0] == '@' ){
        	//	file contains our commands.
	        CStdioFile f;
    	    CString s;
    	    int iNewArgc = 0;
    	    char **apNewArgv = new char*[5000];
			memset(apNewArgv, 0, sizeof(apNewArgv));

			//	First one is always the name of the exe.
			apNewArgv[0] = argv[0];
			iNewArgc++;

			const char *pTraverse;
			const char *pBeginArg;
	        if( f.Open( &argv[i][1], CFile::modeRead ) ){
    	        while( iNewArgc < 5000 && f.ReadString(s) )	{
					//	Scan the string for args, and do the right thing.
					pTraverse = (const char *)s;
					while(iNewArgc < 5000 && *pTraverse)	{
						if(isspace(*pTraverse))	{
								pTraverse++;
								continue;
						}

						//	Extract to next space.
						pBeginArg = pTraverse;
						do	{
							pTraverse++;
						}
						while(*pTraverse && !isspace(*pTraverse));
						apNewArgv[iNewArgc] = new char[pTraverse - pBeginArg + 1];
						memset(apNewArgv[iNewArgc], 0, pTraverse - pBeginArg + 1);
						strncpy(apNewArgv[iNewArgc], pBeginArg, pTraverse - pBeginArg);
						iNewArgc++;
					}
	            } 
    	        f.Close();
        	}
        	
        	//	Recurse if needed.
        	if(iNewArgc > 1)	{
        		main(iNewArgc, apNewArgv);
        	}
        	
        	//	Free off the argvs (but not the very first one which we didn't allocate).
        	while(iNewArgc > 1)	{
        		iNewArgc--;
        		delete [] apNewArgv[iNewArgc];
        	}
        	delete [] apNewArgv;
        }
        else {
            CFileRecord::AddFile( argv[i], NULL, FALSE, TRUE );
        }
        i++;
    }
    
    //	Only of the very bottom level of recursion do we do this.
    if(iRecursion == 1)	{

		// only write the results out if no errors encountered
		if (mainReturn == 0) {
			CFileRecord::ProcessFiles();
            if( !bSimple ){
        		CFileRecord::PrintTargets("OBJ_FILES", "\\");
                if(b16) {
    			    CFileRecord::PrintTargets("LINK_OBJS", "+\\");
                }
                else    {
    			    CFileRecord::PrintTargets("LINK_OBJS", "^");
                }
                CFileRecord::PrintSources();
                CFileRecord::PrintBuildRules();
            }
    		CFileRecord::PrintDependancies();
		}
	    
		if(pAltFile != stdout)	{
			fclose(pAltFile);
			if (mainReturn != 0) {
				remove(outputFileName);	// kill output file if returning an error
			}
		}
	}
	iRecursion--;

    if (iRecursion == 0 )
    {
        // last time through -- clean up allocated CFileRecords!
        CFileRecord *pFRec;
        CString     name;
        POSITION    next;

        next = CFileRecord::fileMap.GetStartPosition();
        while( next ){
            CFileRecord::fileMap.GetNextAssoc( next, name, *(void**)&pFRec );
            delete pFRec;
        }
    }

    return mainReturn;
}