// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult ExecNode::DoBuild( Job * job ) { // If the workingDir is empty, use the current dir for the process const char * workingDir = m_WorkingDir.IsEmpty() ? nullptr : m_WorkingDir.Get(); AStackString<> fullArgs( m_Arguments ); fullArgs.Replace( "%1", m_SourceFile->GetName().Get() ); fullArgs.Replace( "%2", GetName().Get() ); EmitCompilationMessage( fullArgs ); // spawn the process Process p; bool spawnOK = p.Spawn( m_Executable->GetName().Get(), fullArgs.Get(), workingDir, FBuild::Get().GetEnvironmentString() ); if ( !spawnOK ) { FLOG_ERROR( "Failed to spawn process 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(); // did the executable fail? if ( result != m_ExpectedReturnCode ) { // something went wrong, print details Node::DumpOutput( job, memOut.Get(), memOutSize ); Node::DumpOutput( job, memErr.Get(), memErrSize ); FLOG_ERROR( "Execution failed (error %i) '%s'", result, GetName().Get() ); return NODE_RESULT_FAILED; } // update the file's "last modified" time m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); return NODE_RESULT_OK; }
// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult TestNode::DoBuild( Job * job ) { // If the workingDir is empty, use the current dir for the process const char * workingDir = m_TestWorkingDir.IsEmpty() ? nullptr : m_TestWorkingDir.Get(); EmitCompilationMessage( workingDir ); // spawn the process Process p; bool spawnOK = p.Spawn( GetTestExecutable()->GetName().Get(), m_TestArguments.Get(), workingDir, FBuild::Get().GetEnvironmentString() ); if ( !spawnOK ) { FLOG_ERROR( "Failed to spawn process 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; bool timedOut = !p.ReadAllData( memOut, &memOutSize, memErr, &memErrSize, m_TestTimeOut ); if ( timedOut ) { FLOG_ERROR( "Test timed out after %u ms (%s)", m_TestTimeOut, m_TestExecutable.Get() ); return NODE_RESULT_FAILED; } ASSERT( !p.IsRunning() ); // Get result int result = p.WaitForExit(); if ( result != 0 ) { // something went wrong, print details Node::DumpOutput( job, memOut.Get(), memOutSize ); Node::DumpOutput( job, memErr.Get(), memErrSize ); } // write the test output (saved for pass or fail) FileStream fs; if ( fs.Open( GetName().Get(), FileStream::WRITE_ONLY ) == false ) { FLOG_ERROR( "Failed to open test output file '%s'", GetName().Get() ); return NODE_RESULT_FAILED; } if ( ( memOut.Get() && ( fs.Write( memOut.Get(), memOutSize ) != memOutSize ) ) || ( memErr.Get() && ( fs.Write( memErr.Get(), memErrSize ) != memErrSize ) ) ) { FLOG_ERROR( "Failed to write test output file '%s'", GetName().Get() ); return NODE_RESULT_FAILED; } fs.Close(); // did the test fail? if ( result != 0 ) { FLOG_ERROR( "Test failed (error %i) '%s'", result, GetName().Get() ); return NODE_RESULT_FAILED; } // test passed // we only keep the "last modified" time of the test output for passed tests m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); return NODE_RESULT_OK; }
// 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; }
// 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; }
// DoBuild //------------------------------------------------------------------------------ /*virtual*/ Node::BuildResult CSNode::DoBuild( Job * job ) { // 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; if ( p.Spawn( m_CompilerPath.Get(), fullArgs.GetFinalArgs().Get(), workingDir, environment ) == false ) { FLOG_ERROR( "Failed to spawn process to build '%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 ); // Get result ASSERT( !p.IsRunning() ); int result = p.WaitForExit(); bool ok = ( result == 0 ); if ( !ok ) { // something went wrong, print details Node::DumpOutput( job, memOut.Get(), memOutSize ); Node::DumpOutput( job, memErr.Get(), memErrSize ); goto failed; } if ( !FileIO::FileExists( m_Name.Get() ) ) { FLOG_ERROR( "Object missing despite success for '%s'", GetName().Get() ); return NODE_RESULT_FAILED; } // record new file time m_Stamp = FileIO::GetFileLastWriteTime( m_Name ); return NODE_RESULT_OK; failed: FLOG_ERROR( "Failed to build Object (error %i) '%s'", result, GetName().Get() ); return NODE_RESULT_FAILED; }