// PluginOptionsSavedToDB //------------------------------------------------------------------------------ void TestCachePlugin::PluginOptionsSavedToDB() const { FBuildOptions options; options.m_ConfigFile = "Data/TestCachePlugin/useplugin.bff"; AStackString<> cachePath; AStackString<> cachePluginDLL; { // Init the DB from the BFF FBuild f( options ); TEST_ASSERT( f.Initialize() ); // sotre a copy of the cache params cachePath = f.GetCachePath(); cachePluginDLL = f.GetCachePluginDLL(); TEST_ASSERT( !cachePath.IsEmpty() ); TEST_ASSERT( !cachePluginDLL.IsEmpty() ); // save the db to disk TEST_ASSERT( f.SaveDependencyGraph( "../../../../ftmp/Test/CachePlugin/CachePlugin.fdb" ) ); } { // reload from the db FBuild f( options ); TEST_ASSERT( f.Initialize( "../../../../ftmp/Test/CachePlugin/CachePlugin.fdb" ) ); // check that the cache params were persisted TEST_ASSERT( cachePath == f.GetCachePath() ); TEST_ASSERT( cachePluginDLL == f.GetCachePluginDLL() ); } }
// GetFileNode //------------------------------------------------------------------------------ bool Function::GetFileNode( const BFFIterator & iter, Node * & fileNode, const char * name, bool required ) const { // get the string containing the node name AStackString<> fileNodeName; if ( GetString( iter, fileNodeName, name, required ) == false ) { return false; } // handle not-present if ( fileNodeName.IsEmpty() ) { ASSERT( required == false ); // GetString should have managed required string fileNode = nullptr; return true; } // get/create the FileNode NodeGraph & ng = FBuild::Get().GetDependencyGraph(); Node * n = ng.FindNode( fileNodeName ); if ( n == nullptr ) { n = ng.CreateFileNode( fileNodeName ); } else if ( n->IsAFile() == false ) { Error::Error_1103_NotAFile( iter, this, name, n->GetName(), n->GetType() ); return false; } fileNode = n; return true; }
// LoadNode //------------------------------------------------------------------------------ /*static*/ bool Node::LoadNode( IOStream & stream, Node * & node ) { // read the name of the node AStackString< 512 > nodeName; if ( stream.Read( nodeName ) == false ) { node = nullptr; return false; } // empty name means the pointer was null, which is supported if ( nodeName.IsEmpty() ) { node = nullptr; return true; } // find the node by name - this should never fail NodeGraph & ng = FBuild::Get().GetDependencyGraph(); Node * n = ng.FindNode( nodeName ); if ( n == nullptr ) { node = nullptr; return false; } node = n; return true; }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionSettings::Commit( NodeGraph & /*nodeGraph*/, const BFFIterator & funcStartIter ) const { // using a cache plugin? AStackString<> cachePluginDLL; if ( !GetString( funcStartIter, cachePluginDLL, ".CachePluginDLL" ) ) { return false; } FBuild::Get().SetCachePluginDLL( cachePluginDLL ); if ( !cachePluginDLL.IsEmpty() ) { FLOG_INFO( "CachePluginDLL: '%s'", cachePluginDLL.Get() ); } // try to get the cache path from the config const BFFVariable * cachePathVar; if ( !GetString( funcStartIter, cachePathVar, ".CachePath" ) ) { return false; } if ( cachePathVar ) { s_CachePath = cachePathVar->GetString(); // override environment default only if not empty if ( s_CachePath.IsEmpty() == false ) { FBuild::Get().SetCachePath( s_CachePath ); } } // "Workers" Array< AString > workerList; if ( !GetStrings( funcStartIter, workerList, ".Workers" ) ) { return false; } if ( !workerList.IsEmpty() ) { FBuild::Get().SetWorkerList( workerList ); } // "Environment" Array< AString > environment; if ( !GetStrings( funcStartIter, environment, ".Environment" ) ) { return false; } if ( !environment.IsEmpty() ) { ProcessEnvironment( environment ); } return true; }
// CONSTRUCTOR - FBuild //------------------------------------------------------------------------------ FBuild::FBuild( const FBuildOptions & options ) : m_DependencyGraph( nullptr ) , m_JobQueue( nullptr ) , m_Client( nullptr ) , m_Cache( nullptr ) , m_LastProgressOutputTime( 0.0f ) , m_LastProgressCalcTime( 0.0f ) , m_SmoothedProgressCurrent( 0.0f ) , m_SmoothedProgressTarget( 0.0f ) , m_WorkerList( 0, true ) , m_EnvironmentString( nullptr ) , m_EnvironmentStringSize( 0 ) , m_ImportedEnvironmentVars( 0, true ) { #ifdef DEBUG_CRT_MEMORY_USAGE _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF | //_CRTDBG_CHECK_EVERY_16_DF | _CRTDBG_CHECK_CRT_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif m_Macros = FNEW( BFFMacros() ); m_DependencyGraph = FNEW( NodeGraph() ); // store all user provided options m_Options = options; // track the old working dir to restore if modified (mainly for unit tests) VERIFY( FileIO::GetCurrentDir( m_OldWorkingDir ) ); // check for cache environment variable to use as default AStackString<> cachePath; if ( Env::GetEnvVariable( "FASTBUILD_CACHE_PATH", cachePath ) ) { if ( cachePath.IsEmpty() == false ) { SetCachePath( cachePath ); } } // poke options where required FLog::SetShowInfo( m_Options.m_ShowInfo ); FLog::SetShowErrors( m_Options.m_ShowErrors ); FLog::SetShowProgress( m_Options.m_ShowProgress ); Function::Create(); }
// GetBaseDirectory //------------------------------------------------------------------------------ bool FunctionObjectList::GetBaseDirectory( const BFFIterator & iter, AStackString<> & baseDirectory) const { AStackString<> baseDir; if ( !GetString( iter, baseDir, ".CompilerInputFilesRoot", false ) ) // false = optional { return false; // GetString will have emitted error } if ( !baseDir.IsEmpty() ) { NodeGraph::CleanPath( baseDir, baseDirectory ); } else { baseDirectory.Clear(); } return true; }
// AStackStringConstructors //------------------------------------------------------------------------------ void TestAString::AStackStringConstructors() const { { // AStackString<> with no arguments AStackString<> empty; TEST_ASSERT( empty.GetLength() == 0 ); TEST_ASSERT( empty.GetReserved() > 0 ); TEST_ASSERT( empty.IsEmpty() == true ); TEST_ASSERT( empty.MemoryMustBeFreed() == false ); } { // AStackString<> from char * AStackString<> fromCharStar( "hello" ); TEST_ASSERT( fromCharStar.GetLength() == 5 ); TEST_ASSERT( fromCharStar.GetReserved() >= 5 ); TEST_ASSERT( fromCharStar.IsEmpty() == false ); TEST_ASSERT( fromCharStar.MemoryMustBeFreed() == false ); // AStackString<> from AStackString AStackString<> fromAString( fromCharStar ); TEST_ASSERT( fromAString.GetLength() == 5 ); TEST_ASSERT( fromAString.GetReserved() >= 5 ); TEST_ASSERT( fromAString.IsEmpty() == false ); TEST_ASSERT( fromAString.MemoryMustBeFreed() == false ); } { // AStackString<> from AString AString astring( "hello" ); AStackString<> fromAString( astring ); TEST_ASSERT( fromAString.GetLength() == 5 ); TEST_ASSERT( fromAString.GetReserved() >= 5 ); TEST_ASSERT( fromAString.IsEmpty() == false ); TEST_ASSERT( fromAString.MemoryMustBeFreed() == false ); } { const char * hello = "hellohellohello"; AStackString<> fromCharStarPair( hello, hello + 5 ); TEST_ASSERT( fromCharStarPair.GetLength() == 5 ); TEST_ASSERT( fromCharStarPair.GetReserved() >= 5 ); TEST_ASSERT( fromCharStarPair.IsEmpty() == false ); TEST_ASSERT( fromCharStarPair.MemoryMustBeFreed() == false ); } }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionLibrary::Commit( NodeGraph & nodeGraph, const BFFIterator & funcStartIter ) const { // make sure all required variables are defined const BFFVariable * outputLib; const BFFVariable * compiler; const BFFVariable * compilerOptions; AStackString<> compilerOptionsDeoptimized; AStackString<> compilerOutputPath; AStackString<> compilerOutputPrefix; const BFFVariable * compilerOutputExtension; const BFFVariable * librarian; const BFFVariable * librarianOptions; if ( !GetString( funcStartIter, outputLib, ".LibrarianOutput", true ) || !GetString( funcStartIter, compiler, ".Compiler", true ) || !GetString( funcStartIter, compilerOptions, ".CompilerOptions", true ) || !GetString( funcStartIter, compilerOptionsDeoptimized, ".CompilerOptionsDeoptimized", false ) || !GetString( funcStartIter, compilerOutputPath, ".CompilerOutputPath", true ) || !GetString( funcStartIter, compilerOutputPrefix, ".CompilerOutputPrefix", false ) || !GetString( funcStartIter, compilerOutputExtension, ".CompilerOutputExtension", false ) || !GetString( funcStartIter, librarian, ".Librarian", true ) || !GetString( funcStartIter, librarianOptions, ".LibrarianOptions", true ) ) { return false; } PathUtils::FixupFolderPath( compilerOutputPath ); // find or create the compiler node CompilerNode * compilerNode = nullptr; if ( !FunctionObjectList::GetCompilerNode( nodeGraph, funcStartIter, compiler->GetString(), compilerNode ) ) { return false; // GetCompilerNode will have emitted error } // Compiler Force Using Dependencies compilerForceUsing; if ( !GetNodeList( nodeGraph, funcStartIter, ".CompilerForceUsing", compilerForceUsing, false ) ) { return false; // GetNodeList will have emitted an error } // de-optimization setting bool deoptimizeWritableFiles = false; bool deoptimizeWritableFilesWithToken = false; if ( !GetBool( funcStartIter, deoptimizeWritableFiles, ".DeoptimizeWritableFiles", false, false ) ) { return false; // GetBool will have emitted error } if ( !GetBool( funcStartIter, deoptimizeWritableFilesWithToken, ".DeoptimizeWritableFilesWithToken", false, false ) ) { return false; // GetBool will have emitted error } if ( ( deoptimizeWritableFiles || deoptimizeWritableFilesWithToken ) && compilerOptionsDeoptimized.IsEmpty() ) { Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".CompilerOptionsDeoptimized" ) ); return false; } // cache & distribution control bool allowDistribution( true ); bool allowCaching( true ); if ( !GetBool( funcStartIter, allowDistribution, ".AllowDistribution", true ) || !GetBool( funcStartIter, allowCaching, ".AllowCaching", true ) ) { return false; // GetBool will have emitted error } // Precompiled Header support ObjectNode * precompiledHeaderNode = nullptr; AStackString<> compilerOutputExtensionStr( compilerOutputExtension ? compilerOutputExtension->GetString().Get() : ".obj" ); if ( !GetPrecompiledHeaderNode( nodeGraph, funcStartIter, compilerNode, compilerOptions, compilerForceUsing, precompiledHeaderNode, deoptimizeWritableFiles, deoptimizeWritableFilesWithToken, allowDistribution, allowCaching, compilerOutputExtensionStr ) ) { return false; // GetPrecompiledHeaderNode will have emitted error } // Sanity check compile flags const bool usingPCH = ( precompiledHeaderNode != nullptr ); uint32_t objFlags = ObjectNode::DetermineFlags( compilerNode, compilerOptions->GetString(), false, usingPCH ); if ( ( objFlags & ObjectNode::FLAG_MSVC ) && ( objFlags & ObjectNode::FLAG_CREATING_PCH ) ) { // must not specify use of precompiled header (must use the PCH specific options) Error::Error_1303_PCHCreateOptionOnlyAllowedOnPCH( funcStartIter, this, "Yc", "CompilerOptions" ); return false; } // Check input/output for Compiler { bool hasInputToken = false; bool hasOutputToken = false; bool hasCompileToken = false; const AString & args = compilerOptions->GetString(); Array< AString > tokens; args.Tokenize( tokens ); for ( const AString & token : tokens ) { if ( token.Find( "%1" ) ) { hasInputToken = true; } else if ( token.Find( "%2" ) ) { hasOutputToken = true; } else { if ( objFlags & ObjectNode::FLAG_MSVC ) { if ( ( token == "/c" ) || ( token == "-c" ) ) { hasCompileToken = true; } } else { if ( token == "-c" ) { hasCompileToken = true; } } } } if ( hasInputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "%1" ); return false; } if ( hasOutputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "%2" ); return false; } // check /c or -c if ( objFlags & ObjectNode::FLAG_MSVC ) { if ( hasCompileToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "/c or -c" ); return false; } } else if ( objFlags & ( ObjectNode::FLAG_SNC | ObjectNode::FLAG_GCC | ObjectNode::FLAG_CLANG ) ) { if ( hasCompileToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "-c" ); return false; } } } // Check input/output for Librarian { const AString & args = librarianOptions->GetString(); bool hasInputToken = ( args.Find( "%1" ) || args.Find( "\"%1\"" ) ); if ( hasInputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".LibrarianOptions", "%1" ); return false; } bool hasOutputToken = ( args.Find( "%2" ) || args.Find( "\"%2\"" ) ); if ( hasOutputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".LibrarianOptions", "%2" ); return false; } } // Get the (optional) Preprocessor & PreprocessorOptions const BFFVariable * preprocessor = nullptr; const BFFVariable * preprocessorOptions = nullptr; CompilerNode * preprocessorNode = nullptr; if ( !GetString( funcStartIter, preprocessor, ".Preprocessor", false ) ) { return false; // GetString will have emitted an error } if ( preprocessor ) { // get the preprocessor executable if ( !FunctionObjectList::GetCompilerNode( nodeGraph, funcStartIter, preprocessor->GetString(), preprocessorNode ) ) { return false; // GetCompilerNode will have emitted an error } // get the command line args for the preprocessor if ( !GetString( funcStartIter, preprocessorOptions, ".PreprocessorOptions", true ) ) // required { return false; // GetString will have emitted an error } } // Pre-build dependencies Dependencies preBuildDependencies; if ( !GetNodeList( nodeGraph, funcStartIter, ".PreBuildDependencies", preBuildDependencies, false ) ) { return false; // GetNodeList will have emitted an error } Dependencies staticDeps( 32, true ); if ( !GetInputs( nodeGraph, funcStartIter, staticDeps ) ) { return false; // GetStaticDeps will gave emitted error } // are the additional inputs to merge into the libaray? Dependencies additionalInputs; if ( !GetNodeList( nodeGraph, funcStartIter, ".LibrarianAdditionalInputs", additionalInputs, false ) ) { return false;// helper will emit error } if ( staticDeps.IsEmpty() && additionalInputs.IsEmpty() ) { Error::Error_1006_NothingToBuild( funcStartIter, this ); return false; } uint32_t flags = LibraryNode::DetermineFlags( librarian->GetString() ); // Create library node which depends on the single file or list if ( nodeGraph.FindNode( outputLib->GetString() ) ) { Error::Error_1100_AlreadyDefined( funcStartIter, this, outputLib->GetString() ); return false; } AStackString<> baseDirectory; if ( !GetBaseDirectory( funcStartIter, baseDirectory ) ) { return false; // GetBaseDirectory will have emitted error } AStackString<> extraPDBPath, extraASMPath; GetExtraOutputPaths( compilerOptions->GetString(), extraPDBPath, extraASMPath ); LibraryNode * libNode = nodeGraph.CreateLibraryNode( outputLib->GetString(), staticDeps, compilerNode, compilerOptions->GetString(), compilerOptionsDeoptimized, compilerOutputPath, librarian->GetString(), librarianOptions->GetString(), flags, precompiledHeaderNode, compilerForceUsing, preBuildDependencies, additionalInputs, deoptimizeWritableFiles, deoptimizeWritableFilesWithToken, allowDistribution, allowCaching, preprocessorNode, preprocessorOptions ? preprocessorOptions->GetString() : AString::GetEmpty(), baseDirectory ); if ( compilerOutputExtension ) { libNode->m_ObjExtensionOverride = compilerOutputExtension->GetString(); } libNode->m_CompilerOutputPrefix = compilerOutputPrefix; libNode->m_ExtraPDBPath = extraPDBPath; libNode->m_ExtraASMPath = extraASMPath; return ProcessAlias( nodeGraph, funcStartIter, libNode ); }
// GenerateVCXProjFilters //------------------------------------------------------------------------------ const AString & VSProjectGenerator::GenerateVCXProjFilters( const AString & projectFile ) { // preallocate to avoid re-allocations m_Tmp.SetReserved( MEGABYTE ); m_Tmp.SetLength( 0 ); // determine folder for project const char * lastProjSlash = projectFile.FindLast( NATIVE_SLASH ); AStackString<> projectBasePath( projectFile.Get(), lastProjSlash ? lastProjSlash + 1 : projectFile.Get() ); // header Write( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" ); Write( "<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n" ); // list of all folders Array< AString > folders( 1024, true ); // files { Write( " <ItemGroup>\n" ); const AString * const fEnd = m_Files.End(); for ( const AString * fIt = m_Files.Begin(); fIt!=fEnd; ++fIt ) { // get folder part, relative to base dir AStackString<> folder; GetFolderPath( *fIt, folder ); const char * fileName = fIt->BeginsWithI( projectBasePath ) ? fIt->Get() + projectBasePath.GetLength() : fIt->Get(); Write( " <CustomBuild Include=\"%s\">\n", fileName ); if ( !folder.IsEmpty() ) { Write( " <Filter>%s</Filter>\n", folder.Get() ); } Write( " </CustomBuild>\n" ); // add new folders if ( !folder.IsEmpty() ) { for (;;) { // add this folder if unique bool found = false; for ( const AString * it=folders.Begin(); it!=folders.End(); ++it ) { if ( it->CompareI( folder ) == 0 ) { found = true; break; } } if ( !found ) { folders.Append( folder ); } // handle intermediate folders const char * lastSlash = folder.FindLast( BACK_SLASH ); if ( lastSlash == nullptr ) { break; } folder.SetLength( (uint32_t)( lastSlash - folder.Get() ) ); } } } Write( " </ItemGroup>\n" ); } // folders { const AString * const fEnd = folders.End(); for ( const AString * fIt = folders.Begin(); fIt!=fEnd; ++fIt ) { Write( " <ItemGroup>\n" ); Write( " <Filter Include=\"%s\">\n", fIt->Get() ); Write( " <UniqueIdentifier>{%08x-6c94-4f93-bc2a-7f5284b7d434}</UniqueIdentifier>\n", CRC32::Calc( *fIt ) ); Write( " </Filter>\n" ); Write( " </ItemGroup>\n" ); } } // footer Write( "</Project>" ); // no carriage return m_OutputVCXProjFilters = m_Tmp; return m_OutputVCXProjFilters; }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionObjectList::Commit( const BFFIterator & funcStartIter ) const { // make sure all required variables are defined const BFFVariable * compiler; const BFFVariable * compilerOptions; AStackString<> compilerOptionsDeoptimized; AStackString<> compilerOutputPath; AStackString<> compilerOutputPrefix; const BFFVariable * compilerOutputExtension; if ( !GetString( funcStartIter, compiler, ".Compiler", true ) || !GetString( funcStartIter, compilerOptions, ".CompilerOptions", true ) || !GetString( funcStartIter, compilerOptionsDeoptimized, ".CompilerOptionsDeoptimized", false ) || !GetString( funcStartIter, compilerOutputPath, ".CompilerOutputPath", true ) || !GetString( funcStartIter, compilerOutputPrefix, ".CompilerOutputPrefix", false ) || !GetString( funcStartIter, compilerOutputExtension, ".CompilerOutputExtension", false ) ) { return false; } PathUtils::FixupFolderPath( compilerOutputPath ); NodeGraph & ng = FBuild::Get().GetDependencyGraph(); // find or create the compiler node CompilerNode * compilerNode = nullptr; if ( !FunctionObjectList::GetCompilerNode( funcStartIter, compiler->GetString(), compilerNode ) ) { return false; // GetCompilerNode will have emitted error } // Sanity check compile flags uint32_t objFlags = ObjectNode::DetermineFlags( compilerNode, compilerOptions->GetString() ); if ( ( objFlags & ObjectNode::FLAG_MSVC ) && ( objFlags & ObjectNode::FLAG_CREATING_PCH ) ) { // must not specify use of precompiled header (must use the PCH specific options) Error::Error_1303_PCHCreateOptionOnlyAllowedOnPCH( funcStartIter, this, "/Yc", "CompilerOptions" ); return false; } // Check input/output for Compiler { const AString & args = compilerOptions->GetString(); bool hasInputToken = ( args.Find( "%1" ) || args.Find( "\"%1\"" ) ); if ( hasInputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "%1" ); return false; } bool hasOutputToken = ( args.Find( "%2" ) || args.Find( "\"%2\"" ) ); if ( hasOutputToken == false ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "%2" ); return false; } // check /c or -c if ( objFlags & ObjectNode::FLAG_MSVC ) { if ( args.Find( "/c" ) == nullptr && args.Find( "-c" ) == nullptr) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "/c or -c" ); return false; } } else if ( objFlags & ( ObjectNode::FLAG_SNC | ObjectNode::FLAG_GCC | ObjectNode::FLAG_CLANG ) ) { if ( args.Find( "-c" ) == nullptr ) { Error::Error_1106_MissingRequiredToken( funcStartIter, this, ".CompilerOptions", "-c" ); return false; } } } // Compiler Force Using Dependencies compilerForceUsing; if ( !GetNodeList( funcStartIter, ".CompilerForceUsing", compilerForceUsing, false ) ) { return false; // GetNodeList will have emitted an error } // Get the (optional) Preprocessor & PreprocessorOptions const BFFVariable * preprocessor = nullptr; const BFFVariable * preprocessorOptions = nullptr; CompilerNode * preprocessorNode = nullptr; if ( !GetString( funcStartIter, preprocessor, ".Preprocessor", false ) ) { return false; // GetString will have emitted an error } if ( preprocessor ) { // get the preprocessor executable if ( !FunctionObjectList::GetCompilerNode( funcStartIter, preprocessor->GetString(), preprocessorNode ) ) { return false; // GetCompilerNode will have emitted an error } // get the command line args for the preprocessor if ( !GetString( funcStartIter, preprocessorOptions, ".PreprocessorOptions", true ) ) // required { return false; // GetString will have emitted an error } } // Pre-build dependencies Dependencies preBuildDependencies; if ( !GetNodeList( funcStartIter, ".PreBuildDependencies", preBuildDependencies, false ) ) { return false; // GetNodeList will have emitted an error } // de-optimization setting bool deoptimizeWritableFiles = false; bool deoptimizeWritableFilesWithToken = false; if ( !GetBool( funcStartIter, deoptimizeWritableFiles, ".DeoptimizeWritableFiles", false, false ) ) { return false; // GetBool will have emitted error } if ( !GetBool( funcStartIter, deoptimizeWritableFilesWithToken, ".DeoptimizeWritableFilesWithToken", false, false ) ) { return false; // GetBool will have emitted error } if ( ( deoptimizeWritableFiles || deoptimizeWritableFilesWithToken ) && compilerOptionsDeoptimized.IsEmpty() ) { Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".CompilerOptionsDeoptimized" ) ); return false; } // Precompiled Header support ObjectNode * precompiledHeaderNode = nullptr; if ( !GetPrecompiledHeaderNode( funcStartIter, compilerNode, objFlags, compilerOptions, compilerForceUsing, precompiledHeaderNode, deoptimizeWritableFiles, deoptimizeWritableFilesWithToken ) ) { return false; // GetPrecompiledHeaderNode will have emitted error } Dependencies staticDeps( 32, true ); if ( !GetInputs( funcStartIter, staticDeps ) ) { return false; // GetStaticDeps will gave emitted error } if ( staticDeps.IsEmpty() ) { Error::Error_1006_NothingToBuild( funcStartIter, this ); return false; } // parsing logic should guarantee we have a string for our name ASSERT( m_AliasForFunction.IsEmpty() == false ); // Check for existing node if ( ng.FindNode( m_AliasForFunction ) ) { Error::Error_1100_AlreadyDefined( funcStartIter, this, m_AliasForFunction ); return false; } // Create library node which depends on the single file or list ObjectListNode * o = ng.CreateObjectListNode( m_AliasForFunction, staticDeps, compilerNode, compilerOptions->GetString(), compilerOptionsDeoptimized, compilerOutputPath, precompiledHeaderNode, compilerForceUsing, preBuildDependencies, deoptimizeWritableFiles, deoptimizeWritableFilesWithToken, preprocessorNode, preprocessorOptions ? preprocessorOptions->GetString() : AString::GetEmpty() ); if ( compilerOutputExtension ) { o->m_ObjExtensionOverride = compilerOutputExtension->GetString(); } o->m_CompilerOutputPrefix = compilerOutputPrefix; return true; }
// ExtractIntellisenseOptions //------------------------------------------------------------------------------ /*static*/ void ProjectGeneratorBase::ExtractIntellisenseOptions( const AString & compilerArgs, const char * option, const char * alternateOption, Array< AString > & outOptions, bool escapeQuotes ) { ASSERT( option ); Array< AString > tokens; compilerArgs.Tokenize( tokens ); const size_t optionLen = AString::StrLen( option ); const size_t alternateOptionLen = alternateOption ? AString::StrLen( alternateOption ) : 0; for ( size_t i=0; i<tokens.GetSize(); ++i ) { AString & token = tokens[ i ]; // strip quotes around token, e.g: "-IFolder/Folder" if ( token.BeginsWith( '"' ) && token.EndsWith( '"' ) ) { token.Assign( token.Get() + 1, token.GetEnd() - 1 ); } AStackString<> optionBody; // Handle space between option and payload if ( ( token == option ) || ( token == alternateOption ) ) { // Handle an incomplete token at the end of list if ( i == ( tokens.GetSize() - 1 ) ) { break; } // Use next token optionBody = tokens[ i + 1 ]; } else if ( token.BeginsWith( option ) ) { // use everything after token optionBody.Assign( token.Get() + optionLen ); } else if ( alternateOption && token.BeginsWith( alternateOption ) ) { // use everything after token optionBody.Assign( token.Get() + alternateOptionLen ); } // Strip quotes around body (e.g. -I"Folder/Folder") if ( optionBody.BeginsWith( '"' ) && optionBody.EndsWith( '"' ) ) { optionBody.Trim( 1, 1 ); } // Did we find something? if ( optionBody.IsEmpty() == false ) { if ( escapeQuotes ) { optionBody.Replace( "\"", "\\\"" ); } outOptions.Append( optionBody ); } } }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionCopy::Commit( NodeGraph & nodeGraph, const BFFIterator & funcStartIter ) const { // make sure all required variables are defined Array< AString > sources( 16, true ); const BFFVariable * dstFileV; if ( !GetStrings( funcStartIter, sources, ".Source", true ) || !GetString( funcStartIter, dstFileV, ".Dest", true ) ) { return false; // GetString will have emitted errors } // Optional AStackString<> sourceBasePath; if ( !GetString( funcStartIter, sourceBasePath, ".SourceBasePath", false ) ) { return false; // GetString will have emitted errors } // Canonicalize the SourceBasePath if ( !sourceBasePath.IsEmpty() ) { AStackString<> cleanValue; NodeGraph::CleanPath( sourceBasePath, cleanValue ); PathUtils::EnsureTrailingSlash( cleanValue ); sourceBasePath = cleanValue; } // check sources are not paths { const AString * const end = sources.End(); for ( const AString * it = sources.Begin(); it != end; ++it ) { const AString & srcFile( *it ); // source must be a file, not a path if ( PathUtils::IsFolderPath( srcFile ) ) { Error::Error_1105_PathNotAllowed( funcStartIter, this, ".Source", srcFile ); return false; } } } // Pre-build dependencies Dependencies preBuildDependencies; if ( !GetNodeList( nodeGraph, funcStartIter, ".PreBuildDependencies", preBuildDependencies, false ) ) { return false; // GetNodeList will have emitted an error } Array< AString > preBuildDependencyNames( preBuildDependencies.GetSize(), false ); for ( const auto & dep : preBuildDependencies ) { preBuildDependencyNames.Append( dep.GetNode()->GetName() ); } // get source node Array< Node * > srcNodes; { const AString * const end = sources.End(); for ( const AString * it = sources.Begin(); it != end; ++it ) { Node * srcNode = nodeGraph.FindNode( *it ); if ( srcNode ) { if ( GetSourceNodes( funcStartIter, srcNode, srcNodes ) == false ) { return false; } } else { // source file not defined by use - assume an external file srcNodes.Append( nodeGraph.CreateFileNode( *it ) ); } } } AStackString<> dstFile; NodeGraph::CleanPath( dstFileV->GetString(), dstFile ); const bool dstIsFolderPath = PathUtils::IsFolderPath( dstFile ); // make all the nodes for copies Dependencies copyNodes( srcNodes.GetSize(), false ); for ( const Node * srcNode : srcNodes ) { AStackString<> dst( dstFile ); // dest can be a file OR a path. If it's a path, use the source filename part if ( dstIsFolderPath ) { // find filename part of source const AString & srcName = srcNode->GetName(); // If the sourceBasePath is specified (and valid) use the name relative to that if ( !sourceBasePath.IsEmpty() && PathUtils::PathBeginsWith( srcName, sourceBasePath ) ) { // Use everything relative to the SourceBasePath dst += srcName.Get() + sourceBasePath.GetLength(); } else { // Use just the file name const char * lastSlash = srcName.FindLast( NATIVE_SLASH ); dst += lastSlash ? ( lastSlash + 1 ) // append filename part if found : srcName.Get(); // otherwise append whole thing } } // check node doesn't already exist if ( nodeGraph.FindNode( dst ) ) { // TODO:C could have a specific error for multiple sources with only 1 output // to differentiate from two rules creating the same dst target Error::Error_1100_AlreadyDefined( funcStartIter, this, dst ); return false; } // create our node CopyFileNode * copyFileNode = nodeGraph.CreateCopyFileNode( dst ); copyFileNode->m_Source = srcNode->GetName(); copyFileNode->m_PreBuildDependencyNames = preBuildDependencyNames; if ( !copyFileNode->Initialize( nodeGraph, funcStartIter, this ) ) { return false; // Initialize will have emitted an error } copyNodes.Append( Dependency( copyFileNode ) ); } // handle alias creation return ProcessAlias( nodeGraph, funcStartIter, copyNodes ); }
// GetOtherLibsArg //------------------------------------------------------------------------------ /*static*/ bool LinkerNode::GetOtherLibsArg( const char * arg, Array< AString > & list, const AString * & it, const AString * const & end, bool canonicalizePath, bool isMSVC ) { // check for expected arg if ( isMSVC ) { if ( LinkerNode::IsStartOfLinkerArg_MSVC( *it, arg ) == false ) { return false; // not our arg, not consumed } } else { if ( it->BeginsWith( arg ) == false ) { return false; // not our arg, not consumed } } // get remainder of token after arg const char * valueStart = it->Get() + AString::StrLen( arg ) + 1; const char * valueEnd = it->GetEnd(); // if no remainder, arg value is next token if ( valueStart == valueEnd ) { ++it; // no more tokens? (malformed input) if ( it == end ) { // ignore this item and let the linker complain about that return true; // arg consumed } // use next token a value valueStart = it->Get(); valueEnd = it->GetEnd(); } // eliminate quotes AStackString<> value; Args::StripQuotes( valueStart, valueEnd, value ); // store if useful if ( value.IsEmpty() == false ) { if ( canonicalizePath ) { AStackString<> cleanValue; NodeGraph::CleanPath( value, cleanValue ); PathUtils::EnsureTrailingSlash( cleanValue ); list.Append( cleanValue ); } else { list.Append( value ); } } return true; // arg consumed }
// GetPrecompiledHeaderNode //------------------------------------------------------------------------------ bool FunctionObjectList::GetPrecompiledHeaderNode( NodeGraph & nodeGraph, const BFFIterator & iter, CompilerNode * compilerNode, const BFFVariable * compilerOptions, const Dependencies & compilerForceUsing, ObjectNode * & precompiledHeaderNode, bool deoptimizeWritableFiles, bool deoptimizeWritableFilesWithToken, bool allowDistribution, bool allowCaching, const AString& compilerOutputExtension ) const { const BFFVariable * pchInputFile = nullptr; const BFFVariable * pchOutputFile = nullptr; const BFFVariable * pchOptions = nullptr; if ( !GetString( iter, pchInputFile, ".PCHInputFile" ) || !GetString( iter, pchOutputFile, ".PCHOutputFile" ) || !GetString( iter, pchOptions, ".PCHOptions" ) ) { return false; } precompiledHeaderNode = nullptr; AStackString<> pchObjectName; if ( pchInputFile ) { if ( !pchOutputFile || !pchOptions ) { Error::Error_1300_MissingPCHArgs( iter, this ); return false; } Node * pchInputNode = nodeGraph.FindNode( pchInputFile->GetString() ); if ( pchInputNode ) { // is it a file? if ( pchInputNode->IsAFile() == false ) { Error::Error_1103_NotAFile( iter, this, "PCHInputFile", pchInputNode->GetName(), pchInputNode->GetType() ); return false; } } else { // Create input node pchInputNode = nodeGraph.CreateFileNode( pchInputFile->GetString() ); } if ( nodeGraph.FindNode( pchOutputFile->GetString() ) ) { Error::Error_1301_AlreadyDefinedPCH( iter, this, pchOutputFile->GetString().Get() ); return false; } uint32_t pchFlags = ObjectNode::DetermineFlags( compilerNode, pchOptions->GetString(), true, false ); if ( pchFlags & ObjectNode::FLAG_MSVC ) { // sanity check arguments bool foundYcInPCHOptions = false; bool foundFpInPCHOptions = false; // Find /Fo option to obtain pch object file name Array< AString > pchTokens; pchOptions->GetString().Tokenize( pchTokens ); for ( const AString & token : pchTokens ) { if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fo" ) ) { // Extract filename (and remove quotes if found) pchObjectName = token.Get() + 3; pchObjectName.Trim( pchObjectName.BeginsWith( '"' ) ? 1 : 0, pchObjectName.EndsWith( '"' ) ? 1 : 0 ); // Auto-generate name? if ( pchObjectName == "%3" ) { // example 'PrecompiledHeader.pch' to 'PrecompiledHeader.pch.obj' pchObjectName = pchOutputFile->GetString(); pchObjectName += compilerOutputExtension; } } else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Yc" ) ) { foundYcInPCHOptions = true; } else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fp" ) ) { foundFpInPCHOptions = true; } } // PCH must have "Create PCH" (e.g. /Yc"PrecompiledHeader.h") if ( foundYcInPCHOptions == false ) { Error::Error_1302_MissingPCHCompilerOption( iter, this, "Yc", "PCHOptions" ); return false; } // PCH must have "Precompiled Header to Use" (e.g. /Fp"PrecompiledHeader.pch") if ( foundFpInPCHOptions == false ) { Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fp", "PCHOptions" ); return false; } // PCH must have object output option (e.g. /Fo"PrecompiledHeader.obj") if ( pchObjectName.IsEmpty() ) { Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fo", "PCHOptions" ); return false; } // Check Compiler Options bool foundYuInCompilerOptions = false; bool foundFpInCompilerOptions = false; Array< AString > compilerTokens; compilerOptions->GetString().Tokenize( compilerTokens ); for ( const AString & token : compilerTokens ) { if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Yu" ) ) { foundYuInCompilerOptions = true; } else if ( ObjectNode::IsStartOfCompilerArg_MSVC( token, "Fp" ) ) { foundFpInCompilerOptions = true; } } // Object using the PCH must have "Use PCH" option (e.g. /Yu"PrecompiledHeader.h") if ( foundYuInCompilerOptions == false ) { Error::Error_1302_MissingPCHCompilerOption( iter, this, "Yu", "CompilerOptions" ); return false; } // Object using the PCH must have "Precompiled header to use" (e.g. /Fp"PrecompiledHeader.pch") if ( foundFpInCompilerOptions == false ) { Error::Error_1302_MissingPCHCompilerOption( iter, this, "Fp", "CompilerOptions" ); return false; } } precompiledHeaderNode = nodeGraph.CreateObjectNode( pchOutputFile->GetString(), pchInputNode, compilerNode, pchOptions->GetString(), AString::GetEmpty(), nullptr, pchFlags, compilerForceUsing, deoptimizeWritableFiles, deoptimizeWritableFilesWithToken, allowDistribution, allowCaching, nullptr, AString::GetEmpty(), 0 ); // preprocessor args not supported precompiledHeaderNode->m_PCHObjectFileName = pchObjectName; } return true; }