// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult LibraryNode::DoBuild( Job * UNUSED( job ) ) { // delete library before creation (so ar.exe will not merge old symbols) if ( FileIO::FileExists( GetName().Get() ) ) { FileIO::FileDelete( GetName().Get() ); } // Format compiler args string Args fullArgs; if ( !BuildArgs( fullArgs ) ) { return NODE_RESULT_FAILED; // BuildArgs will have emitted an error } // use the exe launch dir as the working dir const char * workingDir = nullptr; const char * environment = FBuild::Get().GetEnvironmentString(); EmitCompilationMessage( fullArgs ); // spawn the process Process p; bool spawnOK = p.Spawn( m_LibrarianPath.Get(), fullArgs.GetFinalArgs().Get(), workingDir, environment ); if ( !spawnOK ) { FLOG_ERROR( "Failed to spawn process for Library creation for '%s'", GetName().Get() ); return NODE_RESULT_FAILED; } // capture all of the stdout and stderr AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize = 0; uint32_t memErrSize = 0; p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); ASSERT( !p.IsRunning() ); // Get result int result = p.WaitForExit(); if ( result != 0 ) { if ( memOut.Get() ) { FLOG_ERROR_DIRECT( memOut.Get() ); } if ( memErr.Get() ) { FLOG_ERROR_DIRECT( memErr.Get() ); } } // did the executable fail? if ( result != 0 ) { FLOG_ERROR( "Failed to build Library (error %i) '%s'", result, GetName().Get() ); return NODE_RESULT_FAILED; } // record time stamp for next time m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); ASSERT( m_Stamp ); return NODE_RESULT_OK; }
// DumpOutput //------------------------------------------------------------------------------ /*static*/ void Node::DumpOutput( Job * job, const char * data, uint32_t dataSize, const Array< AString > * exclusions ) { if ( ( data == nullptr ) || ( dataSize == 0 ) ) { return; } // preallocate a large buffer AString buffer( MEGABYTE ); const char * end = data + dataSize; while( data < end ) { // find the limits of the current line const char * lineStart = data; const char * lineEnd = lineStart; while ( lineEnd < end ) { if ( ( *lineEnd == '\r' ) || ( *lineEnd == '\n' ) ) { break; } lineEnd++; } if ( lineStart != lineEnd ) // ignore empty { // make a copy of the line to output AStackString< 1024 > copy( lineStart, lineEnd ); // skip this line? bool skip = false; if ( exclusions ) { const AString * iter = exclusions->Begin(); const AString * const endIter = exclusions->End(); while ( iter != endIter ) { if ( copy.BeginsWith( *iter ) ) { skip = true; break; } iter++; } } if ( !skip ) { copy += '\n'; // Clang format fixup for Visual Studio // (FBuild is null in remote context - fixup occurs on master) if ( FBuild::IsValid() && FBuild::Get().GetOptions().m_FixupErrorPaths ) { FixupPathForVSIntegration( copy ); } // if the buffer needs to grow, grow in 1MiB chunks if ( buffer.GetLength() + copy.GetLength() > buffer.GetReserved() ) { buffer.SetReserved( buffer.GetReserved() + MEGABYTE ); } buffer += copy; } } data = ( lineEnd + 1 ); } // print everything at once FLOG_ERROR_DIRECT( buffer.Get() ); // send output back to client if operating remotely if ( job && ( !job->IsLocal() ) ) { job->Error( "%s", buffer.Get() ); } }
// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult LinkerNode::DoBuild( Job * job ) { DoPreLinkCleanup(); // Make sure the implib output directory exists if (m_ImportLibName.IsEmpty() == false) { AStackString<> cleanPath; NodeGraph::CleanPath(m_ImportLibName, cleanPath); if (EnsurePathExistsForFile(cleanPath) == false) { // EnsurePathExistsForFile will have emitted error return NODE_RESULT_FAILED; } } // Format compiler args string Args fullArgs; if ( !BuildArgs( fullArgs ) ) { return NODE_RESULT_FAILED; // BuildArgs will have emitted an error } // use the exe launch dir as the working dir const char * workingDir = nullptr; const char * environment = FBuild::Get().GetEnvironmentString(); EmitCompilationMessage( fullArgs ); // we retry if linker crashes uint32_t attempt( 0 ); for (;;) { ++attempt; // spawn the process Process p( FBuild::Get().GetAbortBuildPointer() ); bool spawnOK = p.Spawn( m_Linker.Get(), fullArgs.GetFinalArgs().Get(), workingDir, environment ); if ( !spawnOK ) { if ( p.HasAborted() ) { return NODE_RESULT_FAILED; } FLOG_ERROR( "Failed to spawn process '%s' for %s creation for '%s'", m_Linker.Get(), GetDLLOrExe(), GetName().Get() ); return NODE_RESULT_FAILED; } // capture all of the stdout and stderr AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize = 0; uint32_t memErrSize = 0; p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); ASSERT( !p.IsRunning() ); // Get result int result = p.WaitForExit(); if ( p.HasAborted() ) { return NODE_RESULT_FAILED; } // did the executable fail? if ( result != 0 ) { // Handle bugs in the MSVC linker if ( GetFlag( LINK_FLAG_MSVC ) && ( attempt == 1 ) ) { // Did the linker have an ICE (crash) (LNK1000)? if ( result == 1000 ) { FLOG_WARN( "FBuild: Warning: Linker crashed (LNK1000), retrying '%s'", GetName().Get() ); continue; // try again } // Did the linker have an "unexpected PDB error" (LNK1318)? // Example: "fatal error LNK1318: Unexpected PDB error; CORRUPT (13)" // (The linker or mspdbsrv.exe (as of VS2017) seems to have bugs which cause the PDB // to sometimes be corrupted when doing very large links, possibly because the linker // is running out of memory) if ( result == 1318 ) { FLOG_WARN( "FBuild: Warning: Linker corrupted the PDB (LNK1318), retrying '%s'", GetName().Get() ); continue; // try again } } if ( memOut.Get() ) { job->ErrorPreformatted( memOut.Get() ); } if ( memErr.Get() ) { job->ErrorPreformatted( memErr.Get() ); } // some other (genuine) linker failure FLOG_ERROR( "Failed to build %s (error %i) '%s'", GetDLLOrExe(), result, GetName().Get() ); return NODE_RESULT_FAILED; } else { // If "warnings as errors" is enabled (/WX) we don't need to check // (since compilation will fail anyway, and the output will be shown) if ( GetFlag( LINK_FLAG_MSVC ) && !GetFlag( LINK_FLAG_WARNINGS_AS_ERRORS_MSVC ) ) { HandleWarningsMSVC( job, GetName(), memOut.Get(), memOutSize ); } break; // success! } } // post-link stamp step if ( m_LinkerStampExe.IsEmpty() == false ) { const Node * linkerStampExe = m_StaticDependencies.End()[ -1 ].GetNode(); EmitStampMessage(); Process stampProcess( FBuild::Get().GetAbortBuildPointer() ); bool spawnOk = stampProcess.Spawn( linkerStampExe->GetName().Get(), m_LinkerStampExeArgs.Get(), nullptr, // working dir nullptr ); // env if ( spawnOk == false ) { if ( stampProcess.HasAborted() ) { return NODE_RESULT_FAILED; } FLOG_ERROR( "Failed to spawn process '%s' for '%s' stamping of '%s'", linkerStampExe->GetName().Get(), GetDLLOrExe(), GetName().Get() ); return NODE_RESULT_FAILED; } // capture all of the stdout and stderr AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize = 0; uint32_t memErrSize = 0; stampProcess.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); ASSERT( !stampProcess.IsRunning() ); // Get result int result = stampProcess.WaitForExit(); if ( stampProcess.HasAborted() ) { return NODE_RESULT_FAILED; } // did the executable fail? if ( result != 0 ) { if ( memOut.Get() ) { FLOG_ERROR_DIRECT( memOut.Get() ); } if ( memErr.Get() ) { FLOG_ERROR_DIRECT( memErr.Get() ); } FLOG_ERROR( "Failed to stamp %s '%s' (error %i - '%s')", GetDLLOrExe(), GetName().Get(), result, m_LinkerStampExe.Get() ); return NODE_RESULT_FAILED; } // success! } // record time stamp for next time m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); ASSERT( m_Stamp ); return NODE_RESULT_OK; }
// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult LinkerNode::DoBuild( Job * UNUSED( job ) ) { DoPreLinkCleanup(); // Make sure the implib output directory exists if (m_ImportLibName.IsEmpty() == false) { AStackString<> cleanPath; NodeGraph::CleanPath(m_ImportLibName, cleanPath); if (EnsurePathExistsForFile(cleanPath) == false) { // EnsurePathExistsForFile will have emitted error return NODE_RESULT_FAILED; } } // Format compiler args string Args fullArgs; if ( !BuildArgs( fullArgs ) ) { return NODE_RESULT_FAILED; // BuildArgs will have emitted an error } // use the exe launch dir as the working dir const char * workingDir = nullptr; const char * environment = FBuild::Get().GetEnvironmentString(); EmitCompilationMessage( fullArgs ); // we retry if linker crashes uint32_t attempt( 0 ); for (;;) { ++attempt; // spawn the process Process p; bool spawnOK = p.Spawn( m_Linker.Get(), fullArgs.GetFinalArgs().Get(), workingDir, environment ); if ( !spawnOK ) { FLOG_ERROR( "Failed to spawn process '%s' for %s creation for '%s'", m_Linker.Get(), GetDLLOrExe(), GetName().Get() ); return NODE_RESULT_FAILED; } // capture all of the stdout and stderr AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize = 0; uint32_t memErrSize = 0; p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); ASSERT( !p.IsRunning() ); // Get result int result = p.WaitForExit(); // did the executable fail? if ( result != 0 ) { // did the linker have an ICE (LNK1000)? if ( GetFlag( LINK_FLAG_MSVC ) && ( result == 1000 ) && ( attempt == 1 ) ) { FLOG_WARN( "FBuild: Warning: Linker crashed (LNK1000), retrying '%s'", GetName().Get() ); continue; // try again } if ( memOut.Get() ) { m_BuildOutputMessages.Append( memOut.Get(), memOutSize ); FLOG_ERROR_DIRECT( memOut.Get() ); } if ( memErr.Get() ) { m_BuildOutputMessages.Append( memErr.Get(), memErrSize ); FLOG_ERROR_DIRECT( memErr.Get() ); } // some other (genuine) linker failure FLOG_ERROR( "Failed to build %s (error %i) '%s'", GetDLLOrExe(), result, GetName().Get() ); return NODE_RESULT_FAILED; } else { break; // success! } } // post-link stamp step if ( m_LinkerStampExe ) { EmitStampMessage(); Process stampProcess; bool spawnOk = stampProcess.Spawn( m_LinkerStampExe->GetName().Get(), m_LinkerStampExeArgs.Get(), nullptr, // working dir nullptr ); // env if ( spawnOk == false ) { FLOG_ERROR( "Failed to spawn process '%s' for '%s' stamping of '%s'", m_LinkerStampExe->GetName().Get(), GetDLLOrExe(), GetName().Get() ); return NODE_RESULT_FAILED; } // capture all of the stdout and stderr AutoPtr< char > memOut; AutoPtr< char > memErr; uint32_t memOutSize = 0; uint32_t memErrSize = 0; stampProcess.ReadAllData( memOut, &memOutSize, memErr, &memErrSize ); ASSERT( !stampProcess.IsRunning() ); // Get result int result = stampProcess.WaitForExit(); // did the executable fail? if ( result != 0 ) { if ( memOut.Get() ) { FLOG_ERROR_DIRECT( memOut.Get() ); } if ( memErr.Get() ) { FLOG_ERROR_DIRECT( memErr.Get() ); } FLOG_ERROR( "Failed to stamp %s '%s' (error %i - '%s')", GetDLLOrExe(), GetName().Get(), result, m_LinkerStampExe->GetName().Get() ); return NODE_RESULT_FAILED; } // success! } // record time stamp for next time m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); ASSERT( m_Stamp ); return NODE_RESULT_OK; }