//====================================================================
// 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 VC7 projects and solutions.
//
//====================================================================
#include "stdafx.h"
#include "VC7Objects.h"
#include "PugXml.h"
#include <shlobj.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////////////////////////////////////////////////////////
// Strings we care about in the workspace file.
//////////////////////////////////////////////////////////////////////
static const TCHAR s_pszProject[] = _T("Project(");
static const int s_iProjectLen = sizeof( s_pszProject ) - 1;
static const TCHAR s_pszNoInherit[] = _T("$(NOINHERIT)");
static const int s_iNoInheritLen = sizeof( s_pszNoInherit ) - 1;
//////////////////////////////////////////////////////////////////////
// Strings we care about in the project file.
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// CVC7Settings Implementation.
//////////////////////////////////////////////////////////////////////
CVC7Settings::CVC7Settings()
{
}
//
// ------------------------------------------------------------------
//
CVC7Settings::~CVC7Settings()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Settings::ReadFromXml( CPugXmlBranch * p_poBranch )
{
// Get the preprocessor definitions, and include directories from the branch.
CTCharString a_sDefines = p_poBranch->GetAttribute( _T("PreprocessorDefinitions") );
CTCharString a_sIncludeDirs = p_poBranch->GetAttribute( _T("AdditionalIncludeDirectories") );
CTCharString a_sIgnore = p_poBranch->GetAttribute( _T("IgnoreStandardIncludePath") );
if( a_sIgnore.length() )
{
if( !_tcsicmp( a_sIgnore.c_str(), _T("TRUE") ) )
c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
else
c_oSubtractSettings.push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
}
// Now parse them into the list.
CStdStringArray a_oDefines;
CStdStringArray a_oIncludes;
ParseSeparatedList( a_sDefines.c_str(), _T(";,"), a_oDefines );
ParseSeparatedList( a_sIncludeDirs.c_str(), _T(";,"), a_oIncludes );
int i = 0;
for( i = 0; i<a_oDefines.size(); ++i )
c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_DEFINE, a_oDefines[i].c_str() ) );
for( i = 0; i<a_oIncludes.size(); ++i )
c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_INCLUDE, a_oIncludes[i].c_str() ) );
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
void CVC7Settings::ParseSettings( CDSSettings * p_poFileSettings, CDSParsedSettings & p_roSettings ) const
{
CVC7Settings * a_poFileSettings = static_cast<CVC7Settings*>(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;
bool a_bInheritIncludes = true;
bool a_bInheritDefines = true;
if( p_poFileSettings )
{
// Check if the file has NOINHERIT specified for it's include dirs or preprocessor definitions.
CDSSettingArray::iterator a_oSubIter = a_poFileSettings->c_oAddSettings.begin();
while( a_oSubIter != a_poFileSettings->c_oAddSettings.end() )
{
if( !_tcscmp( a_oSubIter->GetText(), s_pszNoInherit ) )
{
if( a_oSubIter->GetType() == CDSSetting::SETTING_INCLUDE )
a_bInheritIncludes = false;
else
if( a_oSubIter->GetType() == CDSSetting::SETTING_DEFINE )
a_bInheritDefines = false;
}
a_oSubIter++;
}
}
// 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;
}
// Process the setting.
const TCHAR * a_pszText = a_roSetting.GetText();
switch( a_roSetting.GetType() )
{
case CDSSetting::SETTING_DEFINE:
if( !a_bInheritDefines )
continue;
else
a_oNewDefines.push_back( a_pszText );
break;
case CDSSetting::SETTING_INCLUDE:
if( !a_bInheritIncludes )
continue;
else
a_oNewIncludes.push_back( a_pszText );
break;
case CDSSetting::SETTING_FLAG:
if( *a_pszText == _T('X') )
a_oStdIncludes.clear();
break;
}
}
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() )
{
const CDSSetting & a_roSetting = *a_oIter;
a_oIter++;
// Process the setting.
const TCHAR * a_pszText = a_roSetting.GetText();
switch( a_roSetting.GetType() )
{
case CDSSetting::SETTING_DEFINE:
if( _tcscmp( a_pszText, s_pszNoInherit ) )
a_oNewDefines.push_back( a_pszText );
break;
case CDSSetting::SETTING_INCLUDE:
if( _tcscmp( a_pszText, s_pszNoInherit ) )
a_oNewIncludes.push_back( a_pszText );
break;
case CDSSetting::SETTING_FLAG:
if( *a_pszText == _T('X') )
a_oStdIncludes.clear();
break;
}
}
}
// 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() );
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC7ConfigurationHolder Implementation.
//////////////////////////////////////////////////////////////////////
CVC7ConfigurationHolder::CVC7ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
CVC7ConfigurationHolder::~CVC7ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7ConfigurationHolder::ReadFromXml( CPugXmlBranch * p_poBranch,
const TCHAR * p_pszChildName )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
// Find all children with the specified name.
CPugXmlBranchArray a_oConfigs;
p_poBranch->FindAllElements( p_pszChildName, a_oConfigs );
// Parse the configuration settings from each child node.
CVC7Settings * a_poSettings = NULL;
for( int i = 0; i<a_oConfigs.GetCount(); ++i )
{
CPugXmlBranch a_oConfiguration = a_oConfigs.GetAt( i );
CPugXmlBranch a_oCompilerTool = a_oConfiguration.FindFirstElemAttr( _T("Tool"), _T("Name"), _T("VCCLCompilerTool") );
if( a_oCompilerTool.IsNull() )
continue;
a_poSettings = new CVC7Settings;
c_oSettings.insert( CDSMapConfigToSettings::value_type( a_oConfiguration.GetAttribute( _T("Name") ), a_poSettings ) );
a_eStatus = a_poSettings->ReadFromXml( &a_oCompilerTool );
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC7File Implementation.
//////////////////////////////////////////////////////////////////////
CVC7File::CVC7File()
{
c_poConfigHolder = new CVC7ConfigurationHolder;
}
//
// ------------------------------------------------------------------
//
CVC7File::~CVC7File()
{
if( c_poConfigHolder )
{
delete c_poConfigHolder;
c_poConfigHolder = NULL;
}
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7File::ReadFromXml( const TCHAR * p_pszProjectDir,
CPugXmlBranch * p_poBranch )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
// First try to find the "FullPath" branch.
CTCharString a_sPath = p_poBranch->GetAttribute( _T("FullPath") );
if( !a_sPath.length() )
{
// Hopefully it will have a relative path.
a_sPath = p_poBranch->GetAttribute( _T("RelativePath") );
if( !a_sPath.length() )
return PARSE_STATUS_GENERIC_ERROR;
// Get the full path of the project from the relative path.
c_sPath = GetFullPath( p_pszProjectDir, a_sPath.c_str() );
}
else
c_sPath = a_sPath;
DetermineName();
// Read any per file configuration settings we may have.
a_eStatus = static_cast<CVC7ConfigurationHolder*>(c_poConfigHolder)->ReadFromXml( p_poBranch, _T("FileConfiguration") );
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC7Project Implementation.
//////////////////////////////////////////////////////////////////////
CVC7Project::CVC7Project()
{
}
//
// ------------------------------------------------------------------
//
CVC7Project::~CVC7Project()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Project::ReadProject( const TCHAR * p_pszPath )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
// 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 );
// Parse the project's XML.
CPugXmlParser a_oParser;
if( !a_oParser.ParseFile( p_pszPath ) )
return PARSE_STATUS_ERRORS_OCCURRED;
// Get the configurations branch, so we can iterate through them.
CPugXmlBranch a_oConfigurations = a_oParser.GetRoot().FindFirstElement( _T("Configurations") );
if( !a_oConfigurations.HasChildren() )
return PARSE_STATUS_OK;
// Read the project configurations.
CVC7ConfigurationHolder a_oConfigurationHolder;
a_eStatus = a_oConfigurationHolder.ReadFromXml( &a_oConfigurations, _T("Configuration") );
if( a_eStatus != PARSE_STATUS_OK )
return a_eStatus;
// Create a configuration object for each configuration we read.
CDSMapConfigToSettings & a_roSettings = a_oConfigurationHolder.GetSettings();
CDSMapConfigToSettings::iterator a_oIter = a_roSettings.begin();
while( a_oIter != a_roSettings.end() )
{
CDSConfiguration * a_poConfig = new CDSConfiguration( a_oIter->first.c_str(), a_oIter->second );
c_oConfigurations.push_back( a_poConfig );
// Remove the settings from the config holder.
a_roSettings.erase( a_oIter );
a_oIter = a_roSettings.begin();
}
// Get the Files branch, so we can iterate through them.
CPugXmlBranch a_oFiles = a_oParser.GetRoot().FindFirstElement( _T("Files") );
if( !a_oFiles.HasChildren() )
return PARSE_STATUS_OK;
// Get an array of all the files under the Files branch.
CPugXmlBranchArray a_oFileList;
a_oFiles.FindAllElements( _T("File"), a_oFileList );
for( int i = 0; i<a_oFileList.GetCount(); ++i )
{
CPugXmlBranch a_oFile = a_oFileList.GetAt( i );
// Create a new file, and read it's settings.
CVC7File * a_poFile = new CVC7File;
a_eStatus = a_poFile->ReadFromXml( a_szDriveAndDir, &a_oFile );
c_oFiles.push_back( a_poFile );
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////
// CVC7Workspace Implementation.
//////////////////////////////////////////////////////////////////////
bool CVC7Workspace::s_bHaveStandardIncludes = false;
// I don't have the latest platform SDK installed with VC6, so I took this from the VC7 shlobj.h header.
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 0x001c // <user name>\Local Settings\Applicaiton Data (non roaming)
#endif // CSIDL_LOCAL_APPDATA
CVC7Workspace::CVC7Workspace()
{
if( !s_bHaveStandardIncludes )
{
// Get the directory VC7 is installed in, so we can replace "($VCInstallDir)" macros with the correct path.
TCHAR a_szVCDir[MAX_PATH + 1] = {0};
CRegKey a_oVCDirKey;
if( GetRegKey( _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.0"), a_oVCDirKey ) )
{
unsigned long a_ulCount = MAX_PATH;
if( a_oVCDirKey.QueryValue( a_szVCDir, _T("InstallDir"), &a_ulCount ) == ERROR_SUCCESS )
{
TCHAR * a_pszSep = _tcsstr( a_szVCDir, _T("Common7") );
if( a_pszSep )
*a_pszSep = 0;
_tcscat( a_szVCDir, _T("vc7\\") );
}
}
enum { MAX_INCLUDE_DIRS = MAX_PATH * 20 };
TCHAR a_szIncludeDirs[MAX_INCLUDE_DIRS] = {0};
unsigned long a_ulCount = MAX_INCLUDE_DIRS;
// Apparently this function isn't present on some NT boxes :(, so we'll do it the hard way with ID lists...
//if( SUCCEEDED( SHGetSpecialFolderPath( NULL, a_szVCDatPath, CSIDL_LOCAL_APPDATA, FALSE ) ) )
// Attempt to read the include directories from the user's AppData directory. If it doesn't exist, we'll read it from the registry.
bool a_bFoundVCIncludeVars = false;
TCHAR a_szVCDatPath[MAX_PATH + 1] = {0};
IMalloc * a_poMalloc = NULL;
if( SUCCEEDED( SHGetMalloc( &a_poMalloc ) ) )
{
LPITEMIDLIST a_pstPathIDList = NULL;
if( SUCCEEDED( SHGetSpecialFolderLocation( NULL, CSIDL_LOCAL_APPDATA, &a_pstPathIDList ) ) )
{
if( SHGetPathFromIDList( a_pstPathIDList, a_szVCDatPath ) )
{
_tcscat( a_szVCDatPath, _T("\\Microsoft\\VisualStudio\\7.0\\VCComponents.dat") );
FILE * a_pstFile = _tfopen( a_szVCDatPath, _T("rt") );
if( a_pstFile )
{
while( _fgetts( a_szIncludeDirs, MAX_INCLUDE_DIRS, a_pstFile ) )
{
const int s_iIncludeDirsLen = _tcslen( _T("Include Dirs=") );
if( !_tcsncmp( a_szIncludeDirs, _T("Include Dirs="), s_iIncludeDirsLen ) )
{
// We found it.
a_bFoundVCIncludeVars = true;
int a_iBytes = sizeof( TCHAR ) * (_tcslen( a_szIncludeDirs ) - s_iIncludeDirsLen);
memmove( a_szIncludeDirs, a_szIncludeDirs + s_iIncludeDirsLen, a_iBytes );
break;
}
}
if( !a_bFoundVCIncludeVars )
a_szIncludeDirs[0] = _T('\0');
fclose( a_pstFile );
}
}
a_poMalloc->Free( a_pstPathIDList );
a_pstPathIDList = NULL;
}
a_poMalloc->Release();
a_poMalloc = NULL;
}
if( !a_bFoundVCIncludeVars )
{
// Read the standard include directories from the registry.
CRegKey a_oKey;
if( GetRegKey( _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.0\\VC\\VC_OBJECTS_PLATFORM_INFO\\Win32\\Directories"), a_oKey ) )
{
if( a_oKey.QueryValue( a_szIncludeDirs, _T("Include Dirs"), &a_ulCount ) == ERROR_SUCCESS )
a_bFoundVCIncludeVars = true;
}
}
if( a_bFoundVCIncludeVars )
{
// We found the VCDir, and the Directories key, so read the directory list and parse it.
ParseSeparatedList( a_szIncludeDirs, _T(";"), s_oStdIncludes );
// Perform the macro replacement.
for( int i = 0; i<s_oStdIncludes.size(); ++i )
{
CTCharString a_sDir = s_oStdIncludes[i];
TCHAR a_szCurrentDir[MAX_PATH + 1] = {0};
TCHAR * a_pszMacro = _tcsstr( a_sDir.c_str(), _T("$(VCInstallDir)") );
if( a_pszMacro )
{
a_pszMacro += _tcslen( _T("$(VCInstallDir)") );
CTCharString a_sNewDir = a_szVCDir;
a_sNewDir.append( a_pszMacro );
s_oStdIncludes[i] = a_sNewDir;
}
}
}
// Don't need to read them again.
s_bHaveStandardIncludes = true;
}
}
//
// ------------------------------------------------------------------
//
CVC7Workspace::~CVC7Workspace()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Workspace::ParseLine( TCHAR * p_pszLine )
{
ParseStatus a_eStatus = PARSE_STATUS_OK;
if( !_tcsncmp( p_pszLine, s_pszProject, s_iProjectLen ) )
{
TCHAR * a_pszSep = _tcschr( p_pszLine, _T('=') );
if( !a_pszSep )
return PARSE_STATUS_OK;
// Extract the project name.
TCHAR a_szProjectName[MAX_PATH + 1] = {0};
a_pszSep = ExtractString( a_szProjectName, a_pszSep, _T('\"') );
// Extract the relative path to the project.
TCHAR a_szRelativeName[MAX_PATH + 1] = {0};
ExtractString( a_szRelativeName, a_pszSep, _T('\"') );
// Get the full path of the project from the relative path.
CTCharString a_sProjectPath = GetFullPath( c_sDirectory.c_str(), a_szRelativeName );
a_eStatus = ReadOneProject( a_sProjectPath.c_str() );
}
return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Workspace::ReadOneProject( const TCHAR * p_pszProjectPath )
{
// Create a project and parse it's contents.
CVC7Project * a_poProject = new CVC7Project;
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;
}
//
// ------------------------------------------------------------------
//