|
|
//====== Copyright 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "vpc.h"
#include "dependencies.h"
#include "baseprojectdatacollector.h"
#include "tier0/fasttimer.h"
#define VPC_CRC_CACHE_VERSION 3
extern char *g_IncludeSeparators[2];
static const char *g_pDependencyRelevantProperties[] = { g_pOption_AdditionalProjectDependencies, g_pOption_AdditionalOutputFiles, g_pOption_AdditionalIncludeDirectories, g_pOption_ImportLibrary, g_pOption_OutputFile };
static CRelevantPropertyNames g_DependencyRelevantPropertyNames = { g_pDependencyRelevantProperties, Q_ARRAYSIZE( g_pDependencyRelevantProperties ) };
bool IsSharedLibraryFile( const char *pFilename ) { const char *pExt = V_GetFileExtension( pFilename ); if ( pExt && ( V_stricmp( pExt, "so" ) == 0 || V_stricmp( pExt, "dylib" ) == 0 || V_stricmp( pExt, "dll" ) == 0 ) ) { return true; } else { return false; } }
bool IsLibraryFile( const char *pFilename ) { const char *pExt = V_GetFileExtension( pFilename ); if ( IsSharedLibraryFile( pFilename ) || ( pExt && ( V_stricmp( pExt, "lib" ) == 0 || V_stricmp( pExt, "a" ) == 0 ) ) ) { return true; } else { return false; } }
static inline bool IsSourceFile( const char *pFilename ) { const char *pExt = V_GetFileExtension( pFilename ); if ( pExt && ( V_stricmp( pExt, "cpp" ) == 0 || V_stricmp( pExt, "c" ) == 0 || V_stricmp( pExt, "cxx" ) == 0 || V_stricmp( pExt, "cc" ) == 0 || V_stricmp( pExt, "rc" ) == 0 || V_stricmp( pExt, "hxx" ) == 0 || V_stricmp( pExt, "h" ) == 0 || V_stricmp( pExt, "inc" ) == 0 ) ) { return true; } else { return false; } }
// ------------------------------------------------------------------------------------------------------- //
// CDependency functions.
// ------------------------------------------------------------------------------------------------------- //
CDependency::CDependency( CProjectDependencyGraph *pDependencyGraph ) : m_pDependencyGraph( pDependencyGraph ) { m_iDependencyMark = m_pDependencyGraph->m_iDependencyMark - 1; m_bCheckedIncludes = false; m_nCacheModificationTime = m_nCacheFileSize = 0; }
CDependency::~CDependency() { }
const char* CDependency::GetName() const { return m_Filename.String(); }
bool CDependency::CompareAbsoluteFilename( const char *pAbsPath ) const { return ( V_stricmp( m_Filename.String(), pAbsPath ) == 0 ); }
bool CDependency::DependsOn( CDependency *pTest, int flags ) { m_pDependencyGraph->ClearAllDependencyMarks(); CUtlVector<CUtlBuffer> callTreeOutputStack; if ( FindDependency_Internal( callTreeOutputStack, pTest, flags, 1 ) ) { if ( g_pVPC->IsShowDependencies() ) { printf( "-------------------------------------------------------------------------------\n" ); printf( "%s\n", GetName() ); int i; for( i = callTreeOutputStack.Count() - 1; i >= 0; i-- ) { printf( ( const char * )callTreeOutputStack[i].Base() ); } printf( "-------------------------------------------------------------------------------\n" ); } return true; } else { return false; } }
bool CDependency::FindDependency_Internal( CUtlVector<CUtlBuffer> &callTreeOutputStack, CDependency *pTest, int flags, int depth ) { if ( pTest == this ) return true; // Don't revisit us.
if ( HasBeenMarked() ) return false;
Mark();
// Don't recurse further?
if ( depth > 1 && !(flags & k_EDependsOnFlagRecurse) ) return false;
// Don't go into the children of libs if they don't want.
if ( !(flags & k_EDependsOnFlagTraversePastLibs) && m_Type == k_eDependencyType_Library ) return false;
// Go through everything I depend on. If any of those things
for ( int iDepList=0; iDepList < 2; iDepList++ ) { if ( iDepList == 1 && !(flags & k_EDependsOnFlagCheckAdditionalDependencies) ) continue;
CUtlVector<CDependency*> &depList = (iDepList == 0 ? m_Dependencies : m_AdditionalDependencies);
for ( int i=0; i < depList.Count(); i++ ) { CDependency *pChild = depList[i]; if ( pChild->FindDependency_Internal( callTreeOutputStack, pTest, flags, depth+1 ) ) { if ( g_pVPC->IsShowDependencies() ) { char buf[2048]; Q_strncpy( buf, "depends on ", sizeof( buf ) ); Q_strcat( buf, pChild->GetName(), sizeof( buf ) ); Q_strcat( buf, "\n", sizeof( buf ) ); int n = callTreeOutputStack.AddToTail(); CUtlBuffer &b = callTreeOutputStack[n]; b.EnsureCapacity( Q_strlen( buf ) + 2 ); b.PutString( buf ); b.PutChar( 0 ); } return true; } } }
return false; }
void CDependency::Mark() { m_iDependencyMark = m_pDependencyGraph->m_iDependencyMark; }
bool CDependency::HasBeenMarked() { return m_iDependencyMark == m_pDependencyGraph->m_iDependencyMark; }
CDependency_Project::CDependency_Project( CProjectDependencyGraph *pDependencyGraph ) : CDependency( pDependencyGraph ) { }
void CDependency_Project::StoreProjectParameters( const char *szScriptName ) { m_StoredOutputFilename = g_pVPC->GetOutputFilename(); V_GetCurrentDirectory( m_szStoredCurrentDirectory, sizeof( m_szStoredCurrentDirectory ) ); V_strncpy( m_szStoredScriptName, szScriptName, sizeof( m_szStoredScriptName ) ); m_StoredConditionalsActive.SetSize( g_pVPC->m_Conditionals.Count() );
for ( int iConditional=0; iConditional < g_pVPC->m_Conditionals.Count(); iConditional++ ) { m_StoredConditionalsActive[iConditional] = g_pVPC->m_Conditionals[iConditional].m_bGameConditionActive; } }
void CDependency_Project::ExportProjectParameters() { g_pVPC->SetOutputFilename( m_StoredOutputFilename.Get() ); V_SetCurrentDirectory( m_szStoredCurrentDirectory ); if ( m_StoredConditionalsActive.Count() > g_pVPC->m_Conditionals.Count() ) { g_pVPC->VPCError( "ExportProjectParameters( %s ) - too many defines stored.", m_szStoredScriptName ); } for ( int iConditional=0; iConditional < g_pVPC->m_Conditionals.Count(); iConditional++ ) { g_pVPC->m_Conditionals[iConditional].m_bGameConditionActive = m_StoredConditionalsActive[iConditional]; } }
int CDependency_Project::FindByProjectName( CUtlVector<CDependency_Project*> &projects, const char *pTestName ) { for ( int i=0; i < projects.Count(); i++ ) { CDependency_Project *pProject = projects[i];
if ( V_stricmp( pProject->m_ProjectName.String(), pTestName ) == 0 ) return i; }
return -1; }
// This is responsible for scanning a project file and pulling out:
// - a list of libraries it uses
// - the $AdditionalIncludeDirectories paths
// - a list of source files it uses
// - the name of the file it generates
class CSingleProjectScanner : public CBaseProjectDataCollector { public: typedef CBaseProjectDataCollector BaseClass;
CSingleProjectScanner() : CBaseProjectDataCollector( &g_DependencyRelevantPropertyNames ) { m_bInLinker = false; } virtual void EndProject() { }
void ScanProjectFile( CProjectDependencyGraph *pGraph, const char *szScriptName, CDependency_Project *pProject ) { // Someday we'll pass this interface down into VPC_ParseProjectScript instead of using the global.
IBaseProjectGenerator *pOldGenerator = g_pVPC->GetProjectGenerator(); g_pVPC->SetProjectGenerator( this );
// This has VPC parse the script and CBaseProjectDataCollector collects all the data into lists of the
// stuff we care about like source files and include paths.
m_ScriptName = szScriptName; g_pVPC->ParseProjectScript( szScriptName, 0, true, false );
int iConfig = m_BaseConfigData.m_Configurations.First(); if ( iConfig != m_BaseConfigData.m_Configurations.InvalidIndex() ) { CSpecificConfig *pConfig = m_BaseConfigData.m_Configurations[iConfig];
SetupIncludeDirectories( pConfig, szScriptName ); SetupFilesList( pGraph, pProject ); SetupImportLibrary( pGraph, pConfig, szScriptName ); SetupAdditionalProjectDependencies( pProject, pConfig ); SetupAdditionalOutputFiles( pProject, pConfig );
}
g_pVPC->SetProjectGenerator( pOldGenerator ); Term(); }
void SetupFilesList( CProjectDependencyGraph *pGraph, CDependency_Project *pProject ) { for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next( i ) ) { CFileConfig *pFile = m_Files[i];
// If this file is excluded from all configs, then exclude it.
if ( pFile->m_Configurations.Count() > 0 ) { int nExcluded = 0; for ( int iSpecific=pFile->m_Configurations.First(); iSpecific != pFile->m_Configurations.InvalidIndex(); iSpecific=pFile->m_Configurations.Next( iSpecific ) ) { CSpecificConfig *pTest = pFile->m_Configurations[iSpecific]; if ( pTest->m_bFileExcluded && !pTest->m_bIsSchema ) ++nExcluded; } if ( nExcluded == (int)m_BaseConfigData.m_Configurations.Count() ) continue; }
// Make this an absolute path.
const char *pFilename = pFile->GetName(); char sAbsolutePath[MAX_PATH]; V_MakeAbsolutePath( sAbsolutePath, sizeof( sAbsolutePath ), pFilename );
// Don't bother with source files if we're not building the full dependency set.
if ( !pGraph->m_bFullDependencySet ) if ( IsSourceFile( sAbsolutePath ) ) continue;
// For source files, don't bother with files that don't exist. If we do create entries
// for files that don't exist, then they'll have a "cache file size"
if ( !Sys_Exists( sAbsolutePath ) && IsSourceFile( sAbsolutePath ) ) continue;
// Add an entry for this file.
CDependency *pDep = pGraph->FindOrCreateDependency( sAbsolutePath ); pProject->m_Dependencies.AddToTail( pDep );
// Add includes.
if ( pDep->m_Type == k_eDependencyType_SourceFile ) AddIncludesForFile( pGraph, pDep ); } }
void AddIncludesForFile( CProjectDependencyGraph *pGraph, CDependency *pFile ) { // Have we already parsed this file for its includes?
if ( pFile->m_bCheckedIncludes ) return; pFile->m_bCheckedIncludes = true;
// Setup all the include paths we want to search.
CUtlVector<CUtlString> includeDirs; char szDir[MAX_PATH]; if ( !V_ExtractFilePath( pFile->GetName(), szDir, sizeof( szDir ) ) ) g_pVPC->VPCError( "AddIncludesForFile: V_ExtractFilePath( %s ) failed.", pFile->GetName() );
includeDirs.AddToTail( szDir ); includeDirs.AddMultipleToTail( m_IncludeDirectories.Count(), m_IncludeDirectories.Base() );
// Get all the #include directives.
CUtlVector<CUtlString> includes; GetIncludeFiles( pFile->GetName(), includes ); ++pGraph->m_nFilesParsedForIncludes;
// Now see which of them we can open.
for ( int iIncludeFile=0; iIncludeFile < includes.Count(); iIncludeFile++ ) { for ( int iIncludeDir=0; iIncludeDir < includeDirs.Count(); iIncludeDir++ ) { char szFullName[MAX_PATH]; V_ComposeFileName( includeDirs[iIncludeDir].String(), includes[iIncludeFile].String(), szFullName, sizeof( szFullName ) ); CDependency *pIncludeFile = pGraph->FindDependency( szFullName ); if ( !pIncludeFile ) { if ( !Sys_Exists( szFullName ) ) continue;
// Find or add the dependency.
pIncludeFile = pGraph->FindOrCreateDependency( szFullName ); } pFile->m_Dependencies.AddToTail( pIncludeFile );
// Recurse.
AddIncludesForFile( pGraph, pIncludeFile ); } } }
bool SeekToIncludeStart( const char* &pSearchPos ) { while ( 1 ) { ++pSearchPos; if ( *pSearchPos == 0 || *pSearchPos == '\r' || *pSearchPos == '\n' ) return false;
if ( *pSearchPos == '\"' || *pSearchPos == '<' ) { ++pSearchPos; return true; } } }
bool SeekToIncludeEnd( const char* &pSearchPos ) { while ( 1 ) { ++pSearchPos; if ( *pSearchPos == 0 || *pSearchPos == '\r' || *pSearchPos == '\n' ) return false;
if ( *pSearchPos == '\"' || *pSearchPos == '>' ) return true; } }
void GetIncludeFiles( const char *pFilename, CUtlVector<CUtlString> &includes ) { char *pFileData; int ret = Sys_LoadFile( pFilename, (void**)&pFileData, false ); if ( ret == -1 ) { if ( g_pVPC->IsVerbose() ) { g_pVPC->VPCWarning( "GetIncludeFiles( %s ) - can't open file (included by project %s).", pFilename, m_ScriptName.String() ); } return; }
const char *pSearchPos = pFileData; while ( 1 ) { const char *pLookFor = "#include"; const char *pIncludeStatement = V_strstr( pSearchPos, pLookFor ); if ( !pIncludeStatement ) break; pSearchPos = pIncludeStatement + V_strlen( pLookFor ); if ( !SeekToIncludeStart( pSearchPos ) ) continue; const char *pFilenameStart = pSearchPos;
if ( !SeekToIncludeEnd( pSearchPos ) ) continue; const char *pFilenameEnd = pSearchPos;
if ( (pFilenameEnd - pFilenameStart) > MAX_PATH-10 ) g_pVPC->VPCError( "Include statement too long in %s.", pFilename );
char szIncludeFilename[MAX_PATH], szFixed[MAX_PATH]; V_strncpy( szIncludeFilename, pFilenameStart, pFilenameEnd - pFilenameStart + 1 );
// Fixup double slashes.
V_StrSubst( szIncludeFilename, "\\\\", "\\", szFixed, sizeof( szFixed ) ); V_FixSlashes( szFixed );
includes.AddToTail( szFixed ); } free( pFileData ); }
void SetupIncludeDirectories( CSpecificConfig *pConfig, const char *szScriptName ) { if ( m_BaseConfigData.m_Configurations.Count() == 0 ) g_pVPC->VPCError( "No configurations for %s in project %s.", szScriptName, m_ScriptName.String() );
const char *pIncludes = pConfig->m_pKV->GetString( g_pOption_AdditionalIncludeDirectories, "" ); CSplitString relativeIncludeDirs( pIncludes, (const char**)g_IncludeSeparators, Q_ARRAYSIZE( g_IncludeSeparators ) );
for ( int i=0; i < relativeIncludeDirs.Count(); i++ ) { char sAbsolute[MAX_PATH]; V_MakeAbsolutePath( sAbsolute, sizeof( sAbsolute ), relativeIncludeDirs[i] ); m_IncludeDirectories.AddToTail( sAbsolute ); } }
void SetupImportLibrary( CProjectDependencyGraph *pGraph, CSpecificConfig *pConfig, const char *szScriptName ) { m_ImportLibrary = pConfig->m_pKV->GetString( g_pOption_ImportLibrary, NULL ); m_LinkerOutputFile = pConfig->m_pKV->GetString( g_pOption_OutputFile, NULL ); }
void SetupAdditionalProjectDependencies( CDependency_Project *pProject, CSpecificConfig *pConfig ) { const char *pVal = pConfig->m_pKV->GetString( g_pOption_AdditionalProjectDependencies ); if ( pVal ) { pProject->m_AdditionalProjectDependencies.Purge();
CSplitString outStrings ( pVal, ";" ); for ( int i=0; i < outStrings.Count(); i++ ) { char szProjectName[MAX_PATH]; sprintf( szProjectName, "%s", outStrings[i] ); if ( g_pVPC->IsDecorateProject() ) { macro_t *pMacro = g_pVPC->FindOrCreateMacro( "PLATFORM", false, NULL ); if ( pMacro ) { char szPlatform[MAX_PATH]; sprintf( szPlatform, " (%s)", pMacro->value.String() ); strcat( szProjectName, szPlatform ); } } pProject->m_AdditionalProjectDependencies.AddToTail( szProjectName ); } } }
void SetupAdditionalOutputFiles( CDependency_Project *pProject, CSpecificConfig *pConfig ) { const char *pVal = pConfig->m_pKV->GetString( g_pOption_AdditionalOutputFiles ); if ( pVal ) { pProject->m_AdditionalOutputFiles.Purge();
CSplitString outStrings( pVal, ";" ); for ( int i=0; i < outStrings.Count(); i++ ) { pProject->m_AdditionalOutputFiles.AddToTail( outStrings[i] ); } } }
virtual const char* GetProjectFileExtension() { return "UNUSED"; }
protected: virtual bool StartPropertySection( configKeyword_e keyword, bool *pbShouldSkip ) { m_bInLinker = ( keyword == KEYWORD_LINKER || keyword == KEYWORD_LIBRARIAN ); return true; } virtual void HandleProperty( const char *pProperty, const char *pCustomScriptData ) { // We don't want the $OutputFile property from the $BrowseInformation section.
if ( V_stricmp( pProperty, g_pOption_OutputFile ) == 0 && !m_bInLinker ) return;
BaseClass::HandleProperty( pProperty, pCustomScriptData ); }
virtual void EndPropertySection( configKeyword_e keyword ) { m_bInLinker = false; }
public: // Project include directories. These strings are deleted when the object goes away.
CUtlVector<CUtlString> m_IncludeDirectories; CUtlString m_ImportLibrary; CUtlString m_LinkerOutputFile; CUtlString m_ScriptName; bool m_bInLinker; };
CProjectDependencyGraph::CProjectDependencyGraph() { m_iDependencyMark = 0; m_bFullDependencySet = false; m_bHasGeneratedDependencies = false; }
void CProjectDependencyGraph::BuildProjectDependencies( int nBuildProjectDepsFlags ) { m_bFullDependencySet = ( ( nBuildProjectDepsFlags & BUILDPROJDEPS_FULL_DEPENDENCY_SET ) != 0 ); m_nFilesParsedForIncludes = 0; if ( m_bFullDependencySet ) { Log_Msg( LOG_VPC, "\nBuilding full dependency set (all sources and headers)..." ); } else { Log_Msg( LOG_VPC, "\nBuilding partial dependency set (libs only)..." ); }
// Have it iterate ALL projects in the list, with whatever platform conditionals are around.
// When it visits a
CUtlVector<projectIndex_t> projectList; CUtlVector<int> oldState;
if ( nBuildProjectDepsFlags & BUILDPROJDEPS_CHECK_ALL_PROJECTS ) { // So iterate all projects.
projectList.SetSize( g_pVPC->m_Projects.Count() ); for ( int i=0; i < g_pVPC->m_Projects.Count(); i++ ) projectList[i] = i;
// Simulate /allgames but remember the old state too.
for ( int j=0; j<g_pVPC->m_Conditionals.Count(); j++ ) { if ( g_pVPC->m_Conditionals[j].type == CONDITIONAL_GAME ) { oldState.AddToTail( (j << 16) + (int)g_pVPC->m_Conditionals[j].m_bDefined ); g_pVPC->m_Conditionals[j].m_bDefined = true; } } } else { projectList.AddMultipleToTail( g_pVPC->m_TargetProjects.Count(), g_pVPC->m_TargetProjects.Base() ); }
// Load any prior results so we don't have to regenerate the whole cache (which can take a couple minutes).
char sCacheFile[MAX_PATH] = {0}; V_ComposeFileName( g_pVPC->GetSourcePath(), "vpc.cache", sCacheFile, sizeof( sCacheFile ) ); if ( m_bFullDependencySet ) { if ( !LoadCache( sCacheFile ) ) { Log_Msg( LOG_VPC, "\n\nNo vpc.cache file found.\nThis will take a minute to generate dependency info from all the sources.\nPut the kleenex down.\nNext time it will have a cache file and be fast.\n\n" ); } }
CFastTimer timer; timer.Start(); g_pVPC->IterateTargetProjects( projectList, this ); timer.End();
ResolveAdditionalProjectDependencies();
// Restore the old game defines state?
if ( nBuildProjectDepsFlags & BUILDPROJDEPS_CHECK_ALL_PROJECTS ) { for ( int i=0; i < oldState.Count(); i++ ) { int iDefine = oldState[i] >> 16; g_pVPC->m_Conditionals[iDefine].m_bDefined = ( (oldState[i] & 1) != 0 ); } }
// Save the expensive work we did into a cache file so it can be used next time.
if ( m_bFullDependencySet ) { SaveCache( sCacheFile ); }
Log_Msg( LOG_VPC, "\n\n" ); if ( m_nFilesParsedForIncludes > 0 ) { Log_Msg( LOG_VPC, "%d files parsed in %.2f seconds for #includes.\n", m_nFilesParsedForIncludes, timer.GetDuration().GetSeconds() ); }
m_bHasGeneratedDependencies = true; }
void CProjectDependencyGraph::ResolveAdditionalProjectDependencies() { for ( int iMainProject=0; iMainProject < m_Projects.Count(); iMainProject++ ) { CDependency_Project *pMainProject = m_Projects[iMainProject];
for ( int i=0; i < pMainProject->m_AdditionalProjectDependencies.Count(); i++ ) { const char *pLookingFor = pMainProject->m_AdditionalProjectDependencies[i].String();
// Look for this project name among all the projects.
int j; for ( j=0; j < m_Projects.Count(); j++ ) { if ( V_stricmp( m_Projects[j]->m_ProjectName.String(), pLookingFor ) == 0 ) break; }
if ( j == m_Projects.Count() ) { //VPCError( "Project %s lists '%s' in its $AdditionalProjectDependencies, but there is no project by that name.", pMainProject->GetName(), pLookingFor );
continue; }
if ( pMainProject->m_AdditionalDependencies.Find( m_Projects[j] ) == pMainProject->m_AdditionalDependencies.InvalidIndex() ) pMainProject->m_AdditionalDependencies.AddToTail( m_Projects[j] ); } } }
bool CProjectDependencyGraph::HasGeneratedDependencies() const { return m_bHasGeneratedDependencies; }
bool CProjectDependencyGraph::VisitProject( projectIndex_t iProject, const char *szProjectName ) { // Read in the project.
if ( !Sys_Exists( szProjectName ) ) { return false; }
// Add another dot for the pacifier.
Log_Msg( LOG_VPC, "." );
// Add this project.
CDependency_Project *pProject = new CDependency_Project( this ); char szAbsolute[MAX_PATH]; V_MakeAbsolutePath( szAbsolute, sizeof( szAbsolute ), szProjectName ); pProject->m_Filename = szAbsolute;
pProject->m_Type = k_eDependencyType_Project; pProject->m_iProjectIndex = iProject; m_Projects.AddToTail( pProject ); m_AllFiles.Insert( szAbsolute, pProject );
// Remember various parameters passed to us so we can regenerate this project without having
// to call VPC_IterateTargetProjects.
pProject->StoreProjectParameters( szProjectName );
char sAbsProjectFilename[MAX_PATH]; V_MakeAbsolutePath( sAbsProjectFilename, sizeof( sAbsProjectFilename ), g_pVPC->GetOutputFilename() ); pProject->m_ProjectFilename = sAbsProjectFilename;
// Scan the project file and get all its libs, cpp, and h files.
CSingleProjectScanner scanner; scanner.ScanProjectFile( this, szAbsolute, pProject ); pProject->m_IncludeDirectories = scanner.m_IncludeDirectories; pProject->m_ProjectName = scanner.m_ProjectName;
// Get a list of all files that depend on this project, starting with the .lib if it generates one.
CUtlVector<CUtlString> outputFiles; outputFiles = pProject->m_AdditionalOutputFiles;
// Now note that the import library depends on this project.
// $(ImportLibrary) will be a lib in the case of DLLs that create libs (like tier0).
// $(OutputFile) will be a lib in the case of static libs (like tier1).
const char *pLinkerOutputFile = scanner.m_LinkerOutputFile.String(); const char *pImportLibrary = scanner.m_ImportLibrary.String(); if ( !IsLibraryFile( pImportLibrary ) ) { pImportLibrary = pLinkerOutputFile; }
if ( IsLibraryFile( pImportLibrary ) ) { outputFiles.AddToTail( pImportLibrary ); }
// The string that we replace $(TargetName) with is the output project filename without the path or extension.
// That'll be something like "tier0_360".
char sTargetNameReplacement[MAX_PATH]; V_FileBase( pLinkerOutputFile, sTargetNameReplacement, sizeof( sTargetNameReplacement ) ); // Now add a CDependency for each file.
for ( int i=0; i < outputFiles.Count(); i++ ) { const char *pFilename = outputFiles[i].String(); // Replace $(TargetName) and fixup the path.
char sReplaced[MAX_PATH], sAbsImportLibrary[MAX_PATH]; V_StrSubst( pFilename, "$(TargetName)", sTargetNameReplacement, sReplaced, sizeof( sReplaced ) ); V_MakeAbsolutePath( sAbsImportLibrary, sizeof( sAbsImportLibrary ), sReplaced );
CDependency *pImportLibrary = FindOrCreateDependency( sAbsImportLibrary ); pImportLibrary->m_Dependencies.AddToTail( pProject ); }
return true; }
void CProjectDependencyGraph::GetProjectDependencyTree( projectIndex_t iProject, CUtlVector<projectIndex_t> &dependentProjects, bool bDownwards ) { // First add the project itself.
if ( dependentProjects.Find( iProject ) == dependentProjects.InvalidIndex() ) dependentProjects.AddToTail( iProject );
// Now add anything that depends on it.
for ( int i=0; i < m_Projects.Count(); i++) { CDependency_Project *pProject = m_Projects[i]; if ( pProject->m_iProjectIndex != iProject ) continue;
// Ok, this project/game/platform combo comes from iProject. Now find anything that depends on it.
for ( int iOther=0; iOther < m_Projects.Count(); iOther++ ) { CDependency_Project *pOther = m_Projects[iOther];
if ( pOther->m_iProjectIndex == iProject ) continue;
bool bThereIsADependency; if ( bDownwards ) bThereIsADependency = pProject->DependsOn( pOther, k_EDependsOnFlagCheckNormalDependencies | k_EDependsOnFlagCheckAdditionalDependencies | k_EDependsOnFlagRecurse | k_EDependsOnFlagTraversePastLibs ); else bThereIsADependency = pOther->DependsOn( pProject, k_EDependsOnFlagCheckNormalDependencies | k_EDependsOnFlagCheckAdditionalDependencies | k_EDependsOnFlagRecurse | k_EDependsOnFlagTraversePastLibs );
if ( bThereIsADependency ) { if ( dependentProjects.Find( pOther->m_iProjectIndex ) == dependentProjects.InvalidIndex() ) dependentProjects.AddToTail( pOther->m_iProjectIndex ); } } } }
CDependency* CProjectDependencyGraph::FindDependency( const char *pFilename ) { int i = m_AllFiles.Find( pFilename ); if ( i == m_AllFiles.InvalidIndex() ) return NULL; else return m_AllFiles[i]; }
CDependency* CProjectDependencyGraph::FindOrCreateDependency( const char *pFilename ) { // Fix up stuff like blah/../blah
char sFixed[MAX_PATH]; V_FixupPathName( sFixed, sizeof( sFixed ), pFilename ); pFilename = sFixed;
CDependency *pDependency = FindDependency( pFilename ); if ( pDependency ) return pDependency;
// Couldn't find it. Create one.
pDependency = new CDependency( this ); pDependency->m_Filename = pFilename; m_AllFiles.Insert( pFilename, pDependency );
Sys_FileInfo( pFilename, pDependency->m_nCacheFileSize, pDependency->m_nCacheModificationTime );
if ( IsSourceFile( pFilename ) ) pDependency->m_Type = k_eDependencyType_SourceFile; else if ( IsLibraryFile( pFilename ) ) pDependency->m_Type = k_eDependencyType_Library; else pDependency->m_Type = k_eDependencyType_Unknown;
return pDependency; }
void CProjectDependencyGraph::ClearAllDependencyMarks() { if ( m_iDependencyMark == 0xFFFFFFFF ) { m_iDependencyMark = 1; for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next(i) ) { m_AllFiles[i]->m_iDependencyMark = 0; } } else { // The 99.9999999% chance case.
++m_iDependencyMark; } }
bool CProjectDependencyGraph::LoadCache( const char *pFilename ) { FILE *fp = fopen( pFilename, "rb" ); if ( !fp ) return false;
int version; fread( &version, sizeof( version ), 1, fp ); if ( version != VPC_CRC_CACHE_VERSION ) { g_pVPC->VPCWarning( "Invalid dependency cache file version in %s.", pFilename ); return false; }
while ( 1 ) { byte bMore; if ( fread( &bMore, 1, 1, fp ) != 1 || bMore == 0 ) break; CUtlString filename = ReadString( fp ); CDependency *pDep = FindOrCreateDependency( filename.String() ); if ( pDep->m_Dependencies.Count() != 0 ) g_pVPC->VPCError( "Cache loading dependency %s but it already exists!", filename.String() );
fread( &pDep->m_nCacheFileSize, sizeof( pDep->m_nCacheFileSize ), 1, fp ); fread( &pDep->m_nCacheModificationTime, sizeof( pDep->m_nCacheModificationTime ), 1, fp );
int nDependencies; fread( &nDependencies, sizeof( nDependencies ), 1, fp ); pDep->m_Dependencies.SetSize( nDependencies );
for ( int iDependency=0; iDependency < nDependencies; iDependency++ ) { CUtlString childDepName = ReadString( fp ); CDependency *pChildDep = FindOrCreateDependency( childDepName.String() ); pDep->m_Dependencies[iDependency] = pChildDep; } }
fclose( fp );
int nOriginalEntries = m_AllFiles.Count();
CheckCacheEntries(); RemoveDirtyCacheEntries(); MarkAllCacheEntriesValid();
Log_Msg( LOG_VPC, "\n\nLoaded %d valid dependency cache entries (%d were out of date).\n\n", m_AllFiles.Count(), nOriginalEntries-m_AllFiles.Count() ); return true; }
bool CProjectDependencyGraph::SaveCache( const char *pFilename ) { FILE *fp = fopen( pFilename, "wb" ); if ( !fp ) return false;
// Write the version.
int version = VPC_CRC_CACHE_VERSION; fwrite( &version, sizeof( version ), 1, fp );
// Write each file.
for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) { CDependency *pDep = m_AllFiles[i]; // We only care about source files.
if ( pDep->m_Type != k_eDependencyType_SourceFile ) continue;
// Write that there's a file here.
byte bYesThereIsAFileHere = 1; fwrite( &bYesThereIsAFileHere, 1, 1, fp );
WriteString( fp, pDep->m_Filename ); fwrite( &pDep->m_nCacheFileSize, sizeof( pDep->m_nCacheFileSize ), 1, fp ); fwrite( &pDep->m_nCacheModificationTime, sizeof( pDep->m_nCacheModificationTime ), 1, fp );
int nDependencies = pDep->m_Dependencies.Count(); fwrite( &nDependencies, sizeof( nDependencies ), 1, fp );
for ( int iDependency=0; iDependency < pDep->m_Dependencies.Count(); iDependency++ ) { WriteString( fp, pDep->m_Dependencies[iDependency]->m_Filename ); } }
// Write a terminator.
byte bNoMore = 0; fwrite( &bNoMore, 1, 1, fp );
fclose( fp ); return true; }
void CProjectDependencyGraph::WriteString( FILE *fp, CUtlString &utlString ) { const char *pStr = utlString.String(); int len = V_strlen( pStr ); fwrite( &len, sizeof( len ), 1, fp ); fwrite( pStr, len, 1, fp ); }
CUtlString CProjectDependencyGraph::ReadString( FILE *fp ) { int len; fread( &len, sizeof( len ), 1, fp );
char *pTemp = new char[len+1]; fread( pTemp, len, 1, fp ); pTemp[len] = 0;
CUtlString ret = pTemp; delete [] pTemp;
return ret; }
void CProjectDependencyGraph::CheckCacheEntries() { for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) { CDependency *pDep = m_AllFiles[i]; pDep->m_bCacheDirty = false;
if ( pDep->m_Type != k_eDependencyType_SourceFile ) continue; int64 fileSize, modTime; if ( !Sys_FileInfo( pDep->m_Filename.String(), fileSize, modTime ) || pDep->m_nCacheFileSize != fileSize || pDep->m_nCacheModificationTime != modTime ) { pDep->m_bCacheDirty = true; } } }
void CProjectDependencyGraph::RemoveDirtyCacheEntries() { // NOTE: This could be waaaay more efficient by pointing files at their parents and removing all the way
// up the chain rather than iterating over and over but this keeps the data structures simple.
bool bAnyDirty = true; while ( bAnyDirty ) { bAnyDirty = false;
for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) { CDependency *pDep = m_AllFiles[i]; if ( pDep->m_bCacheDirty ) continue;
// If any of its children are dirty, then mark this guy as dirty and make sure to remove the child.
for ( int iChild=0; iChild < pDep->m_Dependencies.Count(); iChild++ ) { CDependency *pChild = pDep->m_Dependencies[iChild]; if ( pChild->m_bCacheDirty ) { pDep->m_bCacheDirty = true; bAnyDirty = true; } } } } // Now that any dirty children have flagged their parents as dirty, we can remove them.
int iNext; for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=iNext ) { iNext = m_AllFiles.Next( i );
if ( m_AllFiles[i]->m_bCacheDirty ) { delete m_AllFiles[ i ]; m_AllFiles.RemoveAt( i ); } } }
void CProjectDependencyGraph::MarkAllCacheEntriesValid() { for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) { CDependency *pDep = m_AllFiles[i]; pDep->m_bCheckedIncludes = true; } }
// This is called by VPC_IterateTargetProjects and all it does is look forf a
class CGameFilterProjectIterator : public IProjectIterator { public: virtual bool VisitProject( projectIndex_t iProject, const char *szProjectName ) { char szAbsolute[MAX_PATH]; V_MakeAbsolutePath( szAbsolute, sizeof( szAbsolute ), szProjectName ); // Ok, we've got an (absolute) project filename. Search all the projects for one with that name.
bool bAdded = false; for ( int i=0; i < m_pAllProjectsList->Count(); i++ ) { CDependency_Project *pProject = m_pAllProjectsList->Element( i );
if ( pProject->CompareAbsoluteFilename( szAbsolute ) ) { m_pOutProjectsList->AddToTail( pProject ); bAdded = true; break; } }
if ( !bAdded ) { g_pVPC->VPCError( "CGameFilterProjectIterator::VisitProject( %s ) - no project found by that name.", szProjectName ); return false; }
return true; }
public: const CUtlVector<CDependency_Project*> *m_pAllProjectsList; CUtlVector<CDependency_Project*> *m_pOutProjectsList; };
void CProjectDependencyGraph::TranslateProjectIndicesToDependencyProjects( CUtlVector<projectIndex_t> &projectList, CUtlVector<CDependency_Project*> &out ) { CGameFilterProjectIterator iterator; iterator.m_pAllProjectsList = &m_Projects; iterator.m_pOutProjectsList = &out;
g_pVPC->IterateTargetProjects( projectList, &iterator ); }
|