//====== 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 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 &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 &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 &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 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 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 &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 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 projectList; CUtlVector 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; jm_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 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 &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 *m_pAllProjectsList; CUtlVector *m_pOutProjectsList; }; void CProjectDependencyGraph::TranslateProjectIndicesToDependencyProjects( CUtlVector &projectList, CUtlVector &out ) { CGameFilterProjectIterator iterator; iterator.m_pAllProjectsList = &m_Projects; iterator.m_pOutProjectsList = &out; g_pVPC->IterateTargetProjects( projectList, &iterator ); }