1023 lines
37 KiB
C++
1023 lines
37 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.
|
|
|
|
#include "RuntimeObjectSystem.h"
|
|
|
|
// Remove windows.h define of GetObject which conflicts with EntitySystem GetObject
|
|
#if defined _WINDOWS_ && defined GetObject
|
|
#undef GetObject
|
|
#endif
|
|
#include "../RuntimeCompiler/AUArray.h"
|
|
#include "../RuntimeCompiler/ICompilerLogger.h"
|
|
#include "../RuntimeCompiler/FileChangeNotifier.h"
|
|
#include "IObjectFactorySystem.h"
|
|
#include "ObjectFactorySystem/ObjectFactorySystem.h"
|
|
#include "ObjectInterfacePerModule.h"
|
|
#include <algorithm>
|
|
#include "IObject.h"
|
|
|
|
#ifndef _WIN32
|
|
//TODO: fix below in a better generic fashion.
|
|
#define MAX_PATH 256
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
using FileSystemUtils::Path;
|
|
|
|
FileSystemUtils::Path RuntimeObjectSystem::ProjectSettings::ms_DefaultIntermediatePath;
|
|
|
|
static Path GetIntermediateFolder( Path basePath_, RCppOptimizationLevel optimizationLevel_ )
|
|
{
|
|
std::string folder;
|
|
#ifdef _DEBUG
|
|
folder = "DEBUG_";
|
|
#else
|
|
folder = "RELEASE_";
|
|
#endif
|
|
folder += RCppOptimizationLevelStrings[ GetActualOptimizationLevel( optimizationLevel_ ) ];
|
|
Path runtimeFolder = basePath_ / folder;
|
|
return runtimeFolder;
|
|
}
|
|
|
|
|
|
RuntimeObjectSystem::RuntimeObjectSystem()
|
|
: m_pCompilerLogger(0)
|
|
, m_pSystemTable(0)
|
|
, m_pObjectFactorySystem(new ObjectFactorySystem())
|
|
, m_pFileChangeNotifier(new FileChangeNotifier())
|
|
, m_pBuildTool(new BuildTool())
|
|
, m_bCompiling( false )
|
|
, m_bLastLoadModuleSuccess( false )
|
|
, m_bAutoCompile( true )
|
|
, m_TotalLoadedModulesEver(1) // starts at one for current exe
|
|
, m_bProtectionEnabled( true )
|
|
, m_pImpl( 0 )
|
|
, m_CurrentlyBuildingProject( 0 )
|
|
{
|
|
ProjectSettings::ms_DefaultIntermediatePath = FileSystemUtils::GetCurrentPath() / "Runtime";
|
|
CreatePlatformImpl();
|
|
}
|
|
|
|
RuntimeObjectSystem::~RuntimeObjectSystem()
|
|
{
|
|
m_pFileChangeNotifier->RemoveListener(this);
|
|
DeletePlatformImpl();
|
|
delete m_pObjectFactorySystem;
|
|
delete m_pFileChangeNotifier;
|
|
delete m_pBuildTool;
|
|
|
|
// Note we do not delete compiler logger, creator should do this
|
|
}
|
|
|
|
|
|
bool RuntimeObjectSystem::Initialise( ICompilerLogger * pLogger, SystemTable* pSystemTable )
|
|
{
|
|
m_pCompilerLogger = pLogger;
|
|
m_pSystemTable = pSystemTable;
|
|
|
|
m_pBuildTool->Initialise(m_pCompilerLogger);
|
|
|
|
// We start by using the code in the current module
|
|
IPerModuleInterface* pPerModuleInterface = PerModuleInterface::GetInstance();
|
|
pPerModuleInterface->SetModuleFileName( "Main Exe" );
|
|
|
|
m_pObjectFactorySystem->SetLogger( m_pCompilerLogger );
|
|
m_pObjectFactorySystem->SetRuntimeObjectSystem( this );
|
|
|
|
FileSystemUtils::Path initialDir = FileSystemUtils::GetCurrentPath();
|
|
m_FoundSourceDirectoryMappings[initialDir] = initialDir;
|
|
|
|
SetupObjectConstructors(pPerModuleInterface);
|
|
|
|
//add this dir to list of include dirs
|
|
FileSystemUtils::Path includeDir = FindFile(__FILE__);
|
|
includeDir = includeDir.ParentPath();
|
|
AddIncludeDir(includeDir.c_str());
|
|
|
|
//also add the runtime compiler dir to list of dirs
|
|
includeDir = includeDir.ParentPath() / Path("RuntimeCompiler");
|
|
AddIncludeDir(includeDir.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void RuntimeObjectSystem::OnFileChange(const IAUDynArray<const char*>& filelist)
|
|
{
|
|
if( !m_bAutoCompile )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for( unsigned short proj = 0; proj < m_Projects.size(); ++proj )
|
|
{
|
|
|
|
std::vector<BuildTool::FileToBuild>* pBuildFileList = &m_Projects[ proj ].m_BuildFileList;
|
|
if( m_bCompiling )
|
|
{
|
|
pBuildFileList = &m_Projects[ proj ].m_PendingBuildFileList;
|
|
}
|
|
|
|
|
|
if (m_pCompilerLogger) { m_pCompilerLogger->LogInfo( "FileChangeNotifier triggered recompile with changes to:\n" ); }
|
|
for( size_t i = 0; i < filelist.Size(); ++i )
|
|
{
|
|
// check this file is in our project list
|
|
TFileList::iterator it = std::find( m_Projects[ proj ].m_RuntimeFileList.begin( ), m_Projects[ proj ].m_RuntimeFileList.end( ), filelist[i] );
|
|
if( it == m_Projects[ proj ].m_RuntimeFileList.end() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (m_pCompilerLogger) { m_pCompilerLogger->LogInfo( " File %s\n", filelist[ i ] ); }
|
|
BuildTool::FileToBuild fileToBuild( filelist[ i ] );
|
|
|
|
bool bFindIncludeDependencies = true; // if this is a header or a source dependency need to find include dependencies
|
|
bool bForceIncludeDependencies = true;
|
|
if( fileToBuild.filePath.Extension() != ".h" ) //TODO: change to check for .cpp and .c as could have .inc files etc.?
|
|
{
|
|
bFindIncludeDependencies = false;
|
|
pBuildFileList->push_back( fileToBuild );
|
|
|
|
// file may be a source dependency, check
|
|
TFileToFilesIterator itrCurr = m_Projects[ proj ].m_RuntimeSourceDependencyMap.begin( );
|
|
while( itrCurr != m_Projects[ proj ].m_RuntimeSourceDependencyMap.end( ) )
|
|
{
|
|
if( itrCurr->second == fileToBuild.filePath )
|
|
{
|
|
BuildTool::FileToBuild fileToBuild( itrCurr->first );
|
|
pBuildFileList->push_back( fileToBuild );
|
|
}
|
|
++itrCurr;
|
|
}
|
|
}
|
|
|
|
if( bFindIncludeDependencies )
|
|
{
|
|
TFileToFilesEqualRange range = m_Projects[ proj ].m_RuntimeIncludeMap.equal_range( fileToBuild.filePath );
|
|
for( TFileToFilesIterator it = range.first; it != range.second; ++it )
|
|
{
|
|
BuildTool::FileToBuild fileToBuildFromIncludes( ( *it ).second, bForceIncludeDependencies );
|
|
pBuildFileList->push_back( fileToBuildFromIncludes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !m_bCompiling )
|
|
{
|
|
StartRecompile();
|
|
}
|
|
}
|
|
|
|
bool RuntimeObjectSystem::GetIsCompiledComplete()
|
|
{
|
|
return m_bCompiling && m_pBuildTool->GetIsComplete();
|
|
}
|
|
|
|
void RuntimeObjectSystem::CompileAllInProject( bool bForceRecompile, unsigned short projectId_ )
|
|
{
|
|
ProjectSettings& project = GetProject( projectId_ );
|
|
// since this is a compile all we can clear any pending compiles
|
|
project.m_BuildFileList.clear( );
|
|
|
|
// ensure we have an up to date list of files to commpile if autocompile is off
|
|
if( !m_bAutoCompile )
|
|
{
|
|
AUDynArray<IObjectConstructor*> constructors;
|
|
m_pObjectFactorySystem->GetAll(constructors);
|
|
SetupRuntimeFileTracking(constructors);
|
|
}
|
|
|
|
// add all files except headers
|
|
for( size_t i = 0; i < m_Projects[ projectId_ ].m_RuntimeFileList.size( ); ++i )
|
|
{
|
|
BuildTool::FileToBuild fileToBuild( project.m_RuntimeFileList[ i ], true ); //force re-compile on compile all
|
|
if( fileToBuild.filePath.Extension() != ".h") //TODO: change to check for .cpp and .c as could have .inc files etc.?
|
|
{
|
|
project.m_BuildFileList.push_back( fileToBuild );
|
|
}
|
|
}
|
|
|
|
StartRecompile();
|
|
}
|
|
|
|
void RuntimeObjectSystem::CompileAll( bool bForceRecompile )
|
|
{
|
|
for( unsigned short proj = 0; proj < m_Projects.size(); ++proj )
|
|
{
|
|
CompileAllInProject( bForceRecompile, proj );
|
|
}
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetAutoCompile( bool autoCompile )
|
|
{
|
|
m_bAutoCompile = autoCompile;
|
|
|
|
if (m_bAutoCompile)
|
|
{
|
|
AUDynArray<IObjectConstructor*> constructors;
|
|
m_pObjectFactorySystem->GetAll(constructors);
|
|
SetupRuntimeFileTracking(constructors);
|
|
}
|
|
}
|
|
|
|
// RuntimeObjectSystem::AddToRuntimeFileList - filename should be cleaned of "/../" etc, see FileSystemUtils::Path::GetCleanPath()
|
|
void RuntimeObjectSystem::AddToRuntimeFileList( const char* filename, unsigned short projectId_ )
|
|
{
|
|
ProjectSettings& project = GetProject( projectId_ );
|
|
TFileList::iterator it = std::find( project.m_RuntimeFileList.begin( ), project.m_RuntimeFileList.end( ), filename );
|
|
if( it == project.m_RuntimeFileList.end( ) )
|
|
{
|
|
project.m_RuntimeFileList.push_back( filename );
|
|
m_pFileChangeNotifier->Watch( filename, this );
|
|
}
|
|
}
|
|
|
|
void RuntimeObjectSystem::RemoveFromRuntimeFileList( const char* filename, unsigned short projectId_ )
|
|
{
|
|
ProjectSettings& project = GetProject( projectId_ );
|
|
TFileList::iterator it = std::find( project.m_RuntimeFileList.begin( ), project.m_RuntimeFileList.end( ), filename );
|
|
if( it != project.m_RuntimeFileList.end( ) )
|
|
{
|
|
project.m_RuntimeFileList.erase( it );
|
|
}
|
|
}
|
|
|
|
void RuntimeObjectSystem::StartRecompile()
|
|
{
|
|
m_bCompiling = true;
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo( "Compiling...\n" ); }
|
|
|
|
//Use a temporary filename for the dll
|
|
#ifdef _WIN32
|
|
char tempPath[ MAX_PATH ];
|
|
GetTempPathA( MAX_PATH, tempPath );
|
|
char tempFileName[ MAX_PATH ];
|
|
GetTempFileNameA( tempPath, "", 0, tempFileName );
|
|
std::string strTempFileName( tempFileName );
|
|
m_CurrentlyCompilingModuleName = strTempFileName;
|
|
#else
|
|
char tempPath[] = "/tmp/RCCppTempDylibXXXXXX";
|
|
int fileDesc = mkstemp(tempPath);
|
|
assert( fileDesc != -1 ); //TODO: should really handle the error
|
|
close( fileDesc ); //we don't actually want to make the file as yet
|
|
m_CurrentlyCompilingModuleName = tempPath;
|
|
|
|
#endif
|
|
|
|
// we step through and build each project in turn if there is anything to build, starting from m_PreviousBuildProject
|
|
bool bHaveProjectToBuild = false;
|
|
unsigned short project = m_CurrentlyBuildingProject;
|
|
if( m_Projects.size( ) )
|
|
{
|
|
do
|
|
{
|
|
++project;
|
|
if( project >= m_Projects.size( ) )
|
|
{
|
|
project = 0;
|
|
}
|
|
if( m_Projects[ project ].m_BuildFileList.size() || m_Projects[ project ].m_PendingBuildFileList.size() )
|
|
{
|
|
bHaveProjectToBuild = true;
|
|
}
|
|
} while( !bHaveProjectToBuild && m_CurrentlyBuildingProject != project );
|
|
m_CurrentlyBuildingProject = project;
|
|
}
|
|
|
|
if( !bHaveProjectToBuild )
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError( "Error - could not find any files to build in any projects\n" ); }
|
|
m_bCompiling = false;
|
|
return;
|
|
}
|
|
|
|
m_Projects[ project ].m_BuildFileList.insert( m_Projects[ project ].m_BuildFileList.end( ), m_Projects[ project ].m_PendingBuildFileList.begin( ), m_Projects[ project ].m_PendingBuildFileList.end( ) );
|
|
m_Projects[ project ].m_PendingBuildFileList.clear( );
|
|
std::vector<BuildTool::FileToBuild> ourBuildFileList( m_Projects[ project ].m_BuildFileList );
|
|
|
|
|
|
//Add libraries which need linking
|
|
std::vector<FileSystemUtils::Path> linkLibraryList;
|
|
for( size_t i = 0; i < ourBuildFileList.size(); ++ i )
|
|
{
|
|
|
|
TFileToFilesEqualRange range = m_Projects[ project ].m_RuntimeLinkLibraryMap.equal_range( ourBuildFileList[ i ].filePath );
|
|
for(TFileToFilesIterator it=range.first; it!=range.second; ++it)
|
|
{
|
|
linkLibraryList.push_back( it->second );
|
|
}
|
|
}
|
|
|
|
|
|
//Add required source files
|
|
const std::vector<const char*> vecRequiredFiles = PerModuleInterface::GetInstance()->GetRequiredSourceFiles();
|
|
FileSystemUtils::Path compileDir = PerModuleInterface::GetInstance()->GetCompiledPath();
|
|
for( size_t i = 0; i < vecRequiredFiles.size(); ++i )
|
|
{
|
|
FileSystemUtils::Path fullpath = compileDir / vecRequiredFiles[i];
|
|
fullpath = FindFile( fullpath );
|
|
BuildTool::FileToBuild reqFile( fullpath, false ); //don't force compile of these
|
|
ourBuildFileList.push_back( reqFile );
|
|
}
|
|
|
|
//Add dependency source files
|
|
size_t buildListSize = ourBuildFileList.size(); // we will add to the build list, so get the size before the loop
|
|
for( size_t i = 0; i < buildListSize; ++ i )
|
|
{
|
|
|
|
TFileToFilesEqualRange range = m_Projects[ project ].m_RuntimeSourceDependencyMap.equal_range( ourBuildFileList[ i ].filePath );
|
|
for(TFileToFilesIterator it=range.first; it!=range.second; ++it)
|
|
{
|
|
BuildTool::FileToBuild reqFile( it->second, false ); //don't force compile of these
|
|
ourBuildFileList.push_back( reqFile );
|
|
}
|
|
}
|
|
|
|
m_Projects[ project ].m_CompilerOptions.intermediatePath = GetIntermediateFolder( m_Projects[ project ].m_CompilerOptions.baseIntermediatePath,
|
|
m_Projects[ project ].m_CompilerOptions.optimizationLevel );
|
|
|
|
|
|
m_pBuildTool->BuildModule( ourBuildFileList,
|
|
m_Projects[ project ].m_CompilerOptions,
|
|
linkLibraryList, m_CurrentlyCompilingModuleName );
|
|
}
|
|
|
|
bool RuntimeObjectSystem::LoadCompiledModule()
|
|
{
|
|
m_bLastLoadModuleSuccess = false;
|
|
m_bCompiling = false;
|
|
|
|
// Since the temporary file is created with 0 bytes, loadlibrary can fail with a dialogue we want to prevent. So check size
|
|
// We pass in the ec value so the function won't throw an exception on error, but the value itself sometimes seems to
|
|
// be set even without an error, so not sure if it should be relied on.
|
|
uint64_t sizeOfModule = m_CurrentlyCompilingModuleName.GetFileSize();
|
|
|
|
HMODULE module = 0;
|
|
if( sizeOfModule )
|
|
{
|
|
#ifdef _WIN32
|
|
module = LoadLibraryA( m_CurrentlyCompilingModuleName.c_str() );
|
|
#else
|
|
module = dlopen( m_CurrentlyCompilingModuleName.c_str(), RTLD_NOW );
|
|
#endif
|
|
}
|
|
|
|
if (!module)
|
|
{
|
|
if (m_pCompilerLogger) { m_pCompilerLogger->LogError( "Failed to load module %s\n",m_CurrentlyCompilingModuleName.c_str()); }
|
|
return false;
|
|
}
|
|
|
|
GETPerModuleInterface_PROC pPerModuleInterfaceProcAdd = 0;
|
|
#ifdef _WIN32
|
|
pPerModuleInterfaceProcAdd = (GETPerModuleInterface_PROC) GetProcAddress(module, "GetPerModuleInterface");
|
|
#else
|
|
pPerModuleInterfaceProcAdd = (GETPerModuleInterface_PROC) dlsym(module,"GetPerModuleInterface");
|
|
|
|
#endif
|
|
if (!pPerModuleInterfaceProcAdd)
|
|
{
|
|
if (m_pCompilerLogger) { m_pCompilerLogger->LogError( "Failed GetProcAddress\n"); }
|
|
return false;
|
|
}
|
|
|
|
pPerModuleInterfaceProcAdd()->SetModuleFileName( m_CurrentlyCompilingModuleName.c_str() );
|
|
pPerModuleInterfaceProcAdd( )->SetProjectIdForAllConstructors( m_CurrentlyBuildingProject );
|
|
m_Modules.push_back( module );
|
|
|
|
if (m_pCompilerLogger) { m_pCompilerLogger->LogInfo( "Compilation Succeeded\n"); }
|
|
++m_TotalLoadedModulesEver;
|
|
|
|
SetupObjectConstructors(pPerModuleInterfaceProcAdd());
|
|
m_Projects[ m_CurrentlyBuildingProject ].m_BuildFileList.clear( ); // clear the files from our compile list
|
|
m_bLastLoadModuleSuccess = true;
|
|
|
|
// check if there is another project to build
|
|
bool bNeedAnotherCompile = false;
|
|
for( unsigned short proj = 0; proj < m_Projects.size( ); ++proj )
|
|
{
|
|
if( m_Projects[ proj ].m_BuildFileList.size( ) || m_Projects[ proj ].m_PendingBuildFileList.size( ) )
|
|
{
|
|
bNeedAnotherCompile = true;
|
|
}
|
|
}
|
|
|
|
if( bNeedAnotherCompile )//
|
|
{
|
|
// we have pending files to compile, go ahead and compile them
|
|
StartRecompile();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetupObjectConstructors(IPerModuleInterface* pPerModuleInterface)
|
|
{
|
|
// Set system Table
|
|
pPerModuleInterface->SetSystemTable( m_pSystemTable );
|
|
|
|
// get hold of the constructors
|
|
const std::vector<IObjectConstructor*> &objectConstructors = pPerModuleInterface->GetConstructors();
|
|
AUDynArray<IObjectConstructor*> constructors(objectConstructors.size());
|
|
for (size_t i = 0, iMax = objectConstructors.size(); i < iMax; ++i)
|
|
{
|
|
constructors[i] = objectConstructors[i];
|
|
}
|
|
|
|
if (m_bAutoCompile)
|
|
{
|
|
SetupRuntimeFileTracking(constructors);
|
|
}
|
|
|
|
m_pObjectFactorySystem->AddConstructors(constructors);
|
|
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetupRuntimeFileTracking(const IAUDynArray<IObjectConstructor*>& constructors_)
|
|
{
|
|
#ifndef RCCPPOFF
|
|
// for optimization purposes we skip some actions when running for the first time (i.e. no previous constructors)
|
|
static bool bFirstTime = true;
|
|
|
|
for (size_t i = 0, iMax = constructors_.Size(); i < iMax; ++i)
|
|
{
|
|
const char* pFilename = constructors_[i]->GetFileName(); // GetFileName returns full path including GetCompiledPath()
|
|
if( !pFilename )
|
|
{
|
|
continue;
|
|
}
|
|
Path filePath = pFilename;
|
|
filePath = filePath.GetCleanPath();
|
|
filePath = FindFile( filePath );
|
|
|
|
unsigned short projectId = constructors_[ i ]->GetProjectId();
|
|
ProjectSettings& project = GetProject( projectId );
|
|
AddToRuntimeFileList( filePath.c_str( ), projectId );
|
|
|
|
if( !bFirstTime )
|
|
{
|
|
//remove old include file mappings for this file
|
|
TFileToFilesIterator itrCurr = project.m_RuntimeIncludeMap.begin( );
|
|
while( itrCurr != project.m_RuntimeIncludeMap.end( ) )
|
|
{
|
|
if( itrCurr->second == filePath )
|
|
{
|
|
TFileToFilesIterator itrErase = itrCurr;
|
|
++itrCurr;
|
|
project.m_RuntimeIncludeMap.erase( itrErase );
|
|
}
|
|
else
|
|
{
|
|
++itrCurr;
|
|
}
|
|
}
|
|
|
|
//remove previous link libraries for this file
|
|
project.m_RuntimeLinkLibraryMap.erase( filePath );
|
|
|
|
//remove previous source dependencies
|
|
project.m_RuntimeSourceDependencyMap.erase( filePath );
|
|
}
|
|
|
|
//we need the compile path for some platforms where the __FILE__ path is relative to the compile path
|
|
FileSystemUtils::Path compileDir = constructors_[i]->GetCompiledPath();
|
|
|
|
//add include file mappings
|
|
for (size_t includeNum = 0; includeNum <= constructors_[i]->GetMaxNumIncludeFiles(); ++includeNum)
|
|
{
|
|
const char* pIncludeFile = constructors_[i]->GetIncludeFile(includeNum);
|
|
if( pIncludeFile )
|
|
{
|
|
FileSystemUtils::Path pathInc = compileDir / pIncludeFile;
|
|
pathInc = FindFile( pathInc.GetCleanPath() );
|
|
TFileToFilePair includePathPair;
|
|
includePathPair.first = pathInc;
|
|
includePathPair.second = filePath;
|
|
AddToRuntimeFileList( pathInc.c_str(), projectId );
|
|
project.m_RuntimeIncludeMap.insert( includePathPair );
|
|
}
|
|
}
|
|
|
|
|
|
//add link library file mappings
|
|
for (size_t linklibraryNum = 0; linklibraryNum <= constructors_[i]->GetMaxNumLinkLibraries(); ++linklibraryNum)
|
|
{
|
|
const char* pLinkLibrary = constructors_[i]->GetLinkLibrary(linklibraryNum);
|
|
if( pLinkLibrary )
|
|
{
|
|
// We do not use FindFiles for Linked Libraries as these are searched for on
|
|
// the library paths, which are themselves searched for.
|
|
TFileToFilePair linklibraryPathPair;
|
|
linklibraryPathPair.first = filePath;
|
|
linklibraryPathPair.second = pLinkLibrary;
|
|
project.m_RuntimeLinkLibraryMap.insert( linklibraryPathPair );
|
|
}
|
|
}
|
|
|
|
//add source dependency file mappings
|
|
for (size_t num = 0; num <= constructors_[i]->GetMaxNumSourceDependencies(); ++num)
|
|
{
|
|
SourceDependencyInfo sourceDependency = constructors_[i]->GetSourceDependency(num);
|
|
FileSystemUtils::Path pathInc[2]; // array of potential include files for later checks
|
|
if( sourceDependency.filename )
|
|
{
|
|
FileSystemUtils::Path pathSrc;
|
|
if( sourceDependency.relativeToPath )
|
|
{
|
|
pathSrc = sourceDependency.relativeToPath;
|
|
if( pathSrc.HasExtension() )
|
|
{
|
|
pathInc[1] = compileDir / pathSrc;
|
|
pathSrc = compileDir / pathSrc.ParentPath() / sourceDependency.filename;
|
|
}
|
|
else
|
|
{
|
|
pathSrc = compileDir / pathSrc / sourceDependency.filename;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pathSrc = compileDir / sourceDependency.filename;
|
|
}
|
|
pathSrc.ToOSCanonicalCase();
|
|
pathSrc = pathSrc.DelimitersToOSDefault();
|
|
pathSrc = pathSrc.GetCleanPath();
|
|
pathInc[0] = pathSrc;
|
|
if( sourceDependency.extension )
|
|
{
|
|
pathSrc.ReplaceExtension( sourceDependency.extension );
|
|
}
|
|
pathSrc = FindFile( pathSrc.GetCleanPath() );
|
|
TFileToFilePair sourcePathPair;
|
|
sourcePathPair.first = filePath;
|
|
sourcePathPair.second = pathSrc;
|
|
project.m_RuntimeSourceDependencyMap.insert( sourcePathPair );
|
|
|
|
// if the include file with a source dependancy is logged as an runtime include, then we mark this .cpp as compile dependencies on change
|
|
for( int inc=0; inc<2; ++inc )
|
|
{
|
|
TFileToFilesEqualRange range = project.m_RuntimeIncludeMap.equal_range( pathInc[inc] );
|
|
if( range.first != range.second )
|
|
{
|
|
// add source file to runtime file list
|
|
AddToRuntimeFileList( pathSrc.c_str(), projectId );
|
|
|
|
// also add this as a source dependency, so it gets force compiled on change of header (and not just compiled)
|
|
TFileToFilePair includePathPair;
|
|
includePathPair.first = pathInc[inc];
|
|
includePathPair.second = pathSrc;
|
|
project.m_RuntimeIncludeMap.insert( includePathPair );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bFirstTime = false;
|
|
#endif
|
|
}
|
|
|
|
RuntimeObjectSystem::ProjectSettings& RuntimeObjectSystem::GetProject( unsigned short projectId_ )
|
|
{
|
|
if( projectId_ >= m_Projects.size() )
|
|
{
|
|
m_Projects.resize(projectId_ + 1);
|
|
}
|
|
return m_Projects[ projectId_ ];
|
|
}
|
|
|
|
|
|
void RuntimeObjectSystem::AddIncludeDir( const char *path_, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_).m_CompilerOptions.includeDirList.push_back( path_ );
|
|
}
|
|
|
|
|
|
void RuntimeObjectSystem::AddLibraryDir( const char *path_, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.libraryDirList.push_back( path_ );
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetAdditionalCompileOptions( const char *options, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.compileOptions = options;
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetCompilerLocation( const char *path, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.compilerLocation = path;
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetAdditionalLinkOptions( const char *options, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.linkOptions = options;
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetOptimizationLevel( RCppOptimizationLevel optimizationLevel_, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.optimizationLevel = optimizationLevel_;
|
|
}
|
|
|
|
RCppOptimizationLevel RuntimeObjectSystem::GetOptimizationLevel( unsigned short projectId_ )
|
|
{
|
|
return GetProject( projectId_ ).m_CompilerOptions.optimizationLevel;
|
|
}
|
|
|
|
void RuntimeObjectSystem::SetIntermediateDir( const char* path_, unsigned short projectId_ )
|
|
{
|
|
GetProject( projectId_ ).m_CompilerOptions.baseIntermediatePath = path_;
|
|
}
|
|
|
|
void RuntimeObjectSystem::CleanObjectFiles() const
|
|
{
|
|
if( m_pBuildTool )
|
|
{
|
|
for( unsigned short proj = 0; proj < m_Projects.size(); ++proj )
|
|
{
|
|
for( int optimizationLevel = 0;
|
|
optimizationLevel < RCCPPOPTIMIZATIONLEVEL_SIZE;
|
|
++optimizationLevel )
|
|
{
|
|
Path intermediateFolder = GetIntermediateFolder( m_Projects[ proj ].m_CompilerOptions.baseIntermediatePath, RCppOptimizationLevel( optimizationLevel ) );
|
|
m_pBuildTool->Clean( intermediateFolder );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FileSystemUtils::Path RuntimeObjectSystem::FindFile( const FileSystemUtils::Path& input )
|
|
{
|
|
FileSystemUtils::Path requestedDirectory = input;
|
|
FileSystemUtils::Path filename;
|
|
FileSystemUtils::Path foundFile = input;
|
|
bool bIsFile = input.HasExtension();
|
|
if( bIsFile )
|
|
{
|
|
requestedDirectory = requestedDirectory.ParentPath();
|
|
filename = input.Filename();
|
|
}
|
|
requestedDirectory.ToOSCanonicalCase();
|
|
filename.ToOSCanonicalCase();
|
|
foundFile.ToOSCanonicalCase();
|
|
|
|
// Step 1: Try input directory
|
|
if( requestedDirectory.Exists() )
|
|
{
|
|
m_FoundSourceDirectoryMappings[ requestedDirectory ] = requestedDirectory;
|
|
}
|
|
else
|
|
{
|
|
// Step 2: Attempt to find a pre-existing mapping
|
|
bool bFoundMapping = false;
|
|
if( m_FoundSourceDirectoryMappings.size() )
|
|
{
|
|
FileSystemUtils::Path testDir = requestedDirectory;
|
|
FileSystemUtils::Path foundDir;
|
|
unsigned int depth = 0;
|
|
bool bFound = false;
|
|
while( testDir.HasParentPath() )
|
|
{
|
|
TFileMapIterator itrFind = m_FoundSourceDirectoryMappings.find( testDir );
|
|
if( itrFind != m_FoundSourceDirectoryMappings.end() )
|
|
{
|
|
foundDir = itrFind->second;
|
|
bFound = true;
|
|
break;
|
|
}
|
|
|
|
testDir = testDir.ParentPath();
|
|
++depth;
|
|
}
|
|
|
|
if( bFound )
|
|
{
|
|
if( depth )
|
|
{
|
|
// not an exact match
|
|
FileSystemUtils::Path directory = requestedDirectory;
|
|
directory.m_string.replace( 0, testDir.m_string.length(), foundDir.m_string );
|
|
if( directory.Exists() )
|
|
{
|
|
foundFile = directory / filename;
|
|
if( foundFile.Exists() )
|
|
{
|
|
m_FoundSourceDirectoryMappings[ requestedDirectory ] = directory;
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo( "Found Directory Mapping: %s to %s\n", requestedDirectory.c_str(), directory.c_str() ); }
|
|
bFoundMapping = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// exact match
|
|
foundFile = foundDir / filename;
|
|
bFoundMapping = true;
|
|
}
|
|
}
|
|
|
|
if( !bFoundMapping )
|
|
{
|
|
// Step 3: Attempt to find a mapping from a known path
|
|
TFileList requestedSubPaths;
|
|
FileSystemUtils::Path requestedSubPath = requestedDirectory;
|
|
while( requestedSubPath.HasParentPath() )
|
|
{
|
|
requestedSubPaths.push_back( requestedSubPath );
|
|
requestedSubPath = requestedSubPath.ParentPath();
|
|
}
|
|
|
|
TFileMapIterator itr = m_FoundSourceDirectoryMappings.begin();
|
|
while( ( itr != m_FoundSourceDirectoryMappings.end() ) && !bFoundMapping )
|
|
{
|
|
FileSystemUtils::Path existingPath = itr->second;
|
|
while( ( existingPath.HasParentPath() ) && !bFoundMapping )
|
|
{
|
|
// check all potentials
|
|
for( size_t i=0; i<requestedSubPaths.size(); ++i )
|
|
{
|
|
FileSystemUtils::Path toCheck = existingPath / requestedSubPaths[i].Filename();
|
|
if( toCheck.Exists() )
|
|
{
|
|
// potential mapping
|
|
FileSystemUtils::Path directory = requestedDirectory;
|
|
directory.m_string.replace( 0, requestedSubPaths[i].m_string.length(), toCheck.m_string );
|
|
if( directory.Exists() )
|
|
{
|
|
foundFile = directory / filename;
|
|
if( foundFile.Exists() )
|
|
{
|
|
m_FoundSourceDirectoryMappings[ requestedDirectory ] = directory;
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo( "Found Directory Mapping: %s to %s\n", requestedDirectory.c_str(), directory.c_str() ); }
|
|
bFoundMapping = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
existingPath = existingPath.ParentPath();
|
|
}
|
|
++itr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !foundFile.Exists() )
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogWarning( "Could not find Directory Mapping for: %s\n", input.c_str() ); }
|
|
++m_NumNotFoundSourceFiles;
|
|
}
|
|
return foundFile;
|
|
}
|
|
|
|
|
|
void RuntimeObjectSystem::AddPathToSourceSearch( const char* path )
|
|
{
|
|
m_FoundSourceDirectoryMappings[ path ] = path;
|
|
}
|
|
|
|
|
|
bool RuntimeObjectSystem::TestBuildCallback(const char* file, TestBuildResult type)
|
|
{
|
|
switch( type )
|
|
{
|
|
case TESTBUILDRRESULT_SUCCESS: // SUCCESS, yay!
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo("TESTBUILDRRESULT_SUCCESS: %s\n", file); }
|
|
break;
|
|
case TESTBUILDRRESULT_NO_FILES_TO_BUILD: // file registration error or no runtime files of this type
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogWarning("TESTBUILDRRESULT_NO_FILES_TO_BUILD\n"); }
|
|
break;
|
|
case TESTBUILDRRESULT_BUILD_FILE_GONE: // the file is no longer present
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("TESTBUILDRRESULT_BUILD_FILE_GONE: %s\n", file); }
|
|
break;
|
|
case TESTBUILDRRESULT_BUILD_NOT_STARTED: // file change detection could be broken, or if an include may not be included anywhere
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("TESTBUILDRRESULT_BUILD_NOT_STARTED: %s\n", file); }
|
|
break;
|
|
case TESTBUILDRRESULT_BUILD_FAILED: // a build was started, but it failed or module failed to load. See log.
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("TESTBUILDRRESULT_BUILD_FAILED: %s\n", file); }
|
|
break;
|
|
case TESTBUILDRRESULT_OBJECT_SWAP_FAIL: // build succeeded, module loaded but errors on swapping
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("TESTBUILDRRESULT_OBJECT_SWAP_FAIL: %s\n", file); }
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// returns 0 on success, -ve number of errors if there is an error and we should quit,
|
|
// positive number of errors if there is an error but we should continue
|
|
static int TestBuildFile( ICompilerLogger* pLog, RuntimeObjectSystem* pRTObjSys, const Path& file,
|
|
ITestBuildNotifier* callback, bool bTestFileTracking )
|
|
{
|
|
assert( callback );
|
|
|
|
if( pLog ) { pLog->LogInfo("Testing change to file: %s\n", file.c_str()); }
|
|
|
|
int numErrors = 0;
|
|
if( file.Exists() )
|
|
{
|
|
if( bTestFileTracking )
|
|
{
|
|
FileSystemUtils::filetime_t currTime = FileSystemUtils::GetCurrentTime();
|
|
FileSystemUtils::filetime_t oldModTime = file.GetLastWriteTime();
|
|
if( currTime == oldModTime )
|
|
{
|
|
// some files may be auto-generated by the program, so may have just been created so won't
|
|
// get a time change unless we force it.
|
|
currTime += 1;
|
|
}
|
|
file.SetLastWriteTime( currTime );
|
|
// we must also change the directories time, as some of our watchers watch the dir
|
|
Path directory = file.ParentPath();
|
|
directory.SetLastWriteTime( currTime );
|
|
for( int i=0; i<50; ++i )
|
|
{
|
|
// wait up to 100 seconds (make configurable?)
|
|
pRTObjSys->GetFileChangeNotifier()->Update( 1.0f ); // force update by using very large time delta
|
|
if( pRTObjSys->GetIsCompiling() ) { break; }
|
|
if( !callback->TestBuildWaitAndUpdate() )
|
|
{
|
|
return -0xD1E;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AUDynArray<const char*> filelist;
|
|
filelist.Add( file.c_str() );
|
|
pRTObjSys->OnFileChange( filelist );
|
|
}
|
|
if( pRTObjSys->GetIsCompiling() )
|
|
{
|
|
while( !pRTObjSys->GetIsCompiledComplete() )
|
|
{
|
|
if( !callback->TestBuildWaitAndUpdate() )
|
|
{
|
|
return -0xD1E;
|
|
}
|
|
}
|
|
int numCurrLoadedModules = pRTObjSys->GetNumberLoadedModules();
|
|
if( pRTObjSys->LoadCompiledModule() )
|
|
{
|
|
if( !callback->TestBuildCallback( file.c_str(), TESTBUILDRRESULT_SUCCESS ) ) { return -0xD1E; }
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
++numErrors;
|
|
if( pRTObjSys->GetNumberLoadedModules() == numCurrLoadedModules )
|
|
{
|
|
if( !callback->TestBuildCallback( file.c_str(), TESTBUILDRRESULT_BUILD_FAILED ) ) { return -numErrors; }
|
|
}
|
|
else
|
|
{
|
|
// loaded the module but some other issue
|
|
if( !callback->TestBuildCallback( file.c_str(), TESTBUILDRRESULT_OBJECT_SWAP_FAIL ) ) { return -numErrors; }
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++numErrors;
|
|
if( !callback->TestBuildCallback( file.c_str(), TESTBUILDRRESULT_BUILD_NOT_STARTED ) ) { return -numErrors; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++numErrors;
|
|
if( !callback->TestBuildCallback( file.c_str(), TESTBUILDRRESULT_BUILD_FILE_GONE ) ) { return -numErrors; }
|
|
}
|
|
return numErrors;
|
|
}
|
|
|
|
// tests one by one touching each runtime modifiable source file
|
|
// returns the number of errors - 0 if all passed.
|
|
int RuntimeObjectSystem::TestBuildAllRuntimeSourceFiles( ITestBuildNotifier* callback, bool bTestFileTracking )
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo("TestBuildAllRuntimeSourceFiles Starting\n"); }
|
|
|
|
ITestBuildNotifier* failCallbackLocal = callback;
|
|
if( !failCallbackLocal )
|
|
{
|
|
failCallbackLocal = this;
|
|
}
|
|
|
|
int numErrors = 0;
|
|
|
|
size_t numFilesToBuild = 0;
|
|
for( unsigned short proj = 0; proj < m_Projects.size( ); ++proj )
|
|
{
|
|
numFilesToBuild += m_Projects[ proj ].m_RuntimeFileList.size( );
|
|
}
|
|
|
|
if( 0 == numFilesToBuild )
|
|
{
|
|
failCallbackLocal->TestBuildCallback( NULL, TESTBUILDRRESULT_NO_FILES_TO_BUILD );
|
|
}
|
|
|
|
for( unsigned short proj = 0; proj < m_Projects.size(); ++proj )
|
|
{
|
|
TFileList filesToTest = m_Projects[ proj ].m_RuntimeFileList; // m_RuntimeFileList could change if file content changes (new includes or source dependencies) so make copy to ensure iterators valid.
|
|
for( TFileList::iterator it = filesToTest.begin(); it != filesToTest.end(); ++it )
|
|
{
|
|
const Path& file = *it;
|
|
if( file.Extension() != ".h" ) // exclude headers, use TestBuildAllRuntimeHeaders
|
|
{
|
|
int fileErrors = TestBuildFile( m_pCompilerLogger, this, file, failCallbackLocal, bTestFileTracking );
|
|
if( fileErrors < 0 )
|
|
{
|
|
// this means exit, and the number of errors is -ve so remove, unless -0xD1E is the response (for no error die)
|
|
if( fileErrors != -0xD1E )
|
|
{
|
|
numErrors -= fileErrors;
|
|
}
|
|
return numErrors;
|
|
}
|
|
numErrors += fileErrors;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( 0 == numErrors )
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo("All Tests Passed\n"); }
|
|
}
|
|
else
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("Tests Failed: %d\n", numErrors); }
|
|
}
|
|
return numErrors;
|
|
}
|
|
|
|
// tests touching each header which has RUNTIME_MODIFIABLE_INCLUDE.
|
|
// returns the number of errors - 0 if all passed.
|
|
int RuntimeObjectSystem::TestBuildAllRuntimeHeaders( ITestBuildNotifier* callback, bool bTestFileTracking )
|
|
{
|
|
ITestBuildNotifier* failCallbackLocal = callback;
|
|
if( !failCallbackLocal )
|
|
{
|
|
failCallbackLocal = this;
|
|
}
|
|
|
|
int numErrors = 0;
|
|
|
|
size_t numFilesToBuild = 0;
|
|
for( unsigned short proj = 0; proj < m_Projects.size( ); ++proj )
|
|
{
|
|
numFilesToBuild += m_Projects[ proj ].m_RuntimeFileList.size( );
|
|
}
|
|
|
|
if( 0 == numFilesToBuild )
|
|
{
|
|
failCallbackLocal->TestBuildCallback( NULL, TESTBUILDRRESULT_NO_FILES_TO_BUILD );
|
|
}
|
|
|
|
for( unsigned short proj = 0; proj < m_Projects.size(); ++proj )
|
|
{
|
|
TFileList filesToTest = m_Projects[ proj ].m_RuntimeFileList; // m_RuntimeFileList could change if file content changes (new includes or source dependencies) so make copy to ensure iterators valid.
|
|
for( TFileList::iterator it = filesToTest.begin( ); it != filesToTest.end( ); ++it )
|
|
{
|
|
const Path& file = *it;
|
|
if( file.Extension() == ".h" ) // exclude headers, use TestBuildAllRuntimeHeaders
|
|
{
|
|
int fileErrors = TestBuildFile( m_pCompilerLogger, this, file, failCallbackLocal, bTestFileTracking );
|
|
if( fileErrors < 0 )
|
|
{
|
|
// this means exit, and the number of errors is -ve so remove
|
|
return numErrors - fileErrors;
|
|
}
|
|
numErrors += fileErrors;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( 0 == numErrors )
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogInfo("All Tests Passed\n"); }
|
|
}
|
|
else
|
|
{
|
|
if( m_pCompilerLogger ) { m_pCompilerLogger->LogError("Tests Failed: %d\n", numErrors); }
|
|
}
|
|
return numErrors;
|
|
}
|