csgo-2018-source/common/clang/clang_utils.cpp
2021-07-24 21:11:47 -07:00

343 lines
15 KiB
C++

//========= Copyright 1996-2016, Valve Corporation, All rights reserved. ============//
//
// Purpose: Clang utility code
//
//=====================================================================================//
#include "clang/clang_utils.h"
#include "tier1/strtools.h"
#include "tier1/utlstring.h"
#include "tier1/utlvector.h"
#include "environment_utils.h"
// Include the main Clang interface header:
#include "clang-c/Index.h"
CUtlString & TransformArgumentForCommandLineUsage( CUtlString &argument, bool bEscapeContainedQuotes )
{
bool bHasSpace = V_containsWhitespace( argument.Get() );
if ( V_strstr( argument.Get(), "\"" ) )
{
if ( bEscapeContainedQuotes )
{
// Transform " to \"
char temp[ MAX_PATH ];
bool bResult = V_StrSubst( argument.Get(), "\"", "\\\"", temp, ARRAYSIZE( temp ), true );
NOTE_UNUSED( bResult );
Assert( bResult );
argument = temp;
}
// TODO: support arguments containing both quotes and spaces, e.g: -Dmystring="hi there"
Assert( !bHasSpace );
}
if ( bHasSpace )
{
// Wrap the argument with quotes
argument = CUtlString( "\"" ) + argument + "\"";
}
// Append a space to separate arguments
argument += " ";
return argument;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_IsHeaderFile( const char *pFilename )
{
// These extensions are used for header files in VPCs in the Source2 tree:
// TODO: move this util into VPC common code... optimize it
if ( V_striEndsWith( pFilename, ".h" ) ) return true;
if ( V_striEndsWith( pFilename, ".inl" ) ) return true;
return false;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_IsSourceFile( const char *pFilename )
{
// These extensions are used for source files in VPCs in the Source2 tree:
// TODO: move this util into VPC common code... optimize it
if ( V_striEndsWith( pFilename, ".cpp" ) ) return true;
if ( V_striEndsWith( pFilename, ".cc" ) ) return true;
if ( V_striEndsWith( pFilename, ".c" ) ) return true;
return false;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_AddCompilerOptions( CUtlVector< CUtlString > &arguments, const char *pCompiler )
{
if ( !V_stricmp_fast( pCompiler, "VS2005" ) || !V_stricmp_fast( pCompiler, "VS2010" ) )
{
// Add Microsoft extensions/compatibility
arguments.AddToTail( "-fms-extensions" );
arguments.AddToTail( "-fms-compatibility" );
arguments.AddToTail( "-mms-bitfields" );
// Omit '_MSC_VER' (we'll get this in VPC's list of defines)
arguments.AddToTail( "-fmsc-version=0" );
// Define the appropriate C/C++ standard
if ( !V_stricmp_fast( pCompiler, "VS2005" ) )
{
arguments.AddToTail( "-std=c++98" );
return true;
}
if ( !V_stricmp_fast( pCompiler, "VS2010" ) )
{
// [sergiy 2012/10/09] this used to crash for src/particles/builtin_constraints.cpp (get the latest Clang drop and/or submit a minimal repro)
// I recompiled clang and it doesn't seem to crash, so I'm swithing it to C++11 to get Rubikon ( which uses c++11 features) to compile. But we can switch back any time
arguments.AddToTail( "-std=c++11" );
//arguments.AddToTail( "-std=c++98" );
arguments.AddToTail( "-fmsc-version=1600" );
return true;
}
}
// NOTE: clang defaults to the 'gnu99' C/C++ standard
// we may want to use '-std=gnu++11' when we add mac+linux support
arguments.AddToTail( "-std=gnu99" );
AssertMsg1( false, "ERROR: Clang_GenerateOptions not implemented for this compiler yet! (%s)\n", pCompiler );
return false;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_AddPlatformOptions( CUtlVector< CUtlString > &arguments, const char *pPlatform )
{
// See LLVM\include\llvm\ADT\Triple.h and LLVM\tools\clang\lib\Basic\Targets.cpp
// for information about supported targets... the target 'triple' is of the form:
//
// architecture-vendor-os[-environment]
//
// The fourth part is optional. Some of the supported values:
//
// [x86|x86_64|ppc|ppc64] - [pc|apple|scei|unknown] - [win32|mingw32|cygwin|macosx|lv2|linux] - [gnu|ANDROIDEABI]
if ( !V_stricmp_fast( pPlatform, "WIN32" ) )
{
// TODO: might get better support with 'mingw32' or 'cygwin' than 'win32' (wtf are these things??)
arguments.AddToTail( "-target" );
arguments.AddToTail( "i686-pc-win32" );
return true;
}
if ( !V_stricmp_fast( pPlatform, "WIN64" ) )
{
// Need to tell Clang if this is a 64-bit CPU (or it will complain about usage of size_t in operator new)
// [ NOTE: the option '-m64' will internally transform i686-pc-win32 into x86_64-pc-win32 ]
arguments.AddToTail( "-target" );
arguments.AddToTail( "x86_64-pc-win32" );
// Define away unsupported intrinsics (breaks Rubikon due to __debugbreak being declared in system header intrin.h). We can put it back any time
//arguments.AddToTail( "-D__debugbreak(...)=__asm { int 3 }" );
return true;
}
if ( !V_stricmp_fast( pPlatform, "OSX" ) )
{
// TODO: for OSX/iOS, the os string should include a version number, e.g 'macosx10.8.0'
AssertMsg( 0, "Untested!\n" );
arguments.AddToTail( "-target" );
arguments.AddToTail( "ppc-apple-macosx" );
return true;
}
if ( !V_stricmp_fast( pPlatform, "X360" ) )
{
// TODO: figure out which target triple to use... we want to pretend we're using PPC for Win32 (Clang doesn't know about XBox)
AssertMsg( 0, "Untested!\n" );
arguments.AddToTail( "-target" );
arguments.AddToTail( "ppc-pc-win32" );
return true;
}
if ( !V_stricmp_fast( pPlatform, "PS3" ) )
{
// TODO: PS3 uses 'lv2' for OS and 'scei' for the vendor, but for some reason 'ppc64-scei-lv2' doesn't work.
// NOTE: SPU compilation is also supported, via 'cellspu-scei-lv2'
AssertMsg( 0, "Untested!\n" );
arguments.AddToTail( "-target" );
arguments.AddToTail( "ppc64-scei-lv2" );
return true;
}
AssertMsg1( false, "ERROR: Clang_GenerateOptions not implemented for this platform yet! (%s)\n", pPlatform );
return false;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_AddSystemIncludePaths( CUtlVector< CUtlString > &arguments, const char *pPlatform, const char *pCompiler )
{
CUtlVector< CUtlString > systemPaths;
if ( !GetSystemIncludePaths( systemPaths, pPlatform, pCompiler ) )
{
AssertMsg2( false, "ERROR: Clang_GenerateOptions not implemented for this platform/compiler yet! (%s/%s)\n", pPlatform, pCompiler );
return false;
}
// System include paths (added with '-isystem') will be searched *after* all the regular '-I' includes
// [http://gcc.gnu.org/onlinedocs/cpp/System-Headers.html]
CUtlString includePrefix( "-isystem" );
for ( int i=0; i < systemPaths.Count(); i++ )
{
arguments.AddToTail( includePrefix + systemPaths[i] );
}
return true;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_GenerateCommandString( CUtlString &command, bool bForLibClangDLL, const char *pFilename, const char *pPlatform, const char *pCompiler,
const CUtlVector< CUtlString > &defines, const CUtlVector< CUtlString > &includes,
const CUtlString &pchName, bool bBuildPCH, int parseFlags )
{
CUtlVector< CUtlString > arguments;
arguments.EnsureCapacity( 512 ); // Avoid needless string copying
command.Clear();
// Detect file type (source or header)
bool bIsHeader = Clang_IsHeaderFile( pFilename );
if ( !bIsHeader && !Clang_IsSourceFile( pFilename ) )
{
AssertMsg2( Clang_IsHeaderFile( pFilename ) || Clang_IsSourceFile( pFilename ), "ERROR (%s): unrecognised file type '%s'!\n", __FUNCTION__, pFilename );
return false;
}
if ( bIsHeader )
{
// By default, Clang interprets '.h' files as C, not C++:
// TODO: should we just always specify "c++"? (i.e should we parse ".c" files as C++? and is "c++-header" actually better for headers?)
arguments.AddToTail( "-x" ); // "Treat subsequent input files as having type <language>"
arguments.AddToTail( "c++-header" );
}
if ( !bBuildPCH )
{
// TODO: -fsyntax-only yields invalid PCHs, but building without it causes clang to crash on other files (e.g src/client/c_baseanimating.cpp)
// need to drill into this (build clang in debug, make a minimal repro), so we can submit bug reports
arguments.AddToTail( "-fsyntax-only" ); // "Check the code for syntax errors, but don't do anything beyond that"
}
// Add standard options
arguments.AddToTail( "-c" ); //[ignored] "Compile or assemble the source files, but do not link."
arguments.AddToTail( "-nostdinc" ); // "Disable standard system #include directories"
// NOTE: -nostdinc works and overrides -nobuiltininc, while
// -nostdsysteminc and nostdinc++ do not work (had to GREP the LLVM source code to determine this !)
// These help to diagnose parse errors:
arguments.AddToTail( "-ferror-limit=100" ); // "Max errors to allow before parsing terminates"
arguments.AddToTail( "-fmacro-backtrace-limit=0" ); // "Max macro expansion depth in diagnostic spew"
//arguments.AddToTail( "-v" ); // "Show commands to run and use verbose output"
// Add options corresponding to the passed parse flags:
if ( parseFlags & CXTranslationUnit_DetailedPreprocessingRecord )
{
arguments.AddToTail( "-Xclang" ); // "Pass <arg> to the clang compiler"
arguments.AddToTail( "-detailed-preprocessing-record" );// "Include pre-processor data in the AST"
}
// Suppress warnings we don't want to see:
// TODO: can these be removed now I've added the VS2010 options?
arguments.AddToTail( "-Wno-microsoft" ); // "XXX is a microsoft extension"
arguments.AddToTail( "-Wno-c++11-extensions" ); // "warnings about using C++11 features as extensions in earlier C++ versions"
// TODO: these suppress warnings we should actually fix later:
arguments.AddToTail( "-Wno-return-type-c-linkage" ); // "XXX has C-linkage specified, but returns user-defined type YYY which is incompatible with C"
arguments.AddToTail( "-Wno-invalid-token-paste" ); // "pasting formed 'XXX', an invalid preprocessing token"
// __FUNCTION__ is implemented by Clang in the GCC style; it's a compiler-generated variable, not
// a preprocessor-generated string literal, so you can't do things like: "Error in " __FUNCTION__ "!\n"
// [ http://stackoverflow.com/questions/11142779/function-and-friends-act-weird-in-xcode ]
arguments.AddToTail( "-D__FUNCTION__=__FILE__" );
if ( bIsHeader && pchName.IsEmpty() )
{
// Clang cannot parse many of our standalone .h files, since they don't include everything they
// need (e.g baseschema.h), so if we're not using a PCH then define away schema stuff:
// TODO: remove this once we port Schemacompiler to use Clang!
arguments.AddToTail( "-Dschema=" );
arguments.AddToTail( "-Dnoschema=" );
arguments.AddToTail( "-Dschema_pragma(...)=" );
arguments.AddToTail( "-DMETA(...)=" );
arguments.AddToTail( "-DTYPEMETA(...)=" );
arguments.AddToTail( "-DSCHEMA_IGNORE_NEXT_PARENS(...)=" );
arguments.AddToTail( "-DMETA_ADD(...)=SCHEMA_IGNORE_NEXT_PARENS" );
arguments.AddToTail( "-DMETA_REMOVE(...)=SCHEMA_IGNORE_NEXT_PARENS" );
arguments.AddToTail( "-DMETA_OVERRIDE(...)=SCHEMA_IGNORE_NEXT_PARENS" );
arguments.AddToTail( "-DMETA_REMOVE_ALL(...)=SCHEMA_IGNORE_NEXT_PARENS" );
}
// __FUNCTION__ is implemented by Clang in the GCC style; it's a compiler-generated variable, not
// a preprocessor-generated string literal, so you can't do things like: "Error in " __FUNCTION__ "!\n"
// [ http://stackoverflow.com/questions/11142779/function-and-friends-act-weird-in-xcode ]
arguments.AddToTail( "-D__FUNCTION__=__FILE__" );
// Add platform-specific options:
if ( !Clang_AddPlatformOptions( arguments, pPlatform ) )
return false;
// Add compiler-specific options:
if ( !Clang_AddCompilerOptions( arguments, pCompiler ) )
return false;
// Add system include dirs explicitly (options above disable Clang's built-in include paths)
if ( !Clang_AddSystemIncludePaths( arguments, pPlatform, pCompiler ) )
return false;
// Add the caller's preprocessor defines and include directories:
CUtlString definePrefix( "-D" ), includePrefix( "-I" );
for ( int i=0; i < defines.Count(); i++ ) arguments.AddToTail( definePrefix + defines[i] );
for ( int i=0; i < includes.Count(); i++ ) arguments.AddToTail( includePrefix + includes[i] );
if ( bBuildPCH )
{
// This file is used as a PCH, so output the file <pchName>.pch:
arguments.AddToTail( "-emit-ast" );
arguments.AddToTail( "-o" );
arguments.AddToTail( pchName + ".pch" );
}
else if ( !pchName.IsEmpty() )
{
// If the file uses a PCH, add "-include <pchName>"
// NOTE: this is safely ignored for files which do not actually include the PCH file (e.g headers)
arguments.AddToTail( "-include" );
arguments.AddToTail( pchName );
}
// Put the filename last, after all the options:
arguments.AddToTail( pFilename );
// Transform each argument as appropriate for its intended usage
// (either passing via the libclang.dll API, or passing via the command-line):
for ( int i = 0; i < arguments.Count(); i++ )
{
command += TransformArgumentForCommandLineUsage( arguments[i], !bForLibClangDLL );
}
return true;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_GenerateDLLArguments( ICommandLine *pArguments, const char *pFilename, const char *pPlatform, const char *pCompiler,
const CUtlVector< CUtlString > &defines, const CUtlVector< CUtlString > &includes,
const CUtlString &pchName, bool bBuildPCH, int parseFlags )
{
CUtlString command;
bool bResult = Clang_GenerateCommandString( command, true, pFilename, pPlatform, pCompiler, defines, includes, pchName, bBuildPCH, parseFlags );
pArguments->CreateCmdLine( command.Get() );
return bResult;
}
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
bool Clang_GenerateCommandLine( CUtlString &command, const char *pFilename, const char *pPlatform, const char *pCompiler,
const CUtlVector< CUtlString > &defines, const CUtlVector< CUtlString > &includes,
const CUtlString &pchName, bool bBuildPCH, int parseFlags )
{
return Clang_GenerateCommandString( command, false, pFilename, pPlatform, pCompiler, defines, includes, pchName, bBuildPCH, parseFlags );
}