// EmitCompilationMessage //------------------------------------------------------------------------------ void TestNode::EmitCompilationMessage( const char * workingDir ) const { AStackString<> output; output += "Running Test: "; output += GetName(); output += '\n'; if ( FLog::ShowInfo() || FBuild::Get().GetOptions().m_ShowCommandLines ) { output += GetTestExecutable()->GetName(); output += ' '; output += m_TestArguments; output += '\n'; if ( workingDir ) { output += "Working Dir: "; output += workingDir; output += '\n'; } } FLOG_BUILD_DIRECT( output.Get() ); }
REGISTER_TESTS_END // FileExists //------------------------------------------------------------------------------ void TestFileIO::FileExists() const { // generate a process unique file path AStackString<> path; GenerateTempFileName( path ); // ensure doesn't exist FileIO::FileDelete( path.Get() ); // delete in case left over from previous test run TEST_ASSERT( FileIO::FileExists( path.Get() ) == false ); // create it FileStream f; TEST_ASSERT( f.Open( path.Get(), FileStream::WRITE_ONLY ) == true ); f.Close(); // ensure exists TEST_ASSERT( FileIO::FileExists( path.Get() ) == true ); // clean up TEST_ASSERT( FileIO::FileDelete( path.Get() ) == true ); TEST_ASSERT( FileIO::FileExists( path.Get() ) == false ); }
// TracingOutputCallback //------------------------------------------------------------------------------ /*static*/ bool FLog::TracingOutputCallback( const char * message ) { uint32_t threadIndex = WorkerThread::GetThreadIndex(); AStackString< 2048 > tmp; if ( s_ShowProgress ) { // clear previous progress message tmp += g_ClearLineString; } // print output and then progress if ( threadIndex > 0 ) { char buffer[ 8 ]; _itoa_s( threadIndex, buffer, 8, 10 ); tmp += buffer; tmp += '>'; if ( threadIndex < 10 ) { tmp += ' '; // keep output aligned when there are > 9 threads } } tmp += message; // output to debugger if present #ifdef DEBUG #ifdef __WINDOWS__ OutputDebugStringA( message ); #endif #endif tmp += m_ProgressText; fwrite( tmp.Get(), 1, tmp.GetLength(), stdout ); return false; // tell tracing not to output it again }
// ReadOnly //------------------------------------------------------------------------------ void TestFileIO::ReadOnly() const { // generate a process unique file path AStackString<> path; GenerateTempFileName( path ); // create it FileStream f; TEST_ASSERT( f.Open( path.Get(), FileStream::WRITE_ONLY ) == true ); f.Close(); // should not be read only TEST_ASSERT( FileIO::GetReadOnly( path ) == false ); // set readonly TEST_ASSERT( FileIO::SetReadOnly( path.Get(), true ) == true ); // should be read only TEST_ASSERT( FileIO::GetReadOnly( path ) == true ); // delete should fail TEST_ASSERT( FileIO::FileDelete( path.Get() ) == false ); // clean up TEST_ASSERT( FileIO::SetReadOnly( path.Get(), false ) == true ); TEST_ASSERT( FileIO::GetReadOnly( path ) == false ); TEST_ASSERT( FileIO::FileDelete( path.Get() ) == true ); }
// FileTime //------------------------------------------------------------------------------ void TestFileIO::FileTime() const { // generate a process unique file path AStackString<> path; GenerateTempFileName( path ); // create it FileStream f; TEST_ASSERT( f.Open( path.Get(), FileStream::WRITE_ONLY ) == true ); f.Close(); // get last write time const uint64_t oldTime = FileIO::GetFileLastWriteTime( path ); TEST_ASSERT( oldTime != 0 ); // wait for some time that is bigger than filesystem time granularity #if defined( __OSX__ ) // HFS+ has surprisingly poor time resolution (1 second) Thread::Sleep( 1100 ); #else Thread::Sleep( 500 ); #endif // modify file FileStream f2; TEST_ASSERT( f.Open( path.Get(), FileStream::WRITE_ONLY ) == true ); f.Write( (uint32_t)0 ); f.Close(); // get new last write time const uint64_t newTime = FileIO::GetFileLastWriteTime( path ); TEST_ASSERT( newTime > oldTime ); // manually set time back TEST_ASSERT( FileIO::SetFileLastWriteTime( path, oldTime ) == true ); uint64_t timeNow = FileIO::GetFileLastWriteTime( path ); TEST_ASSERT( timeNow == oldTime ); }
// ReadWeakRef //------------------------------------------------------------------------------ bool TextReader::ReadWeakRef() { // Name AStackString<> name; if ( !GetToken( name ) ) { Error( "Missing weakref name" ); return false; } AStackString<> value; if ( !GetString( value ) ) { Error( "Missing weakref value" ); return false; } const StackFrame & sf = m_DeserializationStack.Top(); // null weak refs can be taken care of right now if ( value == "null" ) { WeakRef< Object > nullWR; sf.m_Reflection->SetProperty( sf.m_Base, name.Get(), nullWR ); return true; } // non-null WeakRefs must be deferred // (since we might not have created the child object yet) UnresolvedWeakRef u; u.m_Base = sf.m_Base; u.m_Reflection = sf.m_Reflection; u.m_WeakRefName = name; u.m_WeakRefValue = value; m_UnresolvedWeakRefs.Append( u ); return true; }
// AStackStringOverflow //------------------------------------------------------------------------------ void TestAString::AStackStringOverflow() const { { // constructor with string longer than buffer AStackString< 8 > string( "01234567890123456789" ); TEST_ASSERT( string.GetLength() == 20 ); TEST_ASSERT( string.GetLength() == AString::StrLen( string.Get() ) ); } { // assigned of string longer than buffer AStackString< 8 > string; string = "01234567890123456789"; TEST_ASSERT( string.GetLength() == 20 ); TEST_ASSERT( string.GetLength() == AString::StrLen( string.Get() ) ); } { // concetentation of string longer than buffer AStackString< 8 > string; string += "01234567890123456789"; TEST_ASSERT( string.GetLength() == 20 ); TEST_ASSERT( string.GetLength() == AString::StrLen( string.Get() ) ); } }
// EmitCompilationMessage //------------------------------------------------------------------------------ void ExecNode::EmitCompilationMessage( const AString & args ) const { // basic info AStackString< 2048 > output; output += "Run: "; output += GetName(); output += '\n'; // verbose mode if ( FLog::ShowInfo() || FBuild::Get().GetOptions().m_ShowCommandLines ) { AStackString< 1024 > verboseOutput; verboseOutput.Format( "%s %s\nWorkingDir: %s\nExpectedReturnCode: %i\n", m_Executable->GetName().Get(), args.Get(), m_WorkingDir.Get(), m_ExpectedReturnCode ); output += verboseOutput; } // output all at once for contiguousness FLOG_BUILD_DIRECT( output.Get() ); }
// Retrieve //------------------------------------------------------------------------------ /*virtual*/ bool Cache::Retrieve( const AString & cacheId, void * & data, size_t & dataSize ) { data = nullptr; dataSize = 0; AStackString<> cacheFileName; GetCacheFileName( cacheId, cacheFileName ); FileStream cacheFile; if ( cacheFile.Open( cacheFileName.Get(), FileStream::READ_ONLY ) ) { const size_t cacheFileSize = (size_t)cacheFile.GetFileSize(); AutoPtr< char > mem( (char *)ALLOC( cacheFileSize ) ); if ( cacheFile.Read( mem.Get(), cacheFileSize ) == cacheFileSize ) { dataSize = cacheFileSize; data = mem.Release(); return true; } } return false; }
// ProcessEnvironment //------------------------------------------------------------------------------ void FunctionSettings::ProcessEnvironment( const Array< AString > & envStrings ) const { // the environment string is used in windows as a double-null terminated string // so convert our array to a single buffer // work out space required uint32_t size = 0; for ( uint32_t i=0; i<envStrings.GetSize(); ++i ) { size += envStrings[ i ].GetLength() + 1; // string len inc null } // allocate space AutoPtr< char > envString( (char *)ALLOC( size + 1 ) ); // +1 for extra double-null // while iterating, extract the LIB environment variable (if there is one) AStackString<> libEnvVar; // copy strings end to end char * dst = envString.Get(); for ( uint32_t i=0; i<envStrings.GetSize(); ++i ) { if ( envStrings[ i ].BeginsWith( "LIB=" ) ) { libEnvVar.Assign( envStrings[ i ].Get() + 4, envStrings[ i ].GetEnd() ); } const uint32_t thisStringLen = envStrings[ i ].GetLength(); AString::Copy( envStrings[ i ].Get(), dst, thisStringLen ); dst += ( thisStringLen + 1 ); } // final double-null *dst = '\000'; FBuild::Get().SetEnvironmentString( envString.Get(), size, libEnvVar ); }
// Save //------------------------------------------------------------------------------ void WorkerSettings::Save() { AStackString<> settingsPath; Env::GetExePath( settingsPath ); settingsPath += ".settings"; FileStream f; if ( f.Open( settingsPath.Get(), FileStream::WRITE_ONLY ) ) { bool ok = true; // header ok &= ( f.Write( "FWS", 3 ) == 3 ); ok &= ( f.Write( uint8_t( FBUILDWORKER_SETTINGS_CURRENT_VERSION ) ) == 1 ); // settings ok &= f.Write( (uint32_t)m_Mode ); ok &= f.Write( m_NumCPUsToUse ); ok &= f.Write( m_StartMinimized ); if ( ok ) { return; } } #if defined( __WINDOWS__ ) MessageBox( nullptr, "Failed to save settings.", "FBuildWorker", MB_OK ); #elif defined( __APPLE__ ) // TODO:MAC Implement ShowMessageBox #elif defined( __LINUX__ ) // TODO:LINUX Implement ShowMessageBox #else #error Unknown Platform #endif }
// WriteNestedProjects //------------------------------------------------------------------------------ void SLNGenerator::WriteNestedProjects( const Array< AString > & solutionProjectsToFolder, const Array< AString > & solutionFolderPaths ) { if ( solutionProjectsToFolder.GetSize() == 0 && solutionFolderPaths.GetSize() == 0 ) { return; // skip global section } Write( "\tGlobalSection(NestedProjects) = preSolution\r\n" ); // Write every project to solution folder relationships const AString * const solutionProjectsToFolderEnd = solutionProjectsToFolder.End(); for( const AString * it = solutionProjectsToFolder.Begin() ; it != solutionProjectsToFolderEnd ; ++it ) { Write( it->Get() ); } // Write every intermediate path const AString * const solutionFolderPathsEnd = solutionFolderPaths.End(); for( const AString * it = solutionFolderPaths.Begin() ; it != solutionFolderPathsEnd ; ++it ) { // parse solution folder parent path AStackString<> solutionFolderParentGuid; const char * lastSlash = it->FindLast( NATIVE_SLASH ); if ( lastSlash ) { AStackString<> solutionFolderParentPath( it->Get(), lastSlash ); VSProjectGenerator::FormatDeterministicProjectGUID( solutionFolderParentGuid, solutionFolderParentPath ); } if ( solutionFolderParentGuid.GetLength() > 0 ) { // generate a guid for the solution folder AStackString<> solutionFolderGuid; VSProjectGenerator::FormatDeterministicProjectGUID( solutionFolderGuid, *it ); solutionFolderGuid.ToUpper(); solutionFolderParentGuid.ToUpper(); // write parent solution folder relationship Write( "\t\t%s = %s\r\n", solutionFolderGuid.Get(), solutionFolderParentGuid.Get() ); } } Write( "\tEndGlobalSection\r\n" ); }
// StopBuild //------------------------------------------------------------------------------ /*static*/ void FLog::StopBuild() { if ( g_MonitorFileStream ) { MutexHolder lock( g_MonitorMutex ); Monitor( "STOP_BUILD\n" ); g_MonitorFileStream->Close(); delete g_MonitorFileStream; g_MonitorFileStream = nullptr; } Tracing::RemoveCallbackOutput( &TracingOutputCallback ); if ( s_ShowProgress ) { fputs( g_ClearLineString.Get(), stdout ); m_ProgressText.Clear(); } }
// DoToggleSection //------------------------------------------------------------------------------ void Report::DoToggleSection( size_t numMore ) { static int tableId = 0; ++tableId; AStackString<> tableIdStr; tableIdStr.Format( "table%u", tableId ); DoTableStop(); AStackString<> more; if ( numMore ) { more.Format( "%u ", (uint32_t)numMore ); } Write( "<a href='javascript:toggleTable(\"%s\");'>%sMore...</a>\n", tableIdStr.Get(), more.Get() ); DoTableStart( DEFAULT_TABLE_WIDTH, tableIdStr.Get(), true ); // hide table }
// Format //------------------------------------------------------------------------------ void TestAString::Format() const { // Create a really long input string AStackString<> longInput; const size_t longStringLen( 1024 * 1024 ); for ( size_t i=0; i<longStringLen; ++i ) { longInput += 'A'; } // Make sure we correctly handle formatting large strings AStackString<> buffer; buffer.Format( "%s", longInput.Get() ); TEST_ASSERT( buffer.GetLength() == longStringLen ); TEST_ASSERT( AString::StrLen( buffer.Get() ) == longStringLen ); }
// Monitor //------------------------------------------------------------------------------ /*static*/ void FLog::Monitor( const char * formatString, ... ) { // Is monitoring enabled? if ( g_MonitorFileStream == nullptr ) { return; // No - nothing to do } PROFILE_SECTION( "FLog::Monitor" ) AStackString< 1024 > buffer; va_list args; va_start( args, formatString ); buffer.VFormat( formatString, args ); va_end( args ); AStackString< 1024 > finalBuffer; finalBuffer.Format( "%llu %s", Time::GetCurrentFileTime(), buffer.Get() ); MutexHolder lock( g_MonitorMutex ); g_MonitorFileStream->WriteBuffer( finalBuffer.Get(), finalBuffer.GetLength() ); }
// FormatError //------------------------------------------------------------------------------ void Error::FormatError( const BFFIterator & iter, uint32_t errNum, const Function * function, const char * message, ... ) { ASSERT( message ); AStackString< 4096 > buffer; va_list args; va_start(args, message); buffer.VFormat( message, args ); va_end( args ); // get human readable info about the position of the error uint32_t line = 0; uint32_t column = 0; const char * lineStart = nullptr; iter.GetPosInfo( line, column, lineStart ); // convert to full path and '/'->'\' cleanup const AStackString<> fileName( iter.GetFileName() ); AStackString<> fullPath; NodeGraph::CleanPath( fileName, fullPath ); // deliberately using OUTPUT here to avoid "Error:" in front OUTPUT( "%s(%u,%u): FASTBuild Error #%04u - %s%s%s\n", fullPath.Get(), line, column, errNum, function ? function->GetName().Get() : "", function ? "() - " : "", buffer.Get() ); // find the line end BFFIterator lineEnd( iter ); while ( !lineEnd.IsAtEnd() ) { if (( *lineEnd != '\r' ) && ( *lineEnd != '\n' )) { lineEnd++; continue; } break; } // if line is too crazy to be useful, don't print anything more size_t lineLength = lineEnd.GetCurrent() - lineStart; if ( lineLength >= 256 ) { return; } // print the problematic line AString::Copy( lineStart, buffer.Get(), lineLength ); FLOG_ERROR( "%s", buffer.Get() ); // point to the specific pos where parsing broke // (taking into account tabs) char * c = buffer.Get(); const char * end = c + column - 1; for ( ; c < end; ++c ) { if ( *c != '\t' ) { *c = ' '; } } AString::Copy( "^", c, 1 ); FLOG_ERROR( buffer.Get() ); AString::Copy( "\\--here", c, 8 ); FLOG_ERROR( buffer.Get() ); }
// 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; }
// PrintVarRecurse //------------------------------------------------------------------------------ /*static*/ void FunctionPrint::PrintVarRecurse( const BFFVariable & var, uint32_t indent ) { AStackString<> indentStr; for ( uint32_t i=0; i<indent; ++i ) { indentStr += " "; } ++indent; FLOG_BUILD( indentStr.Get() ); switch ( var.GetType() ) { case BFFVariable::VAR_ANY: ASSERT( false ); break; // Something is terribly wrong case BFFVariable::VAR_STRING: { AStackString<> value( var.GetString() ); value.Replace( "'", "^'" ); // escape single quotes FLOG_BUILD( "%s = '%s'\n", var.GetName().Get(), value.Get() ); break; } case BFFVariable::VAR_BOOL: { FLOG_BUILD( "%s = %s\n", var.GetName().Get(), var.GetBool() ? "true" : "false" ); break; } case BFFVariable::VAR_ARRAY_OF_STRINGS: { const auto & strings = var.GetArrayOfStrings(); FLOG_BUILD( "%s = // ArrayOfStrings, size: %u\n%s{\n", var.GetName().Get(), (uint32_t)strings.GetSize(), indentStr.Get() ); for ( const AString & string : strings ) { AStackString<> value( string ); value.Replace( "'", "^'" ); // escape single quotes FLOG_BUILD( "%s '%s'\n", indentStr.Get(), value.Get() ); } FLOG_BUILD( "%s}\n", indentStr.Get() ); break; } case BFFVariable::VAR_INT: { FLOG_BUILD( "%s = %i\n", var.GetName().Get(), var.GetInt() ); break; } case BFFVariable::VAR_STRUCT: { FLOG_BUILD( "%s = // Struct\n%s[\n", var.GetName().Get(), indentStr.Get() ); for ( const BFFVariable * subVar : var.GetStructMembers() ) { PrintVarRecurse( *subVar, indent ); } FLOG_BUILD( "%s]\n", indentStr.Get() ); break; } case BFFVariable::VAR_ARRAY_OF_STRUCTS: { const auto & structs = var.GetArrayOfStructs(); FLOG_BUILD( "%s = // ArrayOfStructs, size: %u\n%s{\n", var.GetName().Get(), (uint32_t)structs.GetSize(), indentStr.Get() ); for ( const BFFVariable * subVar : structs ) { PrintVarRecurse( *subVar, indent ); } FLOG_BUILD( "%s}\n", indentStr.Get() ); break; } case BFFVariable::MAX_VAR_TYPES: ASSERT( false ); break; // Something is terribly wrong } }
// 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; }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionVCXProject::Commit( const BFFIterator & funcStartIter ) const { // required AStackString<> projectOutput; AStackString<> rootNamespace; AStackString<> projectGuid; AStackString<> defaultLanguage; AStackString<> applicationEnvironment; if ( !GetString( funcStartIter, projectOutput, ".ProjectOutput", true ) || !GetString( funcStartIter, rootNamespace, ".RootNamespace", false ) || !GetString( funcStartIter, projectGuid, ".ProjectGuid", false ) || !GetString( funcStartIter, defaultLanguage, ".DefaultLanguage", false ) || !GetString( funcStartIter, applicationEnvironment, ".ApplicationEnvironment", false ) ) { return false; } // optional inputs Array< AString > inputPaths; Array< AString > inputPathsExclude; if ( !GetStrings( funcStartIter, inputPaths, ".ProjectInputPaths", false ) || !GetStrings( funcStartIter, inputPathsExclude, ".ProjectInputPathsExclude", false ) ) { return false; } // project base Array< AString > basePaths; if ( !GetStrings( funcStartIter, basePaths, ".ProjectBasePath", false ) ) { return false; } CleanFolderPaths( basePaths ); // references Array< AString > references; Array< AString > projectReferences; if ( !GetStrings( funcStartIter, references, ".ProjectReferences", false ) || !GetStrings( funcStartIter, projectReferences, ".ProjectProjectReferences", false ) ) { return false; } // permitted file extensions Array< AString > allowedFileExtensions( 8, true ); if ( !GetStrings( funcStartIter, allowedFileExtensions, ".ProjectAllowedFileExtensions", false ) ) { return true; } if ( allowedFileExtensions.IsEmpty() ) { const char * extensions[] = { ".cpp", ".hpp", ".cxx",".hxx",".c",".h",".cc",".hh", ".cp",".hp",".cs",".inl",".bff",".rc",".resx",".m",".mm", ".cu", nullptr }; AStackString<> tmp; const char ** item = extensions; while ( *item ) { tmp.Assign( *item ); allowedFileExtensions.Append( tmp ); ++item; } } // files and filesToExclude Array< AString > files( 8, true ); Array< AString > filesToExclude( 8, true ); if ( !GetStrings( funcStartIter, files, ".ProjectFiles", false ) || !GetStrings( funcStartIter, filesToExclude, ".ProjectFilesToExclude", false ) ) { return false; } // filetypes Array< VSProjectFileType > fileTypes; const BFFVariable * projectFileTypes = BFFStackFrame::GetVar( ".ProjectFileTypes" ); if ( projectFileTypes ) { if ( projectFileTypes->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".ProjectFileTypes", projectFileTypes->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = projectFileTypes->GetArrayOfStructs(); const BFFVariable * const * end = structs.End(); for ( const BFFVariable ** it = structs.Begin(); it != end; ++it ) { const BFFVariable * s = *it; VSProjectFileType ft; // .FileType must be provided if ( !GetStringFromStruct( s, ".FileType", ft.m_FileType ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".FileType" ) ); return false; } // .Pattern must be provided if ( !GetStringFromStruct( s, ".Pattern", ft.m_Pattern ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Pattern" ) ); return false; } fileTypes.Append( ft ); } } // path cleaning CleanFolderPaths( inputPaths ); // input paths CleanFolderPaths( inputPathsExclude ); // exclude paths CleanFilePaths( files ); // explicit files // per-config options VSProjectConfig baseConfig; // various options if ( !GetString( funcStartIter, baseConfig.m_BuildCommand, ".ProjectBuildCommand", false ) || !GetString( funcStartIter, baseConfig.m_RebuildCommand,".ProjectRebuildCommand", false ) || !GetString( funcStartIter, baseConfig.m_CleanCommand, ".ProjectCleanCommand", false ) || !GetString( funcStartIter, baseConfig.m_Output, ".Output", false ) || !GetString( funcStartIter, baseConfig.m_PreprocessorDefinitions, ".PreprocessorDefinitions", false ) || !GetString( funcStartIter, baseConfig.m_IncludeSearchPath, ".IncludeSearchPath", false ) || !GetString( funcStartIter, baseConfig.m_ForcedIncludes, ".ForcedIncludes", false ) || !GetString( funcStartIter, baseConfig.m_AssemblySearchPath, ".AssemblySearchPath", false ) || !GetString( funcStartIter, baseConfig.m_ForcedUsingAssemblies, ".ForcedUsingAssemblies", false ) || !GetString( funcStartIter, baseConfig.m_AdditionalOptions, ".AdditionalOptions", false ) || !GetString( funcStartIter, baseConfig.m_OutputDirectory, ".OutputDirectory", false ) || !GetString( funcStartIter, baseConfig.m_IntermediateDirectory, ".IntermediateDirectory", false ) || !GetString( funcStartIter, baseConfig.m_Xbox360DebuggerCommand,".Xbox360DebuggerCommand", false ) || !GetString( funcStartIter, baseConfig.m_LayoutDir, ".LayoutDir", false ) || !GetString( funcStartIter, baseConfig.m_LayoutExtensionFilter, ".LayoutExtensionFilter", false ) || !GetString( funcStartIter, baseConfig.m_DebuggerFlavor, ".DebuggerFlavor", false ) || !GetString( funcStartIter, baseConfig.m_AumidOverride, ".AumidOverride", false ) || !GetString( funcStartIter, baseConfig.m_PlatformToolset, ".PlatformToolset", false ) || !GetString( funcStartIter, baseConfig.m_DeploymentType, ".DeploymentType", false ) || !GetString( funcStartIter, baseConfig.m_DeploymentFiles, ".DeploymentFiles", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerCommandArguments, ".LocalDebuggerCommandArguments", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerWorkingDirectory, ".LocalDebuggerWorkingDirectory", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerCommand, ".LocalDebuggerCommand", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerEnvironment, ".LocalDebuggerEnvironment", false ) ) { return false; } // create configs Array< VSProjectConfig > configs( 16, true ); const BFFVariable * projectConfigs = BFFStackFrame::GetVar( ".ProjectConfigs" ); if ( projectConfigs ) { if ( projectConfigs->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".ProjectConfigs", projectConfigs->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = projectConfigs->GetArrayOfStructs(); const BFFVariable * const * end = structs.End(); for ( const BFFVariable ** it = structs.Begin(); it != end; ++it ) { const BFFVariable * s = *it; // start with the base configuration VSProjectConfig newConfig( baseConfig ); // .Platform must be provided if ( !GetStringFromStruct( s, ".Platform", newConfig.m_Platform ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Platform" ) ); return false; } // .Config must be provided if ( !GetStringFromStruct( s, ".Config", newConfig.m_Config ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Config" ) ); return false; } GetStringFromStruct( s, ".ProjectBuildCommand", newConfig.m_BuildCommand ); GetStringFromStruct( s, ".ProjectRebuildCommand", newConfig.m_RebuildCommand ); GetStringFromStruct( s, ".ProjectCleanCommand", newConfig.m_CleanCommand ); GetStringFromStruct( s, ".Output", newConfig.m_Output ); GetStringFromStruct( s, ".PreprocessorDefinitions", newConfig.m_PreprocessorDefinitions ); GetStringFromStruct( s, ".IncludeSearchPath", newConfig.m_IncludeSearchPath ); GetStringFromStruct( s, ".ForcedIncludes", newConfig.m_ForcedIncludes ); GetStringFromStruct( s, ".AssemblySearchPath", newConfig.m_AssemblySearchPath ); GetStringFromStruct( s, ".ForcedUsingAssemblies", newConfig.m_ForcedUsingAssemblies ); GetStringFromStruct( s, ".AdditionalOptions", newConfig.m_AdditionalOptions ); GetStringFromStruct( s, ".OutputDirectory", newConfig.m_OutputDirectory ); GetStringFromStruct( s, ".IntermediateDirectory", newConfig.m_IntermediateDirectory ); GetStringFromStruct( s, ".LayoutDir", newConfig.m_LayoutDir ); GetStringFromStruct( s, ".LayoutExtensionFilter", newConfig.m_LayoutExtensionFilter ); GetStringFromStruct( s, ".Xbox360DebuggerCommand", newConfig.m_Xbox360DebuggerCommand ); GetStringFromStruct( s, ".DebuggerFlavor", newConfig.m_DebuggerFlavor ); GetStringFromStruct( s, ".AumidOverride", newConfig.m_AumidOverride ); GetStringFromStruct( s, ".PlatformToolset", newConfig.m_PlatformToolset ); GetStringFromStruct( s, ".DeploymentType", newConfig.m_DeploymentType ); GetStringFromStruct( s, ".DeploymentFiles", newConfig.m_DeploymentFiles ); GetStringFromStruct( s, ".LocalDebuggerCommandArguments", newConfig.m_LocalDebuggerCommandArguments ); GetStringFromStruct( s, ".LocalDebuggerWorkingDirectory", newConfig.m_LocalDebuggerWorkingDirectory ); GetStringFromStruct( s, ".LocalDebuggerCommand", newConfig.m_LocalDebuggerCommand ); GetStringFromStruct( s, ".LocalDebuggerEnvironment", newConfig.m_LocalDebuggerEnvironment ); configs.Append( newConfig ); } } else { // no user specified configs, make some defaults // start from the default VSProjectConfig config( baseConfig ); // make the configs config.m_Platform = "Win32"; config.m_Config = "Debug"; configs.Append( config ); config.m_Config = "Release"; configs.Append( config ); config.m_Platform = "x64"; configs.Append( config ); config.m_Config = "Debug"; configs.Append( config ); } NodeGraph & ng = FBuild::Get().GetDependencyGraph(); // create all of the DirectoryListNodes we need Dependencies dirNodes( inputPaths.GetSize() ); if ( !GetDirectoryListNodeList( funcStartIter, inputPaths, Array< AString >(), Array< AString >(), true, nullptr, "ProjectInputPaths", dirNodes ) ) { return false; // GetDirectoryListNodeList will have emitted an error } // Check for existing node if ( ng.FindNode( projectOutput ) ) { Error::Error_1100_AlreadyDefined( funcStartIter, this, projectOutput ); return false; } VCXProjectNode * pn = ng.CreateVCXProjectNode( projectOutput, basePaths, dirNodes, inputPathsExclude, // TODO:B Remove this (handled by DirectoryListNode now) allowedFileExtensions, // TODO:B Remove this (handled by DirectoryListNode now) files, filesToExclude, rootNamespace, projectGuid, defaultLanguage, applicationEnvironment, configs, fileTypes, references, projectReferences ); ASSERT( pn ); return ProcessAlias( funcStartIter, pn ); }
// WriteProjectListings //------------------------------------------------------------------------------ void SLNGenerator::WriteProjectListings( const AString& solutionBasePath, const AString& solutionBuildProject, const Array< VCXProjectNode * > & projects, const Array< SLNSolutionFolder > & folders, const Array< SLNDependency > & slnDeps, AString & solutionBuildProjectGuid, Array< AString > & projectGuids, Array< AString > & solutionProjectsToFolder ) { // Project Listings VCXProjectNode ** const projectsEnd = projects.End(); for( VCXProjectNode ** it = projects.Begin() ; it != projectsEnd ; ++it ) { // check if this project is the master project const bool projectIsActive = ( solutionBuildProject.CompareI( (*it)->GetName() ) == 0 ); AStackString<> projectPath( (*it)->GetName() ); // get project base name only const char * lastSlash = projectPath.FindLast( NATIVE_SLASH ); const char * lastPeriod = projectPath.FindLast( '.' ); AStackString<> projectName( lastSlash ? lastSlash + 1 : projectPath.Get(), lastPeriod ? lastPeriod : projectPath.GetEnd() ); // retrieve projectGuid AStackString<> projectGuid; if ( (*it)->GetProjectGuid().GetLength() == 0 ) { // For backward compatibility, keep the preceding slash and .vcxproj extension for GUID generation AStackString<> projectNameForGuid( lastSlash ? lastSlash : projectPath.Get() ); VSProjectGenerator::FormatDeterministicProjectGUID( projectGuid, projectNameForGuid ); } else { projectGuid = (*it)->GetProjectGuid(); } // make project path relative projectPath.Replace( solutionBasePath.Get(), "" ); // projectGuid must be uppercase (visual does that, it changes the .sln otherwise) projectGuid.ToUpper(); if ( projectIsActive ) { ASSERT( solutionBuildProjectGuid.GetLength() == 0 ); solutionBuildProjectGuid = projectGuid; } Write( "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"%s\", \"%s\", \"%s\"\r\n", projectName.Get(), projectPath.Get(), projectGuid.Get() ); // Manage dependencies Array< AString > dependencyGUIDs( 64, true ); const AString & fullProjectPath = (*it)->GetName(); for ( const SLNDependency & deps : slnDeps ) { // is the set of deps relevant to this project? if ( deps.m_Projects.Find( fullProjectPath ) ) { // get all the projects this project depends on for ( const AString & dependency : deps.m_Dependencies ) { // For backward compatibility, keep the preceding slash and .vcxproj extension for GUID generation const char * projNameFromSlash = dependency.FindLast( NATIVE_SLASH ); AStackString<> projectNameForGuid( projNameFromSlash ? projNameFromSlash : dependency.Get() ); AStackString<> newGUID; VSProjectGenerator::FormatDeterministicProjectGUID( newGUID, projectNameForGuid ); dependencyGUIDs.Append( newGUID ); } } } if ( !dependencyGUIDs.IsEmpty() ) { Write( "\tProjectSection(ProjectDependencies) = postProject\r\n" ); for ( const AString & guid : dependencyGUIDs ) { Write( "\t\t%s = %s\r\n", guid.Get(), guid.Get() ); } Write( "\tEndProjectSection\r\n" ); } Write( "EndProject\r\n" ); projectGuids.Append( projectGuid ); // check if this project is in a solution folder const SLNSolutionFolder * const foldersEnd = folders.End(); for ( const SLNSolutionFolder * it2 = folders.Begin() ; it2 != foldersEnd ; ++it2 ) { // this has to be done here to have the same order of declaration (like visual) if ( it2->m_ProjectNames.Find( (*it)->GetName() ) ) { // generate a guid for the solution folder AStackString<> solutionFolderGuid; VSProjectGenerator::FormatDeterministicProjectGUID( solutionFolderGuid, it2->m_Path ); solutionFolderGuid.ToUpper(); AStackString<> projectToFolder; projectToFolder.Format( "\t\t%s = %s\r\n", projectGuid.Get(), solutionFolderGuid.Get() ); solutionProjectsToFolder.Append( projectToFolder ); } } } }
// CreateOverview //------------------------------------------------------------------------------ void Report::CreateOverview( const FBuildStats & stats ) { DoSectionTitle( "Overview", "overview" ); AStackString<> buffer; DoTableStart(); // Headings Write( "<tr><th width=150>Item</th><th>Details</th></tr>\n" ); // Full command line AStackString<> commandLineBuffer; Env::GetCmdLine( commandLineBuffer ); #if defined( __WINDOWS__ ) const char * exeExtension = strstr( commandLineBuffer.Get(), ".exe\"" ); const char * commandLine = exeExtension ? ( exeExtension + 5 ) : commandLineBuffer.Get(); // skip .exe + closing quote #else const char * commandLine = commandLineBuffer.Get(); #endif Write( "<tr><td width=80>Cmd Line Options</td><td>%s</td></tr>", commandLine ); // Target AStackString<> targets; const Node * rootNode = stats.GetRootNode(); if (rootNode->GetType() != Node::PROXY_NODE) { targets = rootNode->GetName(); } else { const Dependencies & childNodes = rootNode->GetStaticDependencies(); size_t num = childNodes.GetSize(); for ( size_t i=0; i<num; ++i ) { if ( i != 0 ) { targets += ", "; } const Node * child = childNodes[ i ].GetNode(); targets += child->GetName(); } } Write( "<tr><td>Target(s)</td><td>%s</td></tr>\n", targets.Get() ); // Result const bool buildOK = ( stats.GetRootNode()->GetState() == Node::UP_TO_DATE ); Write( "<tr><td>Result</td><td>%s</td></tr>\n", buildOK ? "OK" : "FAILED" ); // Real Time float totalBuildTime = stats.m_TotalBuildTime; stats.FormatTime( totalBuildTime, buffer ); Write( "<tr><td>Time</td><td>%s</td></tr>\n", buffer.Get() ); // Local CPU Time float totalLocalCPUInSeconds = (float)( (double)stats.m_TotalLocalCPUTimeMS / (double)1000 ); stats.FormatTime( totalLocalCPUInSeconds, buffer ); float localRatio = ( totalLocalCPUInSeconds / totalBuildTime ); Write( "<tr><td>CPU Time</td><td>%s (%2.1f:1)</td></tr>\n", buffer.Get(), localRatio ); // version info Write( "<tr><td>Version</td><td>%s %s</td></tr>\n", FBUILD_VERSION_STRING, FBUILD_VERSION_PLATFORM ); // report time char timeBuffer[ 256 ]; #if defined( __WINDOWS__ ) VERIFY( ::GetTimeFormat( LOCALE_NAME_USER_DEFAULT, // LCID Locale TIME_FORCE24HOURFORMAT, // DWORD dwFlags nullptr, // SYSTEMTIME *lpTime nullptr, // LPCTSTR lpFormat, timeBuffer, // LPTSTR lpTimeStr, 256 ) ); // int cchTime #elif defined( __APPLE__ ) timeBuffer[ 0 ] = '\000'; // TODO:MAC Implement GetTimeFormat in Report #elif defined( __LINUX__ ) timeBuffer[ 0 ] = '\000'; // TODO:LINUX Implement GetTimeFormat in Report #else #error Unknown platform #endif char dateBuffer[ 256 ]; #if defined( __WINDOWS__ ) VERIFY( ::GetDateFormat( LOCALE_NAME_USER_DEFAULT, // LCID Locale DATE_LONGDATE, // DWORD dwFlags nullptr, // SYSTEMTIME *lpTime nullptr, // LPCTSTR lpFormat, dateBuffer, // LPTSTR lpTimeStr, 256 ) ); // int cchTime #elif defined( __APPLE__ ) dateBuffer[ 0 ] = '\000'; // TODO:MAC Implement GetDateFormat in Report #elif defined( __LINUX__ ) dateBuffer[ 0 ] = '\000'; // TODO:LINUX Implement GetDateFormat in Report #else #error Unknown platform #endif // NOTE: leave space to patch in time taken later "^^^^ " Write( "<tr><td>Report Generated</td><td>^^^^ - %s %s</td></tr>\n", dateBuffer, timeBuffer ); DoTableStop(); }
// 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 ); }
/*static*/ bool ReflectionInfo::WriteDefinitions() { uint32_t numProblems = 0; const ReflectionInfo * ri = s_FirstReflectionInfo; for ( ; ri != nullptr; ri = ri->m_Next ) { // ignore abstract classes if ( ri->IsAbstract() ) { continue; } // Serialize a default instance to a MemoryStream MemoryStream ms; { // Create and serialize default instance if ( ri->IsObject() ) { RefObject * object = ri->CreateObject(); { TextWriter tw( ms ); tw.Write( object ); } FDELETE( object ); } else { ASSERT( ri->IsStruct() ) Struct * str = ri->CreateStruct(); { TextWriter tw( ms ); tw.Write( str, ri ); } FDELETE( str ); } } AStackString<> fileName; fileName.Format( "..\\Data\\Reflection\\.Definitions\\%s.definition", ri->GetTypeName() ); // avoid writing file if not changed // Try to open existing file FileStream f; if ( f.Open( fileName.Get(), FileStream::READ_ONLY ) ) { // read content const uint64_t fileSize = f.GetFileSize(); if ( fileSize == ms.GetSize() ) { AutoPtr< char > mem( (char *)Alloc( (size_t)fileSize ) ); if ( f.Read( mem.Get(), (size_t)fileSize ) == fileSize ) { if ( memcmp( mem.Get(), ms.GetData(), (size_t)fileSize ) == 0 ) { continue; // definition has not changed } } } f.Close(); } // Definition changed - try to save it int result = 0; AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize; uint32_t memErrSize; // existing definition? if ( FileIO::FileExists( fileName.Get() ) ) { // existing - need to open for edit? if ( FileIO::GetReadOnly( fileName ) ) { AStackString<> args( "edit " ); args += fileName; Process p; if ( p.Spawn( "p4", args.Get(), nullptr, nullptr ) ) { p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); result = p.WaitForExit(); } } } else { // new - open for add AStackString<> args( "add " ); args += fileName; Process p; if ( p.Spawn( "p4", args.Get(), nullptr, nullptr ) ) { p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); result = p.WaitForExit(); } } if ( result == 0 ) { if ( f.Open( fileName.Get(), FileStream::WRITE_ONLY ) ) { if ( f.Write( ms.GetData(), ms.GetSize() ) == ms.GetSize() ) { continue; // all ok! } } } // PROBLEM! OUTPUT( "Error writing definition '%s'\n", fileName.Get() ); if ( result != 0 ) { OUTPUT( "Perforce error: %s\n", memErr.Get() ); } ++numProblems; } if ( numProblems > 0 ) { FATALERROR( "Problem writing %u definition(s).\n", numProblems ); } return ( numProblems == 0 ); }
// Deserialize //------------------------------------------------------------------------------ void ToolManifest::Deserialize( IOStream & ms ) { ms.Read( m_ToolId ); ASSERT( m_Files.IsEmpty() ); uint32_t numFiles( 0 ); ms.Read( numFiles ); m_Files.SetCapacity( numFiles ); for ( size_t i=0; i<(size_t)numFiles; ++i ) { AStackString<> name; uint64_t timeStamp( 0 ); uint32_t hash( 0 ); uint32_t contentSize( 0 ); ms.Read( name ); ms.Read( timeStamp ); ms.Read( hash ); ms.Read( contentSize ); m_Files.Append( File( name, timeStamp, hash, nullptr, contentSize ) ); } // determine if any files are remaining from a previous run size_t numFilesAlreadySynchronized = 0; for ( size_t i=0; i<(size_t)numFiles; ++i ) { AStackString<> localFile; GetRemoteFilePath( (uint32_t)i, localFile ); // is this file already present? AutoPtr< FileStream > fileStream( FNEW( FileStream ) ); FileStream & f = *( fileStream.Get() ); if ( f.Open( localFile.Get() ) == false ) { continue; // file not found } if ( f.GetFileSize() != m_Files[ i ].m_ContentSize ) { continue; // file is not complete } AutoPtr< char > mem( (char *)ALLOC( (size_t)f.GetFileSize() ) ); if ( f.Read( mem.Get(), (size_t)f.GetFileSize() ) != f.GetFileSize() ) { continue; // problem reading file } if( Murmur3::Calc32( mem.Get(), (size_t)f.GetFileSize() ) != m_Files[ i ].m_Hash ) { continue; // file contents unexpected } // file present and ok m_Files[ i ].m_FileLock = fileStream.Release(); // NOTE: keep file open to prevent deletions m_Files[ i ].m_SyncState = File::SYNCHRONIZED; numFilesAlreadySynchronized++; } // Generate Environment ASSERT( m_RemoteEnvironmentString == nullptr ); // PATH= AStackString<> basePath; GetRemotePath( basePath ); AStackString<> paths; paths.Format( "PATH=%s", basePath.Get() ); // TMP= AStackString<> normalTmp; Env::GetEnvVariable( "TMP", normalTmp ); AStackString<> tmp; tmp.Format( "TMP=%s", normalTmp.Get() ); // SystemRoot= AStackString<> sysRoot( "SystemRoot=C:\\Windows" ); char * mem = (char *)ALLOC( paths.GetLength() + 1 + tmp.GetLength() + 1 + sysRoot.GetLength() + 1 + 1 ); m_RemoteEnvironmentString = mem; AString::Copy( paths.Get(), mem, paths.GetLength() + 1 ); // including null mem += ( paths.GetLength() + 1 ); // including null AString::Copy( tmp.Get(), mem, tmp.GetLength() + 1 ); // including null mem += ( tmp.GetLength() + 1 ); // including null AString::Copy( sysRoot.Get(), mem, sysRoot.GetLength() + 1 ); // including null mem += ( sysRoot.GetLength() + 1 ); // including null *mem = 0; ++mem; // double null // are all files already present? if ( numFilesAlreadySynchronized == m_Files.GetSize() ) { m_Synchronized = true; } }
//------------------------------------------------------------------------------ /*virtual*/ bool FunctionPrint::ParseFunction( const BFFIterator & functionNameStart, const BFFIterator * functionBodyStartToken, const BFFIterator * functionBodyStopToken, const BFFIterator * functionHeaderStartToken, const BFFIterator * functionHeaderStopToken ) const { (void)functionNameStart; (void)functionBodyStartToken; (void)functionBodyStopToken; if ( functionHeaderStartToken && functionHeaderStopToken && ( functionHeaderStartToken->GetDistTo( *functionHeaderStopToken ) >= 1 ) ) { BFFIterator start( *functionHeaderStartToken ); ASSERT( *start == BFFParser::BFF_FUNCTION_ARGS_OPEN ); start++; start.SkipWhiteSpace(); // a quoted string? const char c = *start; if ( ( c == '"' ) || ( c == '\'' ) ) { // find end of string BFFIterator stop( start ); stop.SkipString( c ); ASSERT( stop.GetCurrent() <= functionHeaderStopToken->GetCurrent() ); // should not be in this function if strings are not validly terminated // perform variable substitutions AStackString< 1024 > tmp; start++; // skip past opening quote if ( BFFParser::PerformVariableSubstitutions( start, stop, tmp ) == false ) { return false; // substitution will have emitted an error } tmp += '\n'; FLOG_BUILD_DIRECT( tmp.Get() ); } else if ( c == BFFParser::BFF_DECLARE_VAR_INTERNAL || c == BFFParser::BFF_DECLARE_VAR_PARENT ) { // find end of var name BFFIterator stop( start ); AStackString< BFFParser::MAX_VARIABLE_NAME_LENGTH > varName; bool parentScope = false; if ( BFFParser::ParseVariableName( stop, varName, parentScope ) == false ) { return false; } ASSERT( stop.GetCurrent() <= functionHeaderStopToken->GetCurrent() ); // should not be in this function if strings are not validly terminated const BFFVariable * var = nullptr; BFFStackFrame * const varFrame = ( parentScope ) ? BFFStackFrame::GetParentDeclaration( varName, BFFStackFrame::GetCurrent()->GetParent(), var ) : nullptr; if ( false == parentScope ) { var = BFFStackFrame::GetVar( varName, nullptr ); } if ( ( parentScope && ( nullptr == varFrame ) ) || ( nullptr == var ) ) { Error::Error_1009_UnknownVariable( start, this ); return false; } // dump the contents PrintVarRecurse( *var, 0 ); } else { Error::Error_1001_MissingStringStartToken( start, this ); return false; } } return true; }
// ReceiveFileData //------------------------------------------------------------------------------ bool ToolManifest::ReceiveFileData( uint32_t fileId, const void * data, size_t & dataSize ) { MutexHolder mh( m_Mutex ); File & f = m_Files[ fileId ]; // gracefully handle multiple receipts of the same data if ( f.m_Content ) { ASSERT( f.m_SyncState == File::SYNCHRONIZED ); return true; } ASSERT( f.m_SyncState == File::SYNCHRONIZING ); // prepare name for this file AStackString<> fileName; GetRemoteFilePath( fileId, fileName ); // prepare destination AStackString<> pathOnly( fileName.Get(), fileName.FindLast( NATIVE_SLASH ) ); if ( !FileIO::EnsurePathExists( pathOnly ) ) { return false; // FAILED } // write to disk FileStream fs; if ( !fs.Open( fileName.Get(), FileStream::WRITE_ONLY ) ) { return false; // FAILED } if ( fs.Write( data, dataSize ) != dataSize ) { return false; // FAILED } fs.Close(); // open read-only AutoPtr< FileStream > fileStream( FNEW( FileStream ) ); if ( fileStream.Get()->Open( fileName.Get(), FileStream::READ_ONLY ) == false ) { return false; // FAILED } // This file is now synchronized f.m_FileLock = fileStream.Release(); // NOTE: Keep file open to prevent deletion f.m_SyncState = File::SYNCHRONIZED; // is completely synchronized? const File * const end = m_Files.End(); for ( const File * it = m_Files.Begin(); it != end; ++it ) { if ( it->m_SyncState != File::SYNCHRONIZED ) { // still some files to be received return true; // file stored ok } } // all files received m_Synchronized = true; return true; // file stored ok }
// CompareHashTimes_Small //------------------------------------------------------------------------------ void TestHash::CompareHashTimes_Small() const { // some different strings to hash Array< AString > strings( 32, true ); strings.Append( AString( " " ) ); strings.Append( AString( "short" ) ); strings.Append( AString( "mediumstringmediumstring123456789" ) ); strings.Append( AString( "longstring_98274ncoif834jodhiorhmwe8r8wy48on87h8mhwejrijrdierwurd9j,8chm8hiuorciwriowjri" ) ); strings.Append( AString( "c:\\files\\subdir\\project\\thing\\stuff.cpp" ) ); const size_t numStrings = strings.GetSize(); const size_t numIterations = 102400; // calc datasize size_t dataSize( 0 ); for ( size_t i=0; i<numStrings; ++i ) { dataSize += strings[ i ].GetLength(); } dataSize *= numIterations; // xxHash - 32 { Timer t; uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += xxHash::Calc32( strings[ i ].Get(), strings[ i ].GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "xxHash-32 : %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } // xxHash - 64 { Timer t; uint64_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += xxHash::Calc64( strings[ i ].Get(), strings[ i ].GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "xxHash-64 : %2.3fs @ %6.3f GiB/s (hash: %016llx)\n", time, speed, crc ); } // Murmur3 - 32 { Timer t; uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += Murmur3::Calc32( strings[ i ].Get(), strings[ i ].GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "Murmur3-32 : %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } // Murmur3 - 128 { Timer t; uint64_t hashB( 0 ); uint64_t hashA( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { hashA += Murmur3::Calc128( strings[ i ].Get(), strings[ i ].GetLength(), hashB ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "Murmur3-128 : %2.3fs @ %6.3f GiB/s (%016llx, %016llx)\n", time, speed, hashA, hashB ); } // CRC32 - 8x8 slicing { Timer t; uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += CRC32::Calc( strings[ i ].Get(), strings[ i ].GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "CRC32 8x8 : %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } // CRC32 - "standard" algorithm { Timer t; uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += CRC32::Start(); crc += CRC32::Update( crc, strings[ i ].Get(), strings[ i ].GetLength() ); crc += CRC32::Stop( crc ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "CRC32 : %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } // CRC32Lower { Timer t; uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { crc += CRC32::CalcLower( strings[ i ].Get(), strings[ i ].GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "CRC32Lower : %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } // Murmur3 - 32 Lower { Timer t; // lower-case and hash it uint32_t crc( 0 ); for ( size_t j=0; j<numIterations; ++j ) { for ( size_t i=0; i<numStrings; ++i ) { const AString & str( strings[ i ] ); AStackString<> tmp; const size_t strLen( str.GetLength() ); tmp.SetLength( (uint32_t)strLen ); for ( size_t k=0; k<strLen; ++k ) { char c = str[ (uint32_t)k ]; tmp[ (uint32_t)k ] = ( ( c >= 'A' ) && ( c <= 'Z' ) ) ? 'a' + ( c - 'A' ) : c; } crc += Murmur3::Calc32( tmp.Get(), tmp.GetLength() ); } } float time = t.GetElapsed(); float speed = ( (float)dataSize / (float)( 1024 * 1024 * 1024 ) ) / time; OUTPUT( "Murmur3-32-Lower: %2.3fs @ %6.3f GiB/s (hash: 0x%x)\n", time, speed, crc ); } }
// Spawn //------------------------------------------------------------------------------ bool Process::Spawn( const char * executable, const char * args, const char * workingDir, const char * environment, bool shareHandles ) { ASSERT( !m_Started ); ASSERT( executable ); #if defined( __WINDOWS__ ) // Set up the start up info struct. STARTUPINFO si; ZeroMemory( &si, sizeof(STARTUPINFO) ); si.cb = sizeof( STARTUPINFO ); si.dwFlags |= STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; SECURITY_ATTRIBUTES sa; ZeroMemory( &sa, sizeof( SECURITY_ATTRIBUTES ) ); sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = nullptr; m_SharingHandles = shareHandles; if ( m_RedirectHandles ) { // create the pipes if ( shareHandles ) { si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); si.hStdError = GetStdHandle(STD_ERROR_HANDLE); si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } else { if ( ! CreatePipe( &m_StdOutRead, &m_StdOutWrite, &sa, MEGABYTE ) ) { return false; } SetHandleInformation( m_StdOutRead, HANDLE_FLAG_INHERIT, 0 ); if ( ! CreatePipe( &m_StdErrRead, &m_StdErrWrite, &sa, MEGABYTE ) ) { VERIFY( CloseHandle( m_StdOutRead ) ); VERIFY( CloseHandle( m_StdOutWrite ) ); return false; } SetHandleInformation( m_StdErrRead, HANDLE_FLAG_INHERIT, 0 ); si.hStdOutput = m_StdOutWrite; si.hStdError = m_StdErrWrite; si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); // m_StdInRead; } si.dwFlags |= STARTF_USESTDHANDLES; } // Make sure the first arg is the executable // We also need to make a copy, as CreateProcess can write back to this string AStackString< 1024 > fullArgs; fullArgs += '\"'; fullArgs += executable; fullArgs += '\"'; if ( args ) { fullArgs += ' '; fullArgs += args; } //fullArgs.Format( "\"%s\" %s", executable, args ); // create the child if ( !CreateProcess( nullptr, //executable, fullArgs.Get(), nullptr, nullptr, (BOOL)m_RedirectHandles, // inherit handles 0, (void *)environment, workingDir, &si, (LPPROCESS_INFORMATION)&m_ProcessInfo ) ) { return false; } m_Started = true; return true; #elif defined( __LINUX__ ) || defined( __APPLE__ ) // create StdOut and StdErr pipes to capture output of spawned process int stdOutPipeFDs[ 2 ]; int stdErrPipeFDs[ 2 ]; VERIFY( pipe( stdOutPipeFDs ) == 0 ); VERIFY( pipe( stdErrPipeFDs ) == 0 ); // fork the process const pid_t childProcessPid = fork(); if ( childProcessPid == -1 ) { // cleanup pipes VERIFY( close( stdOutPipeFDs[ 0 ] ) == 0 ); VERIFY( close( stdOutPipeFDs[ 1 ] ) == 0 ); VERIFY( close( stdErrPipeFDs[ 0 ] ) == 0 ); VERIFY( close( stdErrPipeFDs[ 1 ] ) == 0 ); ASSERT( false ); // fork failed - should not happen in normal operation return false; } const bool isChild = ( childProcessPid == 0 ); if ( isChild ) { VERIFY( dup2( stdOutPipeFDs[ 1 ], STDOUT_FILENO ) != -1 ); VERIFY( dup2( stdErrPipeFDs[ 1 ], STDERR_FILENO ) != -1 ); VERIFY( close( stdOutPipeFDs[ 0 ] ) == 0 ); VERIFY( close( stdOutPipeFDs[ 1 ] ) == 0 ); VERIFY( close( stdErrPipeFDs[ 0 ] ) == 0 ); VERIFY( close( stdErrPipeFDs[ 1 ] ) == 0 ); if ( workingDir ) { FileIO::SetCurrentDir( AStackString<>( workingDir ) ); } // split args AString fullArgs( args ); Array< AString > splitArgs( 64, true ); fullArgs.Tokenize( splitArgs ); // prepare args const size_t numArgs = splitArgs.GetSize(); char ** argv = FNEW( char *[ numArgs + 2 ] ); argv[ 0 ] = const_cast< char * >( executable ); for ( size_t i=0; i<numArgs; ++i ) { AString & thisArg = splitArgs[ i ]; if ( thisArg.BeginsWith( '"' ) && thisArg.EndsWith( '"' ) ) { // strip quotes thisArg.SetLength( thisArg.GetLength() - 1 ); // trim end quote argv[ i + 1 ] = thisArg.Get() + 1; // skip start quote continue; } argv[ i + 1 ] = thisArg.Get(); // leave arg as-is } argv[ numArgs + 1 ] = nullptr; // transfer execution to new executable execv( executable, argv ); exit( -1 ); // only get here if execv fails } else {