You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1097 lines
32 KiB
1097 lines
32 KiB
//====== 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 );
|
|
}
|