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.
1127 lines
45 KiB
1127 lines
45 KiB
//====== Copyright 1996-2005, Valve Corporation, All rights reserved. ======= |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "vpc.h" |
|
#include "baseprojectdatacollector.h" |
|
#include "tier1/utlstack.h" |
|
#include "projectgenerator_codelite.h" |
|
|
|
static const char *k_pszBase_Makefile = "$(SRCROOT)/devtools/makefile_base_posix.mak"; |
|
|
|
static const char *g_pOption_BufferSecurityCheck = "$BufferSecurityCheck"; |
|
static const char *g_pOption_CustomBuildStepCommandLine = "$CustomBuildStep/$CommandLine"; |
|
static const char *g_pOption_PostBuildEventCommandLine = "$PostBuildEvent/$CommandLine"; |
|
static const char *g_pOption_CompileAs = "$CompileAs"; |
|
static const char *g_pOption_ConfigurationType = "$ConfigurationType"; |
|
static const char *g_pOption_Description = "$Description"; |
|
static const char *g_pOption_EntryPoint = "$EntryPoint"; |
|
static const char *g_pOption_ExtraCompilerFlags = "$GCC_ExtraCompilerFlags"; |
|
static const char *g_pOption_ExtraLinkerFlags = "$GCC_ExtraLinkerFlags"; |
|
static const char *g_pOption_CustomVersionScript = "$GCC_CustomVersionScript"; |
|
static const char *g_pOption_ForceInclude = "$ForceIncludes"; |
|
static const char *g_pOption_IgnoreAllDefaultLibraries = "$IgnoreAllDefaultLibraries"; |
|
static const char *g_pOption_LocalFrameworks = "$LocalFrameworks"; |
|
static const char *g_pOption_LowerCaseFileNames = "$LowerCaseFileNames"; |
|
static const char *g_pOption_OptimizerLevel = "$OptimizerLevel"; |
|
static const char *g_pOption_AdditionalDependencies = "$AdditionalDependencies"; |
|
static const char *g_pOption_Outputs = "$Outputs"; |
|
static const char *g_pOption_PrecompiledHeader = "$Create/UsePrecompiledHeader"; |
|
static const char *g_pOption_PrecompiledHeaderFile = "$PrecompiledHeaderFile"; |
|
static const char *g_pOption_SymbolVisibility = "$SymbolVisibility"; |
|
static const char *g_pOption_SystemFrameworks = "$SystemFrameworks"; |
|
static const char *g_pOption_SystemLibraries = "$SystemLibraries"; |
|
static const char *g_pOption_UsePCHThroughFile = "$Create/UsePCHThroughFile"; |
|
static const char *g_pOption_TargetCopies = "$TargetCopies"; |
|
static const char *g_pOption_TreatWarningsAsErrors = "$TreatWarningsAsErrors"; |
|
|
|
// These are the only properties we care about for makefiles. |
|
static const char *g_pRelevantProperties[] = |
|
{ |
|
g_pOption_AdditionalIncludeDirectories, |
|
g_pOption_CompileAs, |
|
g_pOption_OptimizerLevel, |
|
g_pOption_OutputFile, |
|
g_pOption_GameOutputFile, |
|
g_pOption_SymbolVisibility, |
|
g_pOption_PreprocessorDefinitions, |
|
g_pOption_ConfigurationType, |
|
g_pOption_ImportLibrary, |
|
g_pOption_PrecompiledHeader, |
|
g_pOption_UsePCHThroughFile, |
|
g_pOption_PrecompiledHeaderFile, |
|
g_pOption_CustomBuildStepCommandLine, |
|
g_pOption_PostBuildEventCommandLine, |
|
g_pOption_AdditionalDependencies, |
|
g_pOption_Outputs, |
|
g_pOption_Description, |
|
g_pOption_SystemLibraries, |
|
g_pOption_SystemFrameworks, |
|
g_pOption_LocalFrameworks, |
|
g_pOption_ExtraCompilerFlags, |
|
g_pOption_ExtraLinkerFlags, |
|
g_pOption_CustomVersionScript, |
|
g_pOption_EntryPoint, |
|
g_pOption_IgnoreAllDefaultLibraries, |
|
g_pOption_BufferSecurityCheck, |
|
g_pOption_ForceInclude, |
|
g_pOption_TargetCopies, |
|
g_pOption_TreatWarningsAsErrors, |
|
g_pOption_LowerCaseFileNames, |
|
}; |
|
|
|
|
|
static CRelevantPropertyNames g_RelevantPropertyNames = |
|
{ |
|
g_pRelevantProperties, |
|
V_ARRAYSIZE( g_pRelevantProperties ) |
|
}; |
|
|
|
void MakeFriendlyProjectName( char *pchProject ); |
|
|
|
void V_MakeAbsoluteCygwinPath( char *pOut, int outLen, const char *pRelativePath ) |
|
{ |
|
// While generating makefiles under Win32, we must translate drive letters like c:\ |
|
// to Cygwin-style paths like /cygdrive/c/ |
|
#ifdef _WIN32 |
|
char tmp[MAX_PATH]; |
|
V_MakeAbsolutePath( tmp, sizeof( tmp ), pRelativePath ); |
|
V_FixSlashes( tmp, '/' ); |
|
|
|
if ( tmp[0] != 0 && tmp[1] == ':' && tmp[2] == '/' ) |
|
{ |
|
// Ok, this is an absolute path |
|
V_snprintf( pOut, outLen, "/cygdrive/%c/", tmp[0] ); |
|
V_strncat( pOut, &tmp[3], outLen ); |
|
} |
|
else |
|
#endif // _WIN32 |
|
{ |
|
V_MakeAbsolutePath( pOut, outLen, pRelativePath ); |
|
V_RemoveDotSlashes( pOut ); |
|
} |
|
} |
|
|
|
// caller is responsible for free'ing (or leaking) the allocated buffer |
|
static const char* UsePOSIXSlashes( const char *pStr ) |
|
{ |
|
int len = V_strlen( pStr ) + 2; |
|
char *str = (char*)malloc(len*sizeof(char)); |
|
V_strncpy( str, pStr, len ); |
|
for ( int i = 0; i < len; i++ ) |
|
{ |
|
if ( str[i] == '\\' ) |
|
{ |
|
// allow escaping of bash special characters |
|
if ( i+1 < len && ( str[i+1] != '"' && str[i+1] != '$' && |
|
str[i+1] != '\'' && str[i+1] != '\\' ) ) |
|
str[i] = '/'; |
|
} |
|
if ( str[i] == '\0' ) |
|
break; |
|
} |
|
return str; |
|
} |
|
|
|
|
|
// pExt should be the bare extension without the . in front. i.e. "h", "cpp", "lib". |
|
static inline bool CheckExtension( const char *pFilename, const char *pExt ) |
|
{ |
|
Assert( pExt[0] != '.' ); |
|
|
|
int nFilenameLen = V_strlen( pFilename ); |
|
int nExtensionLen = V_strlen( pExt ); |
|
|
|
return (nFilenameLen > nExtensionLen && pFilename[nFilenameLen-nExtensionLen-1] == '.' && V_stricmp( &pFilename[nFilenameLen-nExtensionLen], pExt ) == 0 ); |
|
} |
|
|
|
|
|
static bool CheckExtensions( const char *pFilename, const char **ppExtensions ) |
|
{ |
|
for ( int i=0; ppExtensions[i] != NULL; i++ ) |
|
{ |
|
if ( CheckExtension( pFilename, ppExtensions[i] ) ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
static void GetObjFilenameForFile( const char *pConfigName, const char *pFilename, char *pOut, int maxLen ) |
|
{ |
|
char sBaseFilename[MAX_PATH]; |
|
V_FileBase( pFilename, sBaseFilename, sizeof( sBaseFilename ) ); |
|
//V_strlower( sBaseFilename ); |
|
|
|
// We had .cxx files -> .oxx, but this macro: |
|
// GENDEP_CXXFLAGS = -MD -MP -MF $(@:.o=.P) |
|
// was turning into -MF blah.oxx, which led to blah.oxx containing the file dependencies. |
|
// This obviously failed to link. Quick fix is to just have .cxx -> .o like everything else. |
|
//$ const char *pObjExtension = (CheckExtension( pFilename, "cxx" ) ? "oxx" : "o"); |
|
const char *pObjExtension = "o"; |
|
|
|
V_snprintf( pOut, maxLen, "$(OBJ_DIR)/%s.%s", sBaseFilename, pObjExtension ); |
|
} |
|
|
|
|
|
// This class drastically accelerates looking up which file creates which precompiled header. |
|
class CPrecompiledHeaderAccel |
|
{ |
|
public: |
|
void Setup( CUtlDict<CFileConfig*,int> &files, CFileConfig *pBaseConfig ) |
|
{ |
|
for ( int i=files.First(); i != files.InvalidIndex(); i=files.Next( i ) ) |
|
{ |
|
CFileConfig *pFile = files[i]; |
|
|
|
for ( int iSpecific=pFile->m_Configurations.First(); iSpecific != pFile->m_Configurations.InvalidIndex(); iSpecific=pFile->m_Configurations.Next( iSpecific ) ) |
|
{ |
|
CSpecificConfig *pSpecific = pFile->m_Configurations[iSpecific]; |
|
if ( pSpecific->m_bFileExcluded ) |
|
continue; |
|
|
|
// Does this file create a precompiled header? |
|
const char *pPrecompiledHeaderOption = pSpecific->GetOption( g_pOption_PrecompiledHeader ); |
|
if ( pPrecompiledHeaderOption && V_stristr( pPrecompiledHeaderOption, "Create" ) ) |
|
{ |
|
// Ok, which header do we scan through? |
|
const char *pUsePCHThroughFile = pSpecific->GetOption( g_pOption_UsePCHThroughFile ); |
|
if ( !pUsePCHThroughFile ) |
|
{ |
|
g_pVPC->VPCError( "File %s creates a precompiled header in config %s but no UsePCHThroughFile option specified.", pFile->m_Filename.String(), pSpecific->GetConfigName() ); |
|
} |
|
|
|
char sLookup[1024]; |
|
V_snprintf( sLookup, sizeof( sLookup ), "%s__%s", pSpecific->GetConfigName(), pUsePCHThroughFile ); |
|
|
|
if ( m_Lookup.Find( sLookup ) != m_Lookup.InvalidIndex() ) |
|
{ |
|
g_pVPC->VPCError( "File %s has UsePCHThroughFile of %s but another file already does.", pFile->m_Filename.String(), pUsePCHThroughFile ); |
|
} |
|
|
|
m_Lookup.Insert( sLookup, pFile ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
CFileConfig* FindFileThatCreatesPrecompiledHeader( const char *pConfigName, const char *pUsePCHThroughFile ) |
|
{ |
|
char sLookup[1024]; |
|
V_snprintf( sLookup, sizeof( sLookup ), "%s__%s", pConfigName, pUsePCHThroughFile ); |
|
|
|
int i = m_Lookup.Find( sLookup ); |
|
if ( i == m_Lookup.InvalidIndex() ) |
|
return NULL; |
|
else |
|
return m_Lookup[i]; |
|
} |
|
|
|
private: |
|
// This indexes whatever file creates a certain precompiled header for a certain config. |
|
// These are indexed as <config name>_<pchthroughfile>. |
|
// So an entry might look like release_cbase.h |
|
CUtlDict<CFileConfig*,int> m_Lookup; |
|
}; |
|
|
|
|
|
|
|
class CProjectGenerator_Makefile : public CBaseProjectDataCollector |
|
{ |
|
public: |
|
|
|
typedef CBaseProjectDataCollector BaseClass; |
|
|
|
CProjectGenerator_Makefile() : BaseClass( &g_RelevantPropertyNames ) |
|
{ |
|
} |
|
|
|
virtual void Setup() |
|
{ |
|
} |
|
|
|
virtual const char* GetProjectFileExtension() |
|
{ |
|
return "mak"; |
|
} |
|
|
|
virtual void EndProject() |
|
{ |
|
const char *pMakefileFilename = g_pVPC->GetOutputFilename(); |
|
|
|
CUtlString strProjectName = GetProjectName(); |
|
bool bProjectIsCurrent = g_pVPC->IsProjectCurrent( pMakefileFilename, false ); |
|
if ( g_pVPC->IsForceGenerate() || !bProjectIsCurrent ) |
|
{ |
|
g_pVPC->VPCStatus( true, "Saving makefile project for: '%s' File: '%s'", strProjectName.String(), g_pVPC->GetOutputFilename() ); |
|
WriteMakefile( pMakefileFilename ); |
|
} |
|
|
|
const char *pTargetPlatformName = g_pVPC->GetTargetPlatformName(); |
|
if ( !pTargetPlatformName ) |
|
g_pVPC->VPCError( "GetTargetPlatformName failed." ); |
|
|
|
if ( !V_stricmp( pTargetPlatformName, "LINUX32" ) || !V_stricmp( pTargetPlatformName, "LINUX64" ) ) |
|
{ |
|
if ( g_pVPC->IsForceGenerate() || !bProjectIsCurrent ) |
|
{ |
|
// Write a CodeLite project as well. |
|
char sFilename[MAX_PATH]; |
|
V_StripExtension( g_pVPC->GetOutputFilename(), sFilename, sizeof( sFilename ) ); |
|
CProjectGenerator_CodeLite codeLiteGenerator; |
|
codeLiteGenerator.GenerateCodeLiteProject( this, sFilename, pMakefileFilename ); |
|
} |
|
Term(); |
|
} |
|
else |
|
{ |
|
Term(); |
|
} |
|
} |
|
|
|
void WriteSourceFilesList( FILE *fp, const char *pListName, const char **pExtensions, const char *pConfigName ) |
|
{ |
|
fprintf( fp, "%s= \\\n", pListName ); |
|
for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next(i) ) |
|
{ |
|
CFileConfig *pFileConfig = m_Files[i]; |
|
if ( pFileConfig->IsExcludedFrom( pConfigName ) ) |
|
continue; |
|
|
|
const char *pFilename = m_Files[i]->m_Filename.String(); |
|
if ( CheckExtensions( pFilename, pExtensions ) ) |
|
{ |
|
if ( m_bForceLowerCaseFileName ) |
|
fprintf( fp, " %s \\\n", UsePOSIXSlashes( V_strlower((char *)pFilename) ) ); |
|
else |
|
fprintf( fp, " %s \\\n", UsePOSIXSlashes( pFilename ) ); |
|
} |
|
} |
|
fprintf( fp, "\n\n" ); |
|
} |
|
|
|
void WriteNonConfigSpecificStuff( FILE *fp ) |
|
{ |
|
fprintf( fp, "ifneq \"$(LINUX_TOOLS_PATH)\" \"\"\n" ); |
|
fprintf( fp, "TOOL_PATH = $(LINUX_TOOLS_PATH)/\n" ); |
|
fprintf( fp, "endif\n\n" ); |
|
|
|
// NAME |
|
char szName[256]; |
|
V_strncpy( szName, m_ProjectName.String(), sizeof(szName) ); |
|
MakeFriendlyProjectName( szName ); |
|
fprintf( fp, "NAME=%s\n", szName ); |
|
|
|
// SRCDIR |
|
char sSrcRootRelative[MAX_PATH]; |
|
g_pVPC->ResolveMacrosInString( "$SRCDIR", sSrcRootRelative, sizeof( sSrcRootRelative ) ); |
|
|
|
fprintf( fp, "SRCROOT=%s\n", UsePOSIXSlashes( sSrcRootRelative ) ); |
|
|
|
// TargetPlatformName |
|
const char *pTargetPlatformName; |
|
// forestw: if PLATFORM macro exists we should use its value, this accommodates overrides of PLATFORM in .vpc files |
|
macro_t *pMacro = g_pVPC->FindOrCreateMacro( "PLATFORM", false, NULL ); |
|
if ( pMacro ) |
|
pTargetPlatformName = pMacro->value.String(); |
|
else |
|
pTargetPlatformName = g_pVPC->GetTargetPlatformName(); |
|
if ( !pTargetPlatformName ) |
|
g_pVPC->VPCError( "GetTargetPlatformName failed." ); |
|
fprintf( fp, "TARGET_PLATFORM=%s\n", pTargetPlatformName ); |
|
fprintf( fp, "TARGET_PLATFORM_EXT=%s\n", g_pVPC->IsDedicatedBuild() ? "_srv" : "" ); |
|
fprintf( fp, "USE_VALVE_BINDIR=%s\n", ( g_pVPC->UseValveBinDir() ? "1" : "0" ) ); |
|
|
|
fprintf( fp, "PWD:=$(shell $(TOOL_PATH)pwd)\n" ); |
|
|
|
|
|
// Select debug config if no config is specified. |
|
fprintf( fp, "# If no configuration is specified, \"release\" will be used.\n" ); |
|
fprintf( fp, "ifeq \"$(CFG)\" \"\"\n" ); |
|
fprintf( fp, "\tCFG = release\n" ); |
|
fprintf( fp, "endif\n\n" ); |
|
} |
|
|
|
struct Filename_t |
|
{ |
|
char szFilename[MAX_PATH]; |
|
int iBasename; |
|
}; |
|
|
|
class FileSortSortFunc |
|
{ |
|
public: |
|
bool Less( const CFileConfig * const & src1, const CFileConfig * const & src2, void *pCtx ) |
|
{ |
|
return src1->m_nInsertOrder < src2->m_nInsertOrder; |
|
} |
|
}; |
|
|
|
void WriteVpcMacroDefines( CSpecificConfig *pConfig, FILE *fp ) |
|
{ |
|
// Add VPC macros marked to become defines. |
|
CUtlVector< macro_t* > macroDefines; |
|
g_pVPC->GetMacrosMarkedForCompilerDefines( macroDefines ); |
|
for ( int i=0; i < macroDefines.Count(); i++ ) |
|
{ |
|
macro_t *pMacro = macroDefines[i]; |
|
fprintf( fp, "-D%s=%s ", pMacro->name.String(), pMacro->value.String() ); |
|
} |
|
} |
|
|
|
void WriteConfigSpecificStuff( CSpecificConfig *pConfig, FILE *fp, CPrecompiledHeaderAccel *pAccel, CSpecificConfig *pConfig1 ) |
|
{ |
|
KeyValues *pKV = pConfig->m_pKV; |
|
|
|
// If we've got a pConfig1, then that means pConfig0 == pConfig1, except for $PreprocessorDefinitions. So don't special |
|
// case anything other than that one section. |
|
if( !pConfig1 ) |
|
{ |
|
fprintf( fp, "#\n#\n# CFG=%s\n#\n#\n\n", pConfig->GetConfigName() ); |
|
fprintf( fp, "ifeq \"$(CFG)\" \"%s\"\n\n", pConfig->GetConfigName() ); |
|
} |
|
|
|
// GCC_ExtraCompilerFlags |
|
// Hopefully, they don't ever need to use backslashes because we're turning them into forward slashes here. |
|
// If that does become a problem, we can put some token around the pathnames we need to be fixed up and leave the rest alone. |
|
fprintf( fp, "GCC_ExtraCompilerFlags=%s\n", UsePOSIXSlashes( pKV->GetString( g_pOption_ExtraCompilerFlags, "" ) ) ); |
|
|
|
// GCC_ExtraLinkerFlags |
|
fprintf( fp, "GCC_ExtraLinkerFlags=%s\n", pKV->GetString( g_pOption_ExtraLinkerFlags, "" ) ); |
|
|
|
// GCC_CustomVersionScript |
|
fprintf( fp, "GCC_CustomVersionScript=%s\n", pKV->GetString( g_pOption_CustomVersionScript, "" ) ); |
|
|
|
// EntryPoint |
|
fprintf( fp, "EntryPoint=%s\n", pKV->GetString( g_pOption_EntryPoint, "" ) ); |
|
|
|
// IgnoreAllDefaultLibraries |
|
fprintf( fp, "IgnoreAllDefaultLibraries=%s\n", pKV->GetString( g_pOption_IgnoreAllDefaultLibraries, "no" ) ); |
|
|
|
// BufferSecurityCheck |
|
fprintf( fp, "BufferSecurityCheck=%s\n", pKV->GetString( g_pOption_BufferSecurityCheck, "Yes" ) ); |
|
|
|
// SymbolVisibility |
|
fprintf( fp, "SymbolVisibility=%s\n", pKV->GetString( g_pOption_SymbolVisibility, "hidden" ) ); |
|
|
|
// TreatWarningsAsErrors |
|
fprintf( fp, "TreatWarningsAsErrors=%s\n", pKV->GetString( g_pOption_TreatWarningsAsErrors, "false" ) ); |
|
|
|
// OptimizerLevel |
|
fprintf( fp, "OptimizerLevel=%s\n", pKV->GetString( g_pOption_OptimizerLevel, "$(SAFE_OPTFLAGS_GCC_422)" ) ); |
|
|
|
// system libraries |
|
{ |
|
fprintf( fp, "SystemLibraries=" ); |
|
{ |
|
CSplitString libs( pKV->GetString( g_pOption_SystemLibraries ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
for ( int i=0; i < libs.Count(); i++ ) |
|
{ |
|
fprintf( fp, "-l%s ", libs[i] ); |
|
} |
|
} |
|
if ( !V_stricmp( g_pVPC->GetTargetPlatformName(), "OSX32" ) || !V_stricmp( g_pVPC->GetTargetPlatformName(), "OSX64" ) ) |
|
{ |
|
char rgchFrameworkCompilerFlags[1024]; rgchFrameworkCompilerFlags[0] = '\0'; |
|
CSplitString systemFrameworks( pKV->GetString( g_pOption_SystemFrameworks ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
for ( int i=0; i < systemFrameworks.Count(); i++ ) |
|
{ |
|
fprintf( fp, "-framework %s ", systemFrameworks[i] ); |
|
} |
|
CSplitString localFrameworks( pKV->GetString( g_pOption_LocalFrameworks ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
for ( int i=0; i < localFrameworks.Count(); i++ ) |
|
{ |
|
char rgchFrameworkName[MAX_PATH]; |
|
V_StripExtension( V_UnqualifiedFileName( localFrameworks[i] ), rgchFrameworkName, sizeof( rgchFrameworkName ) ); |
|
V_StripFilename( localFrameworks[i] ); |
|
fprintf( fp, "-F%s ", localFrameworks[i] ); |
|
fprintf( fp, "-framework %s ", rgchFrameworkName ); |
|
strcat( rgchFrameworkCompilerFlags, "-F" ); |
|
strcat( rgchFrameworkCompilerFlags, localFrameworks[i] ); |
|
} |
|
fprintf( fp, "\n" ); |
|
if ( rgchFrameworkCompilerFlags[0] ) |
|
// the colon here is important - and should probably get percolated to more places in our generated |
|
// makefiles - it means to perform the assignment once, rather than at evaluation time |
|
fprintf( fp, "GCC_ExtraCompilerFlags:=$(GCC_ExtraCompilerFlags) %s\n", rgchFrameworkCompilerFlags ); |
|
} |
|
else |
|
fprintf( fp, "\n" ); |
|
} |
|
|
|
macro_t *pMacro = g_pVPC->FindOrCreateMacro( "_DLL_EXT", false, NULL ); |
|
if ( pMacro ) |
|
fprintf( fp, "DLL_EXT=%s\n", pMacro->value.String() ); |
|
|
|
pMacro = g_pVPC->FindOrCreateMacro( "_SYM_EXT", false, NULL ); |
|
if ( pMacro ) |
|
fprintf( fp, "SYM_EXT=%s\n", pMacro->value.String() ); |
|
|
|
// ForceIncludes |
|
{ |
|
CSplitString outStrings( pKV->GetString( g_pOption_ForceInclude ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
fprintf( fp, "FORCEINCLUDES= " ); |
|
for ( int i=0; i < outStrings.Count(); i++ ) |
|
{ |
|
if ( V_strlen( outStrings[i] ) > 2 ) |
|
fprintf( fp, "-include %s ", UsePOSIXSlashes( outStrings[i] ) ); |
|
} |
|
} |
|
fprintf( fp, "\n" ); |
|
|
|
// DEFINES |
|
if( !pConfig1 ) |
|
{ |
|
CSplitString outStrings( pKV->GetString( g_pOption_PreprocessorDefinitions ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
fprintf( fp, "DEFINES= " ); |
|
for ( int i=0; i < outStrings.Count(); i++ ) |
|
{ |
|
fprintf( fp, "-D%s ", outStrings[i] ); |
|
} |
|
|
|
WriteVpcMacroDefines( pConfig, fp ); |
|
fprintf( fp, "\n" ); |
|
} |
|
else |
|
{ |
|
// pConfig0 and pConfig1 are the same other than this section. So write just this one out something like: |
|
// ifeq "$(CFG)" "debug" |
|
// DEFINES += -DDEBUG -D_DEBUG -DPOSIX ... |
|
// else |
|
// DEFINES += -DNDEBUG -DPOSIX ... |
|
// endif |
|
fprintf( fp, "ifeq \"$(CFG)\" \"%s\"\n", pConfig->GetConfigName() ); |
|
|
|
CSplitString outStrings0( pKV->GetString( g_pOption_PreprocessorDefinitions ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
fprintf( fp, "DEFINES += " ); |
|
for ( int i=0; i < outStrings0.Count(); i++ ) |
|
{ |
|
fprintf( fp, "-D%s ", outStrings0[i] ); |
|
} |
|
WriteVpcMacroDefines( pConfig, fp ); |
|
|
|
fprintf( fp, "\nelse\n" ); |
|
|
|
CSplitString outStrings1( pConfig1->m_pKV->GetString( g_pOption_PreprocessorDefinitions ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
fprintf( fp, "DEFINES += " ); |
|
for ( int i=0; i < outStrings1.Count(); i++ ) |
|
{ |
|
fprintf( fp, "-D%s ", outStrings1[i] ); |
|
} |
|
WriteVpcMacroDefines( pConfig1, fp ); |
|
|
|
fprintf( fp, "\nendif\n" ); |
|
} |
|
|
|
// INCLUDEDIRS |
|
{ |
|
CSplitString outStrings( pKV->GetString( g_pOption_AdditionalIncludeDirectories ), (const char**)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators) ); |
|
fprintf( fp, "INCLUDEDIRS += " ); |
|
for ( int i=0; i < outStrings.Count(); i++ ) |
|
{ |
|
char sDir[MAX_PATH]; |
|
V_strncpy( sDir, outStrings[i], sizeof( sDir ) ); |
|
if ( !V_stricmp( sDir, "$(IntDir)" ) ) |
|
V_strncpy( sDir, "$(OBJ_DIR)", sizeof( sDir ) ); |
|
|
|
V_FixSlashes( sDir, '/' ); |
|
fprintf( fp, "%s ", sDir ); |
|
} |
|
fprintf( fp, "\n" ); |
|
} |
|
// CONFTYPE |
|
if ( V_stristr( pKV->GetString( g_pOption_ConfigurationType ), "dll" ) ) |
|
{ |
|
fprintf( fp, "CONFTYPE=dll\n" ); |
|
|
|
// Write ImportLibrary for dll (so) builds. |
|
const char *pRelative = pKV->GetString( g_pOption_ImportLibrary, "" ); |
|
fprintf( fp, "IMPORTLIBRARY=%s\n", UsePOSIXSlashes( pRelative ) ); |
|
} |
|
else if ( V_stristr( pKV->GetString( g_pOption_ConfigurationType ), "lib" ) ) |
|
{ |
|
fprintf( fp, "CONFTYPE=lib\n" ); |
|
} |
|
else if ( V_stristr( pKV->GetString( g_pOption_ConfigurationType ), "exe" ) ) |
|
{ |
|
fprintf( fp, "CONFTYPE=exe\n" ); |
|
} |
|
else |
|
{ |
|
fprintf( fp, "CONFTYPE=***UNKNOWN***\n" ); |
|
} |
|
|
|
// GameOutputFile is where it copies OutputFile to. |
|
fprintf( fp, "GAMEOUTPUTFILE=%s\n", UsePOSIXSlashes( pKV->GetString( g_pOption_GameOutputFile, "" ) ) ); |
|
|
|
// TargetCopies are where OutputFile copies are placed. |
|
fprintf( fp, "TARGETCOPIES=%s\n", UsePOSIXSlashes( pKV->GetString( g_pOption_TargetCopies, "" ) ) ); |
|
|
|
// OutputFile is where it builds to. |
|
char szFixedOutputFile[MAX_PATH]; |
|
V_strncpy( szFixedOutputFile, pKV->GetString( g_pOption_OutputFile ), sizeof( szFixedOutputFile ) ); |
|
V_FixSlashes( szFixedOutputFile, '/' ); |
|
// This file uses a custom build step. |
|
char sFormattedOutputFile[MAX_PATH]; |
|
char szAbsPath[MAX_PATH]; |
|
V_MakeAbsolutePath( szAbsPath, sizeof(szAbsPath), szFixedOutputFile ); |
|
DoStandardVisualStudioReplacements( szFixedOutputFile, szAbsPath, sFormattedOutputFile, sizeof( sFormattedOutputFile ) ); |
|
|
|
fprintf( fp, "OUTPUTFILE=%s\n", sFormattedOutputFile ); |
|
|
|
fprintf( fp, "\n\n" ); |
|
|
|
// post build event |
|
char rgchPostBuildCommand[2048]; rgchPostBuildCommand[0] = '\0'; |
|
if ( pKV->GetString( g_pOption_PostBuildEventCommandLine, NULL ) ) |
|
{ |
|
V_strncpy( rgchPostBuildCommand, pKV->GetString( g_pOption_PostBuildEventCommandLine, NULL ), sizeof( rgchPostBuildCommand ) ); |
|
// V_StripPrecedingAndTrailingWhitespace( rgchPostBuildCommand ); |
|
} |
|
if ( V_strlen( rgchPostBuildCommand ) ) |
|
fprintf( fp, "POSTBUILDCOMMAND=%s\n", UsePOSIXSlashes( rgchPostBuildCommand ) ); |
|
else |
|
fprintf( fp, "POSTBUILDCOMMAND=/bin/true\n" ); |
|
|
|
fprintf( fp, "\n\n" ); |
|
|
|
|
|
// Write all the filenames. |
|
const char *sSourceFileExtensions[] = { "cpp", "cxx", "cc", "c", "mm", NULL }; |
|
fprintf( fp, "\n" ); |
|
WriteSourceFilesList( fp, "CPPFILES", (const char**)sSourceFileExtensions, pConfig->GetConfigName() ); |
|
|
|
// LIBFILES |
|
char sImportLibraryFile[MAX_PATH]; |
|
const char *pRelative = pKV->GetString( g_pOption_ImportLibrary, "" ); |
|
V_strncpy( sImportLibraryFile, UsePOSIXSlashes( pRelative ), sizeof( sImportLibraryFile ) ); |
|
V_RemoveDotSlashes( sImportLibraryFile ); |
|
|
|
char sOutputFile[MAX_PATH]; |
|
const char *pOutputFile = pKV->GetString( g_pOption_OutputFile, "" ); |
|
V_strncpy( sOutputFile, UsePOSIXSlashes( pOutputFile ), sizeof( sOutputFile ) ); |
|
V_RemoveDotSlashes( sOutputFile ); |
|
|
|
fprintf( fp, "LIBFILES = \\\n" ); |
|
|
|
// Get original order the link files were specified in the .vpc files. See: |
|
// http://stackoverflow.com/questions/45135/linker-order-gcc |
|
// TL;DR. Gcc does a single pass through the list of libraries to resolve references. |
|
// If library A depends on symbols in library B, library A should appear first so we |
|
// need to restore the original order to allow users to control link order via |
|
// their .vpc files. |
|
CUtlSortVector< CFileConfig *, FileSortSortFunc > OriginalSort; |
|
|
|
for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next(i) ) |
|
{ |
|
CFileConfig *pFileConfig = m_Files[i]; |
|
OriginalSort.InsertNoSort( pFileConfig ); |
|
} |
|
OriginalSort.RedoSort(); |
|
|
|
|
|
CUtlVector< Filename_t > ImportLib; |
|
CUtlVector< Filename_t > StaticLib; |
|
|
|
for ( int i=0; i < OriginalSort.Count(); i++ ) |
|
{ |
|
CFileConfig *pFileConfig = OriginalSort[i]; |
|
if ( pFileConfig->IsExcludedFrom( pConfig->GetConfigName() ) ) |
|
continue; |
|
|
|
Filename_t Filename; |
|
|
|
Filename.iBasename = 0; |
|
|
|
V_strncpy( Filename.szFilename, UsePOSIXSlashes( pFileConfig->m_Filename.String() ), sizeof( Filename.szFilename ) ); |
|
const char *pFilename = Filename.szFilename; |
|
if ( IsLibraryFile( pFilename ) ) |
|
{ |
|
char *pchFileName = (char*)V_strrchr( pFilename, '/' ) + 1; |
|
if (( !sImportLibraryFile[0] || V_stricmp( sImportLibraryFile, pFilename )) && // only link this as a library if it isn't our own output! |
|
( !sOutputFile[0] || V_stricmp( sOutputFile, pFilename))) |
|
{ |
|
char szExt[32]; |
|
V_ExtractFileExtension( pFilename, szExt, sizeof(szExt) ); |
|
if (!( !V_stricmp( g_pVPC->GetTargetPlatformName(), "OSX32" ) || !V_stricmp( g_pVPC->GetTargetPlatformName(), "OSX64" ) ) && |
|
IsLibraryFile( pFilename ) && (pchFileName-1) && pchFileName[0] == 'l' && pchFileName[1] == 'i' && pchFileName[2] == 'b' |
|
&& szExt[0] != 'a' ) // its a lib ext but not an archive file, link like a library |
|
{ |
|
*(pchFileName-1) = 0; |
|
|
|
// Cygwin import libraries use ".dll.a", so get rid of any file extensions here. |
|
char *pExt; |
|
while ( 1 ) |
|
{ |
|
pExt = (char*)V_strrchr( pchFileName, '.' ); |
|
if ( !pExt || V_strrchr( pchFileName, '/' ) > pExt || V_strrchr( pchFileName, '\\' ) > pExt ) |
|
break; |
|
|
|
*pExt = 0; |
|
} |
|
|
|
// +3 to dodge the lib ext |
|
Filename.iBasename = ( pchFileName - pFilename ) + 3; |
|
ImportLib.AddToTail( Filename ); |
|
} |
|
else |
|
{ |
|
StaticLib.AddToTail( Filename ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Spew static libs out first, then import libraries. Otherwise things like bsppack |
|
// will fail to link because libvstdlib.so came before tier1.a. |
|
for( int i = 0; i < StaticLib.Count(); i++ ) |
|
{ |
|
fprintf( fp, " %s \\\n", StaticLib[ i ].szFilename ); |
|
} |
|
for( int i = 0; i < ImportLib.Count(); i++ ) |
|
{ |
|
fprintf( fp, " -L%s -l%s \\\n", ImportLib[ i ].szFilename, &ImportLib[ i ].szFilename[ ImportLib[ i ].iBasename ] ); // +3 to dodge the lib ext |
|
} |
|
|
|
fprintf( fp, "\n\n" ); |
|
|
|
fprintf( fp, "LIBFILENAMES = \\\n" ); |
|
for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next(i) ) |
|
{ |
|
CFileConfig *pFileConfig = m_Files[i]; |
|
if ( pFileConfig->IsExcludedFrom( pConfig->GetConfigName() ) ) |
|
continue; |
|
|
|
const char *pFilename = pFileConfig->m_Filename.String(); |
|
if ( IsLibraryFile( pFilename ) ) |
|
{ |
|
if (( !sImportLibraryFile[0] || V_stricmp( sImportLibraryFile, pFilename ) ) && // only link this as a library if it isn't our own output! |
|
( !sOutputFile[0] || V_stricmp( sOutputFile, pFilename))) |
|
{ |
|
fprintf( fp, " %s \\\n", UsePOSIXSlashes( pFilename ) ); |
|
} |
|
} |
|
} |
|
|
|
fprintf( fp, "\n\n" ); |
|
|
|
|
|
CUtlVector< CUtlString > otherDependencies; |
|
static const char *sDependenciesSeparators[] = { ";", "\r", "\n" }; |
|
|
|
// Scan the list of files for any generated dependencies so we can pull them up front |
|
for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next(i) ) |
|
{ |
|
CFileConfig *pFileConfig = m_Files[i]; |
|
CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( pConfig->GetConfigName(), pConfig ); |
|
|
|
if ( pFileConfig->IsExcludedFrom( pConfig->GetConfigName() ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
const char *pCustomBuildCommandLine = pFileSpecificData->GetOption( g_pOption_CustomBuildStepCommandLine ); |
|
const char *pOutputFile = pFileSpecificData->GetOption( g_pOption_Outputs ); |
|
if ( pOutputFile && pCustomBuildCommandLine && V_strlen( pCustomBuildCommandLine ) > 0 ) |
|
{ |
|
char szTempFilename[MAX_PATH]; |
|
V_strncpy( szTempFilename, UsePOSIXSlashes( pFileConfig->m_Filename.String() ), sizeof( szTempFilename ) ); |
|
const char *pFilename = szTempFilename; |
|
|
|
// This file uses a custom build step. |
|
char sFormattedOutputFile[8192]; |
|
char szAbsPath[MAX_PATH]; |
|
V_MakeAbsolutePath( szAbsPath, sizeof(szAbsPath), pFilename ); |
|
DoStandardVisualStudioReplacements( pOutputFile, szAbsPath, sFormattedOutputFile, sizeof( sFormattedOutputFile ) ); |
|
|
|
CSplitString outFiles( sFormattedOutputFile, sDependenciesSeparators, sizeof( sDependenciesSeparators ) / sizeof( sDependenciesSeparators[ 0 ] ) ); |
|
|
|
for ( int i = 0; i < outFiles.Count(); i ++ ) |
|
{ |
|
const char *pchOneFile = outFiles[ i ]; |
|
if ( *pchOneFile == '\0' ) |
|
{ |
|
continue; |
|
} |
|
// Remember this as a dependency so the executable will depend on it. |
|
if ( otherDependencies.Find( pchOneFile ) == otherDependencies.InvalidIndex() ) |
|
otherDependencies.AddToTail( pchOneFile ); |
|
} |
|
} |
|
} |
|
|
|
WriteOtherDependencies( fp, otherDependencies ); |
|
fprintf( fp, "\n\n" ); |
|
|
|
// Include the base makefile before the rules to build the .o files. |
|
// Do this after we output otherDependencies definition since $(OTHER_DEPENDENCIES) is a dependency of all: target |
|
const char *sMakeFileDependency = ""; |
|
bool bCondPOSIX = g_pVPC->FindOrCreateConditional( "POSIX", false, CONDITIONAL_NULL ) != NULL; |
|
fprintf( fp, "# Include the base makefile now.\n" ); |
|
if ( bCondPOSIX ) |
|
{ |
|
sMakeFileDependency = k_pszBase_Makefile; |
|
fprintf( fp, "include %s\n\n\n", sMakeFileDependency ); |
|
} |
|
|
|
// Now write the rules to build the .o files. |
|
// .o files go in [project dir]/obj/[config]/[base filename] |
|
for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next(i) ) |
|
{ |
|
CFileConfig *pFileConfig = m_Files[i]; |
|
CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( pConfig->GetConfigName(), pConfig ); |
|
|
|
if ( pFileConfig->IsExcludedFrom( pConfig->GetConfigName() ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
char szTempFilename[MAX_PATH]; |
|
V_strncpy( szTempFilename, UsePOSIXSlashes( pFileConfig->m_Filename.String() ), sizeof( szTempFilename ) ); |
|
const char *pFilename = szTempFilename; |
|
|
|
// Custom build steps?? |
|
const char *pCustomBuildCommandLine = pFileSpecificData->GetOption( g_pOption_CustomBuildStepCommandLine ); |
|
const char *pOutputFile = pFileSpecificData->GetOption( g_pOption_Outputs ); |
|
if ( pOutputFile && pCustomBuildCommandLine && V_strlen( pCustomBuildCommandLine ) > 0 ) |
|
{ |
|
// This file uses a custom build step. |
|
char sFormattedOutputFile[8192]; |
|
char sFormattedCommandLine[8192]; |
|
char sFormattedDependencies[8192]; |
|
DoStandardVisualStudioReplacements( pCustomBuildCommandLine, UsePOSIXSlashes( pFilename ), \ |
|
sFormattedCommandLine, sizeof( sFormattedCommandLine ) ); |
|
DoStandardVisualStudioReplacements( pOutputFile, UsePOSIXSlashes( pFilename ), \ |
|
sFormattedOutputFile, sizeof( sFormattedOutputFile ) ); |
|
|
|
|
|
// AdditionalDependencies only applies to custom build steps, not normal compilation steps |
|
const char *pAdditionalDeps = pFileSpecificData->GetOption( g_pOption_AdditionalDependencies ); |
|
if ( pAdditionalDeps ) |
|
DoStandardVisualStudioReplacements( pAdditionalDeps, szAbsPath, sFormattedDependencies, sizeof( sFormattedDependencies ) ); |
|
else |
|
sFormattedDependencies[0] = 0; |
|
|
|
CSplitString additionalDeps( sFormattedDependencies, sDependenciesSeparators, sizeof( sDependenciesSeparators ) / sizeof( sDependenciesSeparators[0] ) ); |
|
FOR_EACH_VEC_BACK( additionalDeps, i ) |
|
{ |
|
const char *pchOneFile = additionalDeps[i]; |
|
if ( *pchOneFile == '\0' ) |
|
{ |
|
additionalDeps.Remove( i ); |
|
} |
|
} |
|
|
|
CSplitString outFiles( sFormattedOutputFile, sDependenciesSeparators, sizeof( sDependenciesSeparators ) / sizeof( sDependenciesSeparators[ 0 ] ) ); |
|
FOR_EACH_VEC_BACK( outFiles, i ) |
|
{ |
|
const char *pchOneFile = outFiles[ i ]; |
|
if ( *pchOneFile == '\0' ) |
|
{ |
|
outFiles.Remove( i ); |
|
} |
|
} |
|
|
|
char rgchIntermediateFile[MAX_PATH]; |
|
rgchIntermediateFile[0] = 0; |
|
|
|
// ABSPATH NOTE |
|
// |
|
// Make will not do what we expect with ../../this/dir/foo.cpp, and these are often the result of nested macros |
|
// in VPC files, so ensure we are giving make absolute paths for our targets via $(abspath ...) |
|
|
|
if ( outFiles.Count() == 1 ) |
|
{ |
|
// one output file: create a standard rule -- output : input \n \t command |
|
fprintf( fp, "\n$(abspath %s) ", outFiles[0] ); |
|
} |
|
else |
|
{ |
|
// multiple output files: DO NOT DO THIS -- output output output : input \n \t command |
|
// as this will cause up to three parallel invocations of command! |
|
// best-practice workaround is to create a temp intermediate file |
|
static int s_uniqueId = 0; |
|
V_snprintf( rgchIntermediateFile, sizeof(rgchIntermediateFile), "$(OBJ_DIR)/_custombuildstep_%d.touchfile", s_uniqueId ); |
|
fprintf( fp, "\n%s ", rgchIntermediateFile ); |
|
++s_uniqueId; |
|
|
|
V_strcat( sFormattedCommandLine, CFmtStrMax( "\n@touch %s", rgchIntermediateFile ), sizeof(sFormattedCommandLine) ); |
|
} |
|
// Outputs dependent on input file and .mak file |
|
fprintf( fp, ": $(abspath %s) %s", UsePOSIXSlashes( pFilename ), g_pVPC->GetOutputFilename() ); |
|
FOR_EACH_VEC( additionalDeps, i ) |
|
{ |
|
fprintf( fp, " %s", additionalDeps[i] ); |
|
} |
|
/// XXX(JohnS): Was this double-added as an accident, or is there some arcane make reason to have it be the |
|
/// first and last dep? |
|
fprintf( fp, " $(abspath %s)\n", UsePOSIXSlashes( pFilename ) ); |
|
const char *pDescription = pFileSpecificData->GetOption( g_pOption_Description ); |
|
DoStandardVisualStudioReplacements( pDescription, UsePOSIXSlashes( pFilename ), \ |
|
sFormattedOutputFile, sizeof( sFormattedOutputFile ) ); |
|
|
|
fprintf( fp, "\t @echo \"%s\";mkdir -p $(OBJ_DIR) 2> /dev/null;\n", sFormattedOutputFile ); |
|
|
|
static const char *sSeparators[] = { "\r", "\n" }; |
|
CSplitString outLines( sFormattedCommandLine, sSeparators, sizeof( sSeparators ) / sizeof( sSeparators[ 0 ] ) ); |
|
for ( int i = 0; i < outLines.Count(); ++i ) |
|
{ |
|
const char *pchOneLine = outLines[ i ]; |
|
if ( *pchOneLine == '\0' ) |
|
{ |
|
continue; |
|
} |
|
fprintf( fp, "\t %s\n", pchOneLine ); |
|
} |
|
fprintf( fp, "\n" ); |
|
|
|
// for multiple output files, create a dependency between output files and intermediate file + makefile |
|
if ( outFiles.Count() > 1 ) |
|
{ |
|
FOR_EACH_VEC( outFiles, i ) |
|
{ |
|
// See ABSPATH NOTE above |
|
fprintf( fp, "$(abspath %s) : %s %s\n\t @touch %s\n\n", outFiles[i], rgchIntermediateFile, g_pVPC->GetOutputFilename(), outFiles[i] ); |
|
} |
|
} |
|
} |
|
else if ( CheckExtensions( pFilename, (const char**)sSourceFileExtensions ) ) |
|
{ |
|
char sObjFilename[MAX_PATH]; |
|
GetObjFilenameForFile( pConfig->GetConfigName(), pFilename, sObjFilename, sizeof( sObjFilename ) ); |
|
|
|
// Get the base obj filename for the .P file. |
|
char sPFileBase[MAX_PATH]; |
|
V_StripExtension( sObjFilename, sPFileBase, sizeof( sPFileBase ) ); |
|
|
|
fprintf( fp, "\nifneq (clean, $(findstring clean, $(MAKECMDGOALS)))\n" ); |
|
fprintf( fp, "-include %s.P\n", sPFileBase ); |
|
fprintf( fp, "endif\n" ); |
|
|
|
bool bUsedPrecompiledHeader = false; |
|
|
|
// Handle precompiled header options. |
|
const char *pPrecompiledHeaderOption = pFileSpecificData->GetOption( g_pOption_PrecompiledHeader ); |
|
const char *pUsePCHThroughFile = pFileSpecificData->GetOption( g_pOption_UsePCHThroughFile ); |
|
|
|
if ( !g_pVPC->IsPosixPCHDisabled() && pPrecompiledHeaderOption && pUsePCHThroughFile ) |
|
{ |
|
char sIncludeFilename[MAX_PATH]; |
|
const char *pHeaderFileName = V_GetFileName( pUsePCHThroughFile ); |
|
V_snprintf( sIncludeFilename, sizeof( sIncludeFilename ), "$(OBJ_DIR)/%s", pHeaderFileName ); |
|
|
|
// Note that we use $(abspath ...) for most things, but the provided PCH file (pUsePCHThroughFile) |
|
// is actually a include filename -- that is, it is meant to be found the same way #include "pch" |
|
// would be. We use make's vpath directive to tell it to resolve this file relative to the |
|
// INCLUDEDIRS list we conveniently have, (starting with PWD to mimic c/c++ include searching), but |
|
// that means we have to be careful to always reference it as a dependency the same way. |
|
// |
|
// However, since we explicitly opt-in to files using said PCH, we can just specifically set the PCH |
|
// as a dependency of every file that has opted-in to use it, so we don't run into issues with |
|
// things like the compiler's -MD dependencies referring to it differently. |
|
if ( V_stristr( pPrecompiledHeaderOption, "Not Using" ) ) |
|
{ |
|
// Don't do anything special if this file doesn't want to use a precompiled header. |
|
} |
|
else if ( V_stristr( pPrecompiledHeaderOption, "Create" ) ) |
|
{ |
|
// Compile pUsePCHThroughFile and output it to obj/<config>/filename.h.gch |
|
fprintf( fp, "\n%s.gch : %s $(PWD)/%s %s $(OTHER_DEPENDENCIES)\n", |
|
sIncludeFilename, pUsePCHThroughFile, g_pVPC->GetOutputFilename(), sMakeFileDependency ); |
|
fprintf( fp, "\t$(PRE_COMPILE_FILE)\n" ); |
|
fprintf( fp, "\t$(COMPILE_PCH) $(POST_COMPILE_FILE)\n" ); |
|
|
|
// include the .P it spits out, ensuring it is marked as depending on the gch build finishing so |
|
// we don't include a stale one. |
|
fprintf( fp, "\n%s.P : %s.gch\n", sIncludeFilename, sIncludeFilename ); |
|
fprintf( fp, "\nvpath %s . $(INCLUDEDIRS)\n", pUsePCHThroughFile ); |
|
fprintf( fp, "\nifneq (clean, $(findstring clean, $(MAKECMDGOALS)))\n" ); |
|
fprintf( fp, "include %s.P\n", sIncludeFilename ); |
|
fprintf( fp, "endif\n" ); |
|
|
|
// Create obj/<config>/filename.h as well, depending on the GCH so it is re-copied when we |
|
// recompile it. |
|
// |
|
// Because we pass this as -include <foo> to the compiler, this allows conditions where the PCH |
|
// cannot be used to fall back to the compiler simply using the .h, rather than failing |
|
// entirely. |
|
fprintf( fp, "\n%s : %s %s.gch $(PWD)/%s %s\n", |
|
sIncludeFilename, pUsePCHThroughFile, sIncludeFilename, g_pVPC->GetOutputFilename(), sMakeFileDependency ); |
|
fprintf( fp, "\tcp -f $< %s\n", sIncludeFilename ); |
|
} |
|
else if ( V_stristr( pPrecompiledHeaderOption, "Use" ) ) |
|
{ |
|
CFileConfig *pCreator = pAccel->FindFileThatCreatesPrecompiledHeader( pConfig->GetConfigName(), pUsePCHThroughFile ); |
|
if ( pCreator && !pCreator->IsExcludedFrom( pConfig->GetConfigName() ) ) |
|
{ |
|
const char *pCompileAsOption = pFileSpecificData->GetOption( g_pOption_CompileAs ); |
|
fprintf( fp, "\n%s : TARGET_PCH_FILE = %s\n", sObjFilename, sIncludeFilename ); |
|
fprintf( fp, "%s : $(abspath %s) %s.gch %s $(PWD)/%s %s\n", |
|
sObjFilename, UsePOSIXSlashes( pFilename ), sIncludeFilename, sIncludeFilename, |
|
g_pVPC->GetOutputFilename(), sMakeFileDependency ); |
|
fprintf( fp, "\t$(PRE_COMPILE_FILE)\n" ); |
|
if ( pCompileAsOption && strstr( pCompileAsOption, "(/TC)" ) ) // Compile as C code (/TC) |
|
{ |
|
fprintf( fp, "\t$(COMPILE_FILE_WITH_PCH_C) $(POST_COMPILE_FILE)\n" ); |
|
} |
|
else |
|
{ |
|
fprintf( fp, "\t$(COMPILE_FILE_WITH_PCH) $(POST_COMPILE_FILE)\n" ); |
|
} |
|
bUsedPrecompiledHeader = true; |
|
} |
|
} |
|
} |
|
|
|
if ( !bUsedPrecompiledHeader ) |
|
{ |
|
const char *pCompileAsOption = pFileSpecificData->GetOption( g_pOption_CompileAs ); |
|
fprintf( fp, "\n%s : $(abspath %s) $(PWD)/%s %s $(OTHER_DEPENDENCIES)\n", |
|
sObjFilename, UsePOSIXSlashes( pFilename ), g_pVPC->GetOutputFilename(), sMakeFileDependency ); |
|
fprintf( fp, "\t$(PRE_COMPILE_FILE)\n" ); |
|
if ( pCompileAsOption && strstr( pCompileAsOption, "(/TC)" ) ) // Compile as C code (/TC) |
|
{ |
|
fprintf( fp, "\t$(COMPILE_FILE_C) $(POST_COMPILE_FILE)\n" ); |
|
} |
|
else |
|
{ |
|
fprintf( fp, "\t$(COMPILE_FILE) $(POST_COMPILE_FILE)\n" ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if( !pConfig1 ) |
|
{ |
|
fprintf( fp, "\n\nendif # (CFG=%s)\n\n", pConfig->GetConfigName() ); |
|
fprintf( fp, "\n\n" ); |
|
} |
|
} |
|
|
|
void WriteOtherDependencies( FILE *fp, CUtlVector< CUtlString > &otherDependencies ) |
|
{ |
|
fprintf( fp, "\nOTHER_DEPENDENCIES = \\\n" ); |
|
for ( int i=0; i < otherDependencies.Count(); i++ ) |
|
{ |
|
fprintf( fp, "\t$(abspath %s)%s\n", UsePOSIXSlashes( otherDependencies[i].String() ), |
|
(i == otherDependencies.Count()-1) ? "" : " \\" ); |
|
} |
|
fprintf( fp, "\n\n" ); |
|
fprintf( fp, "-include $(OBJ_DIR)/_other_deps.P\n" ); |
|
} |
|
|
|
bool CheckReleaseDebugConfigsAreSame() |
|
{ |
|
if ( g_pVPC->IsVerboseMakefile() ) |
|
return false; |
|
|
|
// Go through two configs and check to see that all the strings except the defines are the same. |
|
// If so, we can write the entire makefile and only special case the $PreprocessorDefinitions portion. |
|
// Makes for a much simpler makefile to read and modify when testing various flags, etc. |
|
if( m_BaseConfigData.m_Configurations.Count() == 2 ) |
|
{ |
|
CSpecificConfig *pConfig0 = m_BaseConfigData.m_Configurations[0]; |
|
CSpecificConfig *pConfig1 = m_BaseConfigData.m_Configurations[1]; |
|
KeyValues *pKV0 = pConfig0->m_pKV; |
|
KeyValues *pKV1 = pConfig1->m_pKV; |
|
|
|
KeyValues *val0 = pKV0->GetFirstValue(); |
|
KeyValues *val1 = pKV1->GetFirstValue(); |
|
for( ;; ) |
|
{ |
|
// If one has run out and the other hasn't, bail. |
|
if( !val0 != !val1 ) |
|
break; |
|
|
|
if( !val0 ) |
|
{ |
|
// We've hit the end of both keyvalues, and everything was the same. |
|
Assert( !val1 ); |
|
return true; |
|
} |
|
|
|
// If the datatypes aren't strings or aren't the same, bail. |
|
if( ( val0->GetDataType() != KeyValues::TYPE_STRING ) || ( val0->GetDataType() != val1->GetDataType() ) ) |
|
break; |
|
|
|
// If the keynames differ, bail. |
|
if( V_strcmp( val0->GetName(), val1->GetName() ) ) |
|
break; |
|
|
|
// If this isn't the $PreprocessorDefinitions key, check the values. |
|
if( V_strcmp( val0->GetName(), g_pOption_PreprocessorDefinitions ) ) |
|
{ |
|
if( V_strcmp( val0->GetString(), val1->GetString() ) ) |
|
break; |
|
|
|
// look for visual studio macros and assume those evaluate to config specific values |
|
if ( V_strstr( val0->GetString(), "$(" ) ) |
|
break; |
|
} |
|
|
|
// Next. |
|
val0 = val0->GetNextValue(); |
|
val1 = val1->GetNextValue(); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void WriteMakefile( const char *pFilename ) |
|
{ |
|
FILE *fp = fopen( pFilename, "wt" ); |
|
|
|
CPrecompiledHeaderAccel accel; |
|
accel.Setup( m_Files, &m_BaseConfigData ); |
|
|
|
m_bForceLowerCaseFileName = false; |
|
|
|
// Write all the non-config-specific stuff. |
|
WriteNonConfigSpecificStuff( fp ); |
|
|
|
bool bReleaseDebugAreSame = CheckReleaseDebugConfigsAreSame(); |
|
if( bReleaseDebugAreSame ) |
|
{ |
|
// If the two configs are the same except for $PreprocessorDefinitions, then call |
|
// WriteConfigSpecificStuff() once with both configs. |
|
Assert( m_BaseConfigData.m_Configurations.Count() == 2 ); |
|
CSpecificConfig *pConfig0 = m_BaseConfigData.m_Configurations[0]; |
|
CSpecificConfig *pConfig1 = m_BaseConfigData.m_Configurations[1]; |
|
|
|
// Make sure we always have "release" second (ie, the default case). |
|
if( !V_stricmp( pConfig0->GetConfigName(), "release" ) ) |
|
{ |
|
CSpecificConfig *pConfigTemp = pConfig0; |
|
pConfig0 = pConfig1; |
|
pConfig1 = pConfigTemp; |
|
} |
|
|
|
m_bForceLowerCaseFileName = pConfig0->m_pKV->GetBool( g_pOption_LowerCaseFileNames, false ); |
|
WriteConfigSpecificStuff( pConfig0, fp, &accel, pConfig1 ); |
|
} |
|
else |
|
{ |
|
// Write each config out. |
|
for ( int i=m_BaseConfigData.m_Configurations.First(); i != m_BaseConfigData.m_Configurations.InvalidIndex(); i=m_BaseConfigData.m_Configurations.Next( i ) ) |
|
{ |
|
CSpecificConfig *pConfig = m_BaseConfigData.m_Configurations[i]; |
|
m_bForceLowerCaseFileName = pConfig->m_pKV->GetBool( g_pOption_LowerCaseFileNames, false ); |
|
WriteConfigSpecificStuff( pConfig, fp, &accel, NULL ); |
|
} |
|
} |
|
|
|
fclose( fp ); |
|
Sys_CopyToMirror( pFilename ); |
|
} |
|
|
|
bool m_bForceLowerCaseFileName; |
|
}; |
|
|
|
static CProjectGenerator_Makefile g_ProjectGenerator_Makefile; |
|
IBaseProjectGenerator* GetMakefileProjectGenerator() |
|
{ |
|
return &g_ProjectGenerator_Makefile; |
|
}
|
|
|