//====================================================================
// Although great care has gone into developing this software,
// it is provided without any guarantee of reliability, accuracy
// of information, or correctness of operation. I am not responsible
// for any damages that may occur as a result of using this software.
// Use this software entirely at your own risk.
// Copyright 2003, Chris Richardson
//
// Description: Classes used to read VC6 projects and workspaces.
//
//====================================================================
#include "stdafx.h"
#include "VC6Objects.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
using namespace std;
//////////////////////////////////////////////////////////////////////
// Strings we care about in the workspace file.
//////////////////////////////////////////////////////////////////////
static const TCHAR s_pszProject[] = _T("Project:");
static int s_iProjectLen = sizeof( s_pszProject ) - 1;
//////////////////////////////////////////////////////////////////////
// Strings we care about in the project file.
//////////////////////////////////////////////////////////////////////
static const TCHAR s_pszProjectFile[] = _T("# Microsoft Developer Studio Project File");
static const int s_iProjectFileLen = sizeof( s_pszProjectFile ) - 1;
static const TCHAR s_pszBeginProject[] = _T("# Begin Project");
static const int s_iBeginProjectLen = sizeof( s_pszBeginProject ) - 1;
static const TCHAR s_pszBeginTarget[] = _T("# Begin Target");
static const int s_iBeginTargetLen = sizeof( s_pszBeginTarget ) - 1;
static const TCHAR s_pszName[] = _T("# Name");
static const int s_iNameLen = sizeof( s_pszName ) - 1;
static const TCHAR s_pszBeginSource[] = _T("# Begin Source File");
static const int s_iBeginSourceLen = sizeof( s_pszBeginSource ) - 1;
static const TCHAR s_pszIf[] = _T("IF");
static const int s_iIfLen = sizeof( s_pszIf ) - 1;
static const TCHAR s_pszElseIf[] = _T("ELSEIF");
static const int s_iElseIfLen = sizeof( s_pszElseIf ) - 1;
static const TCHAR s_pszEndIf[] = _T("ENDIF");
static const int s_iEndIfLen = sizeof( s_pszEndIf ) - 1;
static const TCHAR s_pszAddCPP[] = _T("# ADD CPP");
static const int s_iAddCPPLen = sizeof( s_pszAddCPP ) - 1;
static const TCHAR s_pszSubtractCPP[] = _T("# SUBTRACT CPP");
static const int s_iSubtractCPPLen = sizeof( s_pszSubtractCPP ) - 1;
static const TCHAR s_pszSource[] = _T("SOURCE=");
static const int s_iSourceLen = sizeof( s_pszSource ) - 1;
static const TCHAR s_pszEndSource[] = _T("# End Source File");
static const int s_iEndSourceLen = sizeof( s_pszEndSource ) - 1;
//////////////////////////////////////////////////////////////////////
// CVC6Settings Implementation.
//////////////////////////////////////////////////////////////////////
CVC6Settings::CVC6Settings()
{
}
//
// ------------------------------------------------------------------
//
CVC6Settings::~CVC6Settings()
{
}
//
// ------------------------------------------------------------------
//
TCHAR * CVC6Settings::ExtractOptionAndAppend( CDSSettingArray & p_roSettings,
TCHAR * p_pszOpt,
CDSSetting::DSSettingType p_eType )
{
TCHAR a_cOption = *p_pszOpt;
p_pszOpt++;
while( _istspace( *p_pszOpt ) )
p_pszOpt++;
TCHAR a_cEndChar = _T('\"');
if( *p_pszOpt == _T('\"') )
{
p_pszOpt++;
}
else
a_cEndChar = _T(' ');
// Extract the option.
TCHAR * a_pszSep = _tcschr( p_pszOpt, a_cEndChar );
if( !a_pszSep )
{
a_pszSep = _tcschr( p_pszOpt, _T('\n') );
if( !a_pszSep )
a_pszSep = p_pszOpt;
}
*a_pszSep = 0;
if( *p_pszOpt )
{
p_roSettings.push_back( CDSSetting( p_eType, p_pszOpt ) );
}
return a_pszSep+1;
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ParseCPPCommandLine( TCHAR * p_pszLine )
{
CDSSettingArray * a_poSettings = NULL;
const TCHAR * a_pszStart = NULL;
if( !_tcsncmp( p_pszLine, s_pszAddCPP, s_iAddCPPLen ) )
{
a_poSettings = &c_oAddSettings;
a_pszStart = p_pszLine + s_iAddCPPLen;
}
else
if( !_tcsncmp( p_pszLine, s_pszSubtractCPP, s_iSubtractCPPLen ) )
{
a_poSettings = &c_oSubtractSettings;
a_pszStart = p_pszLine + s_iSubtractCPPLen;
}
else
// Somebody passed us an invalid line.
return;
// Read the preprocessor macro definitions and include directories from the line.
TCHAR * a_pszSep = p_pszLine;
while( 1 )
{
a_pszSep = _tcschr( a_pszSep, _T('/') );
if( !a_pszSep )
break;
a_pszSep++;
if( *a_pszSep == _T('X') )
{
// Ignore standard include directories.
a_poSettings->push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
}
else
if( *a_pszSep == _T('D') )
{
// Define a symbol.
a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_DEFINE );
}
else
if( *a_pszSep == _T('U') )
{
// Undefine a symbol.
a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_UNDEFINE );
}
else
if( *a_pszSep == _T('u') )
{
// Undefine all symbols.
a_poSettings->push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("u") ) );
}
else
if( *a_pszSep == _T('I') )
{
// Additional include directory.
a_pszSep = ExtractOptionAndAppend( *a_poSettings, a_pszSep, CDSSetting::SETTING_INCLUDE );
}
}
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ProcessAddSetting( const CDSSetting * p_poSetting,
CStdStringArray & p_roStdIncludes,
CStdStringArray & p_roIncludes,
CStdStringArray & p_roDefines,
CStdStringArray & p_roNewIncludes,
CStdStringArray & p_roNewDefines ) const
{
const TCHAR * a_pszText = p_poSetting->GetText();
switch( p_poSetting->GetType() )
{
case CDSSetting::SETTING_FLAG:
if( *a_pszText == _T('X') )
{
// Ignore standard include directories.
p_roStdIncludes.clear();
}
else
if( *a_pszText == _T('u') )
{
// Undefine all symbols
p_roDefines.clear();
}
break;
case CDSSetting::SETTING_DEFINE:
// Add a new definition.
p_roNewDefines.push_back( a_pszText );
break;
case CDSSetting::SETTING_UNDEFINE:
{
// Remove the definition.
for( int i = 0; i<p_roDefines.size(); ++i )
{
if( !p_roDefines[i].compare( a_pszText ) )
{
p_roDefines.erase( &p_roDefines[i] );
break;
}
}
break;
}
case CDSSetting::SETTING_INCLUDE:
// Add a new include dir.
p_roNewIncludes.push_back( a_pszText );
break;
default:
assert( false );
break;
}
}
//
// ------------------------------------------------------------------
//
void CVC6Settings::ParseSettings( CDSSettings * p_poFileSettings,
CDSParsedSettings & p_roSettings ) const
{
CVC6Settings * a_poFileSettings = static_cast<CVC6Settings*>(p_poFileSettings);
CStdStringArray & a_roIncludes = p_roSettings.GetIncludes();
CStdStringArray & a_roDefines = p_roSettings.GetDefines();
CStdStringArray a_oStdIncludes;
CDSWorkspace::GetStdIncludes( a_oStdIncludes );
CStdStringArray a_oNewIncludes;
CStdStringArray a_oNewDefines;
// Parse our settings.
CDSSettingArray::const_iterator a_oIter = c_oAddSettings.begin();
while( a_oIter != c_oAddSettings.end() )
{
const CDSSetting & a_roSetting = *a_oIter;
a_oIter++;
// Check if the setting is in any of the file's "subtract" settings.
if( a_poFileSettings )
{
if( a_poFileSettings->IsSubtractedSetting( a_roSetting ) )
continue;
}
ProcessAddSetting( &a_roSetting,
a_oStdIncludes,
a_roIncludes,
a_roDefines,
a_oNewIncludes,
a_oNewDefines );
}
if( a_poFileSettings )
{
// Parse the file's settings.
CDSSettingArray::iterator a_oIter = a_poFileSettings->c_oAddSettings.begin();
while( a_oIter != a_poFileSettings->c_oAddSettings.end() )
{
CDSSetting & a_roSetting = *a_oIter;
a_oIter++;
ProcessAddSetting( &a_roSetting,
a_oStdIncludes,
a_roIncludes,
a_roDefines,
a_oNewIncludes,
a_oNewDefines );
}
}
// Add the new settings to the output settings.
a_roIncludes.insert( a_roIncludes.begin(), a_oStdIncludes.begin(), a_oStdIncludes.end() );
a_roIncludes.insert( a_roIncludes.end(), a_oNewIncludes.begin(), a_oNewIncludes.end() );
a_roDefines.insert( a_roDefines.end(), a_oNewDefines.begin(), a_oNewDefines.end() );
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC6ConfigurationHolder Implementation.
//////////////////////////////////////////////////////////////////////
CVC6ConfigurationHolder::CVC6ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
CVC6ConfigurationHolder::~CVC6ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6ConfigurationHolder::ParseIfElseBlock( CStream & p_roStream )
{
enum{ MAX_LINE_LENGTH = 2048 };
TCHAR a_szLine[MAX_LINE_LENGTH] = {0};
CVC6Settings * a_poSettings = NULL;
TCHAR a_szCurrentConfigName[MAX_PATH + 1] = {0};
while( p_roStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
{
if( !_tcsncmp( a_szLine + 1, s_pszEndIf, s_iEndIfLen ) )
{
// We're done the configuration settings.
break;
}
else
if( !_tcsncmp( a_szLine + 1, s_pszElseIf, s_iElseIfLen ) ||
!_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
{
// I've only seen cases that look like this:
// !IF "$(CFG)" == "IncFinder - Win32 Release"
// so that's what we look for here.
// Get the name of the variable.
TCHAR a_szVariable[MAX_PATH + 1] = {0};
TCHAR * a_pszSep = ExtractString( a_szVariable, a_szLine, _T('\"') );
if( !a_pszSep )
continue;
if( !_tcscmp( a_szVariable, _T("$(CFG)") ) )
{
// Get the value of the variable that's being tested. This is the name of the configuration
// that the next set of options is specified for.
a_pszSep = ExtractString( a_szCurrentConfigName, a_pszSep, _T('\"') );
a_poSettings = NULL;
}
}
else
if( !_tcsncmp( a_szLine, s_pszAddCPP, s_iAddCPPLen ) || !_tcsncmp( a_szLine, s_pszSubtractCPP, s_iSubtractCPPLen ) )
{
// The current line is specifiying C++ compiler command line parameters.
if( !a_poSettings )
{
// Create the settings object, since we need it now.
a_poSettings = new CVC6Settings;
c_oSettings.insert( CDSMapConfigToSettings::value_type( a_szCurrentConfigName, a_poSettings ) );
}
a_poSettings->ParseCPPCommandLine( a_szLine );
}
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC6File Implementation.
//////////////////////////////////////////////////////////////////////
CVC6File::CVC6File()
{
c_poConfigHolder = new CVC6ConfigurationHolder;
}
//
// ------------------------------------------------------------------
//
CVC6File::~CVC6File()
{
if( c_poConfigHolder )
{
delete c_poConfigHolder;
c_poConfigHolder = NULL;
}
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6File::ReadProject( const TCHAR * p_pszProjectDir,
CStream & p_roStream )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
enum{ MAX_LINE_LENGTH = 2048 };
TCHAR a_szLine[MAX_LINE_LENGTH] = {0};
while( p_roStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
{
if( !_tcsncmp( a_szLine, s_pszSource, s_iSourceLen ) )
{
// Find the separator before the source file path.
TCHAR * a_pszSep = a_szLine + s_iSourceLen;
if( !*a_pszSep )
continue;
if( *a_pszSep == _T('\"') )
{
a_pszSep++;
// Find the end quote and remove it.
TCHAR * a_pszEndSep = _tcschr( a_pszSep, _T('\"') );
if( !a_pszEndSep )
continue;
*a_pszEndSep = 0;
}
else
{
// Remove the newline from the end of the line.
int a_iLen = _tcslen( a_pszSep );
if( a_iLen && a_pszSep[a_iLen-1] == _T('\n') )
a_pszSep[a_iLen-1] = _T('\0');
}
// Get the full path of the project from the relative path.
c_sPath = GetFullPath( p_pszProjectDir, a_pszSep );
DetermineName();
}
else
if( !_tcsncmp( a_szLine, _T("!"), _tcslen( _T("!") ) ) )
{
// These are expressions or MESSAGE statements.
// We only care about the expressions.
if( !_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
{
unsigned long a_ulPos = p_roStream.GetPos();
a_ulPos -= _tcslen( a_szLine ) + sizeof( TCHAR );
p_roStream.SetPos( a_ulPos );
a_eStatus = static_cast<CVC6ConfigurationHolder*>(c_poConfigHolder)->ParseIfElseBlock( p_roStream );
}
}
else
if( !_tcsncmp( a_szLine, s_pszEndSource, s_iEndSourceLen ) )
{
// We're done reading the current file.
break;
}
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC6Project Implementation.
//////////////////////////////////////////////////////////////////////
CVC6Project::CVC6Project()
{
}
//
// ------------------------------------------------------------------
//
CVC6Project::~CVC6Project()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Project::ReadProject( const TCHAR * p_pszPath )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
CFILEStream a_oStream;
// First open a stream on the file.
a_eStatus = a_oStream.Open( p_pszPath );
if( a_eStatus != PARSE_STATUS_OK )
return a_eStatus;
// Get the directory and drive information out of the project path.
TCHAR a_szDriveAndDir[MAX_PATH + 1] = {0};
TCHAR a_szDirectory[MAX_PATH + 1] = {0};
_tsplitpath( p_pszPath, a_szDriveAndDir, a_szDirectory, NULL, NULL );
_tcscat( a_szDriveAndDir, a_szDirectory );
CVC6ConfigurationHolder a_oConfigurationSettings;
BOOL a_bInProject = FALSE;
enum{ MAX_LINE_LENGTH = 2048 };
TCHAR a_szLine[MAX_LINE_LENGTH] = {0};
while( a_oStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
{
if( !_tcsncmp( a_szLine, s_pszProjectFile, s_iProjectFileLen ) )
{
// Extract the project name.
TCHAR a_szName[MAX_PATH + 1] = {0};
ExtractString( a_szName, a_szLine, _T('\"') );
c_sName = a_szName;
}
if( !_tcsncmp( a_szLine, s_pszBeginProject, s_iBeginProjectLen ) )
{
// We think we have a valid project.
a_bInProject = TRUE;
}
// If we haven't reached the "# Begin Project" yet, we don't know what kind of information we could have,
// so we won't bother parsing it.
if( !a_bInProject )
continue;
if( !_tcsncmp( a_szLine, s_pszBeginTarget, s_iBeginTargetLen ) )
{
// Read the targets (configurations).
while( 1 )
{
unsigned long a_ulPos = a_oStream.GetPos();
if( !a_oStream.GetS( a_szLine, MAX_LINE_LENGTH ) )
break;
if( !_tcslen( a_szLine ) || a_szLine[0] == _T('\n') )
continue;
if( !_tcsncmp( a_szLine, s_pszName, s_iNameLen ) )
{
// Extract the name of the target.
TCHAR * a_pszTarget = ExtractString( NULL, a_szLine, _T('\"') );
if( !a_pszTarget )
continue;
// Lookup the settings we read for the configuration.
CDSMapConfigToSettings & a_roSettings = a_oConfigurationSettings.GetSettings();
CDSMapConfigToSettings::iterator a_oIter = a_roSettings.find( a_pszTarget );
CDSSettings * a_poSettings = a_oIter->second;
if( a_oIter != a_roSettings.end() )
{
// Create a new configuration.
CDSConfiguration * a_poConfiguration = new CDSConfiguration( a_pszTarget, a_poSettings );
c_oConfigurations.push_back( a_poConfiguration );
// Remove the settings object from the configuration holder, since the new configuration object is storing them.
a_roSettings.erase( a_oIter );
}
else
{
// There is no settings object for the configuration. Looks like a problem (maybe a manually added target string?).
// ?? Not sure how I want to handle it.
}
}
else
{
// Done reading the targets, push the stream back to the position it was at before we read the current line.
a_oStream.SetPos( a_ulPos );
break;
}
}
}
else
if( !_tcsncmp( a_szLine, _T("!"), _tcslen( _T("!") ) ) )
{
// These are expressions or MESSAGE statements.
// We only care about the expressions.
if( !_tcsncmp( a_szLine + 1, s_pszIf, s_iIfLen ) )
{
unsigned long a_ulPos = a_oStream.GetPos();
a_ulPos -= _tcslen( a_szLine ) + sizeof( TCHAR );
a_oStream.SetPos( a_ulPos );
a_eStatus = a_oConfigurationSettings.ParseIfElseBlock( a_oStream );
}
}
else
if( !_tcsncmp( a_szLine, s_pszBeginSource, s_iBeginSourceLen ) )
{
// Create a new source file, and have it read it's info from the project file.
CVC6File * a_poFile = new CVC6File;
a_eStatus = a_poFile->ReadProject( a_szDriveAndDir, a_oStream );
c_oFiles.push_back( a_poFile );
}
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC6Workspace Implementation.
//////////////////////////////////////////////////////////////////////
bool CVC6Workspace::s_bHaveStandardIncludes = false;
CVC6Workspace::CVC6Workspace()
{
if( !s_bHaveStandardIncludes )
{
// Read the standard include directories from the registry.
CRegKey a_oKey;
if( GetRegKey( _T("HKEY_CURRENT_USER\\Software\\Microsoft\\DevStudio\\6.0\\Build System\\Components\\Platforms\\Win32 (x86)\\Directories"), a_oKey ) )
{
// We found the key, now read the directory list and parse it.
enum { MAX_INCLUDE_DIRS = MAX_PATH * 20 };
TCHAR a_szIncludeDirs[MAX_INCLUDE_DIRS] = {0};
unsigned long a_ulCount = MAX_INCLUDE_DIRS;
if( a_oKey.QueryValue( a_szIncludeDirs, _T("Include Dirs"), &a_ulCount ) == ERROR_SUCCESS )
{
ParseSeparatedList( a_szIncludeDirs, _T(";"), s_oStdIncludes );
}
}
// Don't need to read them again.
s_bHaveStandardIncludes = true;
}
}
//
// ------------------------------------------------------------------
//
CVC6Workspace::~CVC6Workspace()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Workspace::ParseLine( TCHAR * p_pszLine )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
if( !_tcsncmp( p_pszLine, s_pszProject, s_iProjectLen ) )
{
// Find the project name.
//
TCHAR a_szProjectName[MAX_PATH + 1] = {0};
TCHAR * a_pszEndSep = ExtractString( a_szProjectName, p_pszLine, _T('\"') );
if( !a_pszEndSep )
return PARSE_STATUS_OK;
// Find the path of the project. We can't use ExtractString because there are a few different cases to deal with.
//
TCHAR * a_pszSep = _tcschr( a_pszEndSep, _T('=') );
if( !a_pszSep )
return PARSE_STATUS_OK;
// Not all project paths have quotes around them.
a_pszEndSep = NULL;
if( *++a_pszSep == _T('\"') )
{
// The current project path should have an end quote.
a_pszEndSep = _tcschr( ++a_pszSep, _T('\"') );
if( !a_pszEndSep )
return PARSE_STATUS_OK;
}
else
{
// The project path doesn't have a quote, so search for the "- Package Owner" beginning.
a_pszEndSep = _tcschr( a_pszSep, _T('-') );
if( !a_pszEndSep )
return PARSE_STATUS_OK;
else
a_pszEndSep--;
}
*a_pszEndSep = 0;
// Get the full path of the project from the relative path.
CTCharString a_sProjectPath = GetFullPath( c_sDirectory.c_str(), a_pszSep );
a_eStatus = ReadOneProject( a_sProjectPath.c_str() );
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC6Workspace::ReadOneProject( const TCHAR * p_pszProjectPath )
{
// Create a project and parse it's contents.
CVC6Project * a_poProject = new CVC6Project;
ParseStatus a_eStatus = a_poProject->ReadProject( p_pszProjectPath );
if( a_eStatus != PARSE_STATUS_OK )
delete a_poProject;
else
c_oProjects.push_back( a_poProject );
return a_eStatus;
}
//
// ------------------------------------------------------------------
//