// // 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 #include #include #include #include #include "FileSystemUtils.h" #include "assert.h" #include #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* 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 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& filesToCompile_, const CompilerOptions& compilerOptions_, std::vector 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 filteredPaths; for( size_t i = 0; i < filesToCompile_.size(); ++i ) { std::string strPath = filesToCompile_[i].m_string; FileSystemUtils::ToLowerInPlace(strPath); std::set::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* pVersions ) { //HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\\Setup\VS\ 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 ); }