protot/3rdparty/RuntimeCompiledCpp/RuntimeCompiler/Compiler_PlatformWindows.cpp

568 lines
17 KiB
C++

//
// Copyright (c) 2010-2011 Matthew Jack and Doug Binks
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
// RuntimeDLLTest01.cpp : Defines the entry point for the console application.
//
// Notes:
// - We use a single intermediate directory for compiled .obj files, which means
// we don't support compiling multiple files with the same name. Could fix this
// with either mangling names to include paths, or recreating folder structure
//
//
#include "Compiler.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include <sstream>
#include <vector>
#include <set>
#include "FileSystemUtils.h"
#include "assert.h"
#include <process.h>
#include "ICompilerLogger.h"
using namespace std;
using namespace FileSystemUtils;
struct VSVersionInfo
{
int Version;
std::string Path;
};
const std::string c_CompletionToken( "_COMPLETION_TOKEN_" );
void GetPathsOfVisualStudioInstalls( std::vector<VSVersionInfo>* pVersions );
void ReadAndHandleOutputThread( LPVOID arg );
void WriteInput( HANDLE hPipeWrite, std::string& input );
class PlatformCompilerImplData
{
public:
PlatformCompilerImplData()
: m_bCompileIsComplete( false )
, m_CmdProcessOutputRead( NULL )
, m_CmdProcessInputWrite( NULL )
{
ZeroMemory( &m_CmdProcessInfo, sizeof(m_CmdProcessInfo) );
}
void InitialiseProcess()
{
//init compile process
STARTUPINFOW si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
#ifndef _WIN64
std::string cmdSetParams = "@PROMPT $ \n\"" + m_VSPath + "Vcvarsall.bat\" x86\n";
#else
std::string cmdSetParams = "@PROMPT $ \n\"" + m_VSPath + "Vcvarsall.bat\" x86_amd64\n";
#endif
// Set up the security attributes struct.
SECURITY_ATTRIBUTES sa;
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// Create the child output pipe.
//redirection of output
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
HANDLE hOutputReadTmp = NULL, hOutputWrite = NULL, hErrorWrite = NULL;
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,20*1024))
{
if( m_pLogger ) m_pLogger->LogError("[RuntimeCompiler] Failed to create output redirection pipe\n");
goto ERROR_EXIT;
}
si.hStdOutput = hOutputWrite;
// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
GetCurrentProcess(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
{
if( m_pLogger ) m_pLogger->LogError("[RuntimeCompiler] Failed to duplicate error output redirection pipe\n");
goto ERROR_EXIT;
}
si.hStdError = hErrorWrite;
// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if( si.hStdOutput )
{
if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&m_CmdProcessOutputRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
if( m_pLogger ) m_pLogger->LogError("[RuntimeCompiler] Failed to duplicate output read pipe\n");
goto ERROR_EXIT;
}
CloseHandle( hOutputReadTmp );
hOutputReadTmp = NULL;
}
HANDLE hInputRead,hInputWriteTmp;
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&hInputRead, &hInputWriteTmp, &sa, 4096))
{
if( m_pLogger ) m_pLogger->LogError("[RuntimeCompiler] Failed to create input pipes\n");
goto ERROR_EXIT;
}
si.hStdInput = hInputRead;
// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if( si.hStdOutput )
{
if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
&m_CmdProcessInputWrite, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
if( m_pLogger ) m_pLogger->LogError("[RuntimeCompiler] Failed to duplicate input write pipe\n");
goto ERROR_EXIT;
}
}
/*
// Ensure the write handle to the pipe for STDIN is not inherited.
if ( !SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0) )
{
m_pLogger->LogError("[RuntimeCompiler] Failed to make input write pipe non inheritable\n");
goto ERROR_EXIT;
}
*/
wchar_t* pCommandLine = L"cmd /q";
//CreateProcessW won't accept a const pointer, so copy to an array
wchar_t pCmdLineNonConst[1024];
wcscpy_s( pCmdLineNonConst, pCommandLine );
CreateProcessW(
NULL, //__in_opt LPCTSTR lpApplicationName,
pCmdLineNonConst, //__inout_opt LPTSTR lpCommandLine,
NULL, //__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
NULL, //__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
TRUE, //__in BOOL bInheritHandles,
0, //__in DWORD dwCreationFlags,
NULL, //__in_opt LPVOID lpEnvironment,
NULL, //__in_opt LPCTSTR lpCurrentDirectory,
&si, //__in LPSTARTUPINFO lpStartupInfo,
&m_CmdProcessInfo //__out LPPROCESS_INFORMATION lpProcessInformation
);
//send initial set up command
WriteInput( m_CmdProcessInputWrite, cmdSetParams );
//launch threaded read.
_beginthread( ReadAndHandleOutputThread, 0, this ); //this will exit when process for compile is closed
ERROR_EXIT:
if( hOutputReadTmp )
{
CloseHandle( hOutputReadTmp );
}
if( hOutputWrite )
{
CloseHandle( hOutputWrite );
}
if( hErrorWrite )
{
CloseHandle( hErrorWrite );
}
}
void CleanupProcessAndPipes()
{
// do not reset m_bCompileIsComplete and other members here, just process and pipes
if( m_CmdProcessInfo.hProcess )
{
TerminateProcess( m_CmdProcessInfo.hProcess, 0 );
TerminateThread( m_CmdProcessInfo.hThread, 0 );
CloseHandle( m_CmdProcessInfo.hThread );
ZeroMemory( &m_CmdProcessInfo, sizeof(m_CmdProcessInfo) );
CloseHandle( m_CmdProcessInputWrite );
m_CmdProcessInputWrite = 0;
CloseHandle( m_CmdProcessOutputRead );
m_CmdProcessOutputRead = 0;
}
}
~PlatformCompilerImplData()
{
CleanupProcessAndPipes();
}
std::string m_VSPath;
PROCESS_INFORMATION m_CmdProcessInfo;
HANDLE m_CmdProcessOutputRead;
HANDLE m_CmdProcessInputWrite;
volatile bool m_bCompileIsComplete;
ICompilerLogger* m_pLogger;
};
Compiler::Compiler()
: m_pImplData( 0 )
, m_bFastCompileMode( false )
{
}
Compiler::~Compiler()
{
delete m_pImplData;
}
std::string Compiler::GetObjectFileExtension() const
{
return ".obj";
}
bool Compiler::GetIsComplete() const
{
bool bComplete = m_pImplData->m_bCompileIsComplete;
if( bComplete & !m_bFastCompileMode )
{
m_pImplData->CleanupProcessAndPipes();
}
return bComplete;
}
void Compiler::Initialise( ICompilerLogger * pLogger )
{
m_pImplData = new PlatformCompilerImplData;
m_pImplData->m_pLogger = pLogger;
// get VS compiler path
std::vector<VSVersionInfo> Versions;
GetPathsOfVisualStudioInstalls( &Versions );
if( !Versions.empty() )
{
m_pImplData->m_VSPath = Versions[0].Path;
}
else
{
m_pImplData->m_VSPath = "";
if( m_pImplData->m_pLogger )
{
m_pImplData->m_pLogger->LogError("No Supported Compiler for RCC++ found.\n");
}
}
}
void Compiler::RunCompile( const std::vector<FileSystemUtils::Path>& filesToCompile_,
const CompilerOptions& compilerOptions_,
std::vector<FileSystemUtils::Path> linkLibraryList_,
const FileSystemUtils::Path& moduleName_ )
{
if( m_pImplData->m_VSPath.empty() )
{
if (m_pImplData->m_pLogger) { m_pImplData->m_pLogger->LogError("No Supported Compiler for RCC++ found, cannot compile changes.\n"); }
m_pImplData->m_bCompileIsComplete = true;
return;
}
m_pImplData->m_bCompileIsComplete = false;
//optimization and c runtime
#ifdef _DEBUG
std::string flags = "/nologo /Zi /FC /MDd /LDd ";
#else
std::string flags = "/nologo /Zi /FC /MD /LD "; //also need debug information in release
#endif
RCppOptimizationLevel optimizationLevel = GetActualOptimizationLevel( compilerOptions_.optimizationLevel );
switch( optimizationLevel )
{
case RCCPPOPTIMIZATIONLEVEL_DEFAULT:
assert(false);
case RCCPPOPTIMIZATIONLEVEL_DEBUG:
flags += "/Od ";
break;
case RCCPPOPTIMIZATIONLEVEL_PERF:
flags += "/O2 /Oi ";
// Add improved debugging options if available: http://randomascii.wordpress.com/2013/09/11/debugging-optimized-codenew-in-visual-studio-2012/
#if (_MSC_VER >= 1700)
flags += "/d2Zi+ ";
#endif
break;
case RCCPPOPTIMIZATIONLEVEL_NOT_SET:;
}
if( NULL == m_pImplData->m_CmdProcessInfo.hProcess )
{
m_pImplData->InitialiseProcess();
}
flags += compilerOptions_.compileOptions;
flags += " ";
std::string linkOptions;
bool bHaveLinkOptions = ( 0 != compilerOptions_.linkOptions.length() );
if( compilerOptions_.libraryDirList.size() || bHaveLinkOptions )
{
linkOptions = " /link ";
for( size_t i = 0; i < compilerOptions_.libraryDirList.size(); ++i )
{
linkOptions += " /LIBPATH:\"" + compilerOptions_.libraryDirList[i].m_string + "\"";
}
if( bHaveLinkOptions )
{
linkOptions += compilerOptions_.linkOptions;
linkOptions += " ";
}
}
// faster linking if available: https://randomascii.wordpress.com/2015/07/27/programming-is-puzzles/
#if (_MSC_VER >= 1900)
if( linkOptions.empty() )
{
linkOptions = " /link ";
}
linkOptions += "/DEBUG:FASTLINK ";
#endif
// Check for intermediate directory, create it if required
// There are a lot more checks and robustness that could be added here
if ( !compilerOptions_.intermediatePath.Exists() )
{
bool success = compilerOptions_.intermediatePath.CreateDir();
if( success && m_pImplData->m_pLogger ) { m_pImplData->m_pLogger->LogInfo("Created intermediate folder \"%s\"\n", compilerOptions_.intermediatePath.c_str()); }
else if( m_pImplData->m_pLogger ) { m_pImplData->m_pLogger->LogError("Error creating intermediate folder \"%s\"\n", compilerOptions_.intermediatePath.c_str()); }
}
//create include path search string
std::string strIncludeFiles;
for( size_t i = 0; i < compilerOptions_.includeDirList.size(); ++i )
{
strIncludeFiles += " /I \"" + compilerOptions_.includeDirList[i].m_string + "\"";
}
// When using multithreaded compilation, listing a file for compilation twice can cause errors, hence
// we do a final filtering of input here.
// See http://msdn.microsoft.com/en-us/library/bb385193.aspx - "Source Files and Build Order"
// Create compile path search string
std::string strFilesToCompile;
std::set<std::string> filteredPaths;
for( size_t i = 0; i < filesToCompile_.size(); ++i )
{
std::string strPath = filesToCompile_[i].m_string;
FileSystemUtils::ToLowerInPlace(strPath);
std::set<std::string>::const_iterator it = filteredPaths.find(strPath);
if (it == filteredPaths.end())
{
strFilesToCompile += " \"" + strPath + "\"";
filteredPaths.insert(strPath);
}
}
std::string strLinkLibraries;
for( size_t i = 0; i < linkLibraryList_.size(); ++i )
{
strLinkLibraries += " \"" + linkLibraryList_[i].m_string + "\" ";
}
char* pCharTypeFlags = "";
#ifdef UNICODE
pCharTypeFlags = "/D UNICODE /D _UNICODE ";
#endif
// /MP - use multiple processes to compile if possible. Only speeds up compile for multiple files and not link
std::string cmdToSend = "cl " + flags + pCharTypeFlags
+ " /MP /Fo\"" + compilerOptions_.intermediatePath.m_string + "\\\\\" "
+ "/D WIN32 /EHa /Fe" + moduleName_.m_string;
cmdToSend += " " + strIncludeFiles + " " + strFilesToCompile + strLinkLibraries + linkOptions
+ "\necho ";
if( m_pImplData->m_pLogger ) m_pImplData->m_pLogger->LogInfo( "%s", cmdToSend.c_str() ); // use %s to prevent any tokens in compile string being interpreted as formating
cmdToSend += c_CompletionToken + "\n";
WriteInput( m_pImplData->m_CmdProcessInputWrite, cmdToSend );
}
void GetPathsOfVisualStudioInstalls( std::vector<VSVersionInfo>* pVersions )
{
//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<version>\Setup\VS\<edition>
std::string keyName = "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7";
const size_t NUMNAMESTOCHECK = 6;
// supporting: VS2005, VS2008, VS2010, VS2011, VS2013, VS2015
std::string valueName[NUMNAMESTOCHECK] = {"8.0","9.0","10.0","11.0","12.0","14.0"};
// we start searching for a compatible compiler from the current version backwards
int startVersion = NUMNAMESTOCHECK - 1;
//switch around prefered compiler to the one we've used to compile this file
const unsigned int MSCVERSION = _MSC_VER;
switch( MSCVERSION )
{
case 1400: //VS 2005
startVersion = 0;
break;
case 1500: //VS 2008
startVersion = 1;
break;
case 1600: //VS 2010
startVersion = 2;
break;
case 1700: //VS 2012
startVersion = 3;
break;
case 1800: //VS 2013
startVersion = 4;
break;
case 1900: //VS 2015
startVersion = 5;
break;
default:
assert( false ); //unsupported compiler, find MSCVERSION to add case, increase NUMNAMESTOCHECK and add valueName.
}
char value[MAX_PATH];
DWORD size = MAX_PATH;
HKEY key;
LONG retKeyVal = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, //__in HKEY hKey,
keyName.c_str(), //__in_opt LPCTSTR lpSubKey,
0, //__reserved DWORD ulOptions,
KEY_READ | KEY_WOW64_32KEY, //__in REGSAM samDesired,
&key //__out PHKEY phkResult
);
int loopCount = 1;
if( startVersion != NUMNAMESTOCHECK - 1 )
{
// we potentially need to restart search from top
loopCount = 2;
}
for( int loop = 0; loop < loopCount; ++loop )
{
for( int i = startVersion; i >= 0; --i )
{
LONG retVal = RegQueryValueExA(
key, //__in HKEY hKey,
valueName[i].c_str(), //__in_opt LPCTSTR lpValueName,
NULL, //__reserved LPDWORD lpReserved,
NULL , //__out_opt LPDWORD lpType,
(LPBYTE)value, //__out_opt LPBYTE lpData,
&size //__inout_opt LPDWORD lpcbData
);
if( ERROR_SUCCESS == retVal )
{
VSVersionInfo vInfo;
vInfo.Version = i + 8;
vInfo.Path = value;
pVersions->push_back( vInfo );
}
}
startVersion = NUMNAMESTOCHECK - 1; // if we loop around again make sure it's from the top
}
RegCloseKey( key );
return;
}
void ReadAndHandleOutputThread( LPVOID arg )
{
PlatformCompilerImplData* pImpl = (PlatformCompilerImplData*)arg;
CHAR lpBuffer[1024];
DWORD nBytesRead;
bool bReadActive = true;
bool bReadOneMore = false;
while( bReadActive )
{
if (!ReadFile(pImpl->m_CmdProcessOutputRead,lpBuffer,sizeof(lpBuffer)-1,
&nBytesRead,NULL) || !nBytesRead)
{
bReadActive = false;
if (GetLastError() != ERROR_BROKEN_PIPE) //broken pipe is OK
{
if( pImpl->m_pLogger ) pImpl->m_pLogger->LogError( "[RuntimeCompiler] Redirect of compile output failed on read\n" );
}
}
else
{
// Display the characters read in logger.
lpBuffer[nBytesRead]=0;
//fist check for completion token...
std::string buffer( lpBuffer );
size_t found = buffer.find( c_CompletionToken );
if( found != std::string::npos )
{
//we've found the completion token, which means we quit
buffer = buffer.substr( 0, found );
if( pImpl->m_pLogger ) pImpl->m_pLogger->LogInfo("[RuntimeCompiler] Complete\n");
pImpl->m_bCompileIsComplete = true;
}
if( bReadActive || buffer.length() ) //don't output blank last line
{
//check if this is an error
size_t errorFound = buffer.find( " : error " );
size_t fatalErrorFound = buffer.find( " : fatal error " );
if( ( errorFound != std::string::npos ) || ( fatalErrorFound != std::string::npos ) )
{
if( pImpl->m_pLogger ) pImpl->m_pLogger->LogError( "%s", buffer.c_str() );
}
else
{
if( pImpl->m_pLogger ) pImpl->m_pLogger->LogInfo( "%s", buffer.c_str() );
}
}
}
}
}
void WriteInput( HANDLE hPipeWrite, std::string& input )
{
DWORD nBytesWritten;
DWORD length = (DWORD)input.length();
WriteFile( hPipeWrite, input.c_str() , length, &nBytesWritten, NULL );
}