// FBuild.cpp : Defines the entry point for the console application. //------------------------------------------------------------------------------ // Includes //------------------------------------------------------------------------------ #include "Tools/FBuild/FBuildCore/FBuild.h" #include "Tools/FBuild/FBuildCore/FBuildVersion.h" #include "Tools/FBuild/FBuildCore/FLog.h" #include "Core/Containers/Array.h" #include "Core/Env/Env.h" #include "Core/Process/Process.h" #include "Core/Process/SharedMemory.h" #include "Core/Process/SystemMutex.h" #include "Core/Process/Thread.h" #include "Core/Strings/AStackString.h" #include "Core/Tracing/Tracing.h" #include #include #if defined( __WINDOWS__ ) #include #endif // Return Codes //------------------------------------------------------------------------------ enum ReturnCodes { FBUILD_OK = 0, FBUILD_BUILD_FAILED = -1, FBUILD_ERROR_LOADING_BFF = -2, FBUILD_BAD_ARGS = -3, FBUILD_ALREADY_RUNNING = -4, FBUILD_FAILED_TO_SPAWN_WRAPPER = -5, FBUILD_FAILED_TO_SPAWN_WRAPPER_FINAL = -6 }; // Headers //------------------------------------------------------------------------------ void DisplayHelp(); void DisplayVersion(); #if defined( __WINDOWS ) BOOL CtrlHandler( DWORD fdwCtrlType ); // Handle Ctrl+C etc #else // TODO:MAC Implement CtrlHandler // TODO:LINUX Implement CtrlHandler #endif int WrapperMainProcess( const AString & args ); int WrapperIntermediateProcess( const AString & args ); // Misc //------------------------------------------------------------------------------ enum WrapperMode { WRAPPER_MODE_NONE, WRAPPER_MODE_MAIN_PROCESS, WRAPPER_MODE_INTERMEDIATE_PROCESS, WRAPPER_MODE_FINAL_PROCESS }; // data passed between processes in "wrapper" mode struct SharedData { bool Started; int ReturnCode; }; // Global //------------------------------------------------------------------------------ SharedMemory g_SharedMemory; // ensure only one FASTBuild instance is running at a time SystemMutex mainProcess( "Global\\FASTBuild" ); // in "wrapper" mode, Main process monitors life of final process using this // (when main process can acquire, final process has terminated) SystemMutex finalProcess( "Global\\FASTBuild_Final" ); // main //------------------------------------------------------------------------------ int main(int argc, char * argv[]) { #if defined( __WINDOWS ) VERIFY( SetConsoleCtrlHandler( (PHANDLER_ROUTINE)CtrlHandler, TRUE ) ); // Register #endif Timer t; // handle cmd line args Array< AString > targets( 8, true ); bool cleanBuild = false; bool verbose = false; bool progressBar = true; bool useCacheRead = false; bool useCacheWrite = false; bool allowDistributed = false; bool showCommands = false; bool showSummary = false; bool noOutputBuffering = false; bool report = false; bool fixupErrorPaths = false; bool waitMode = false; int32_t numWorkers = -1; WrapperMode wrapperMode( WRAPPER_MODE_NONE ); AStackString<> args; const char * configFile = nullptr; for ( int32_t i=1; i thisArg( argv[ i ] ); args += thisArg; args += ' '; // options start with a '-' if ( thisArg.BeginsWith( '-' ) ) { if ( thisArg == "-cache" ) { useCacheRead = true; useCacheWrite = true; continue; } else if ( thisArg == "-cacheread" ) { useCacheRead = true; continue; } else if ( thisArg == "-cachewrite" ) { useCacheWrite = true; continue; } else if ( thisArg == "-clean" ) { cleanBuild = true; continue; } else if ( thisArg == "-config" ) { int pathIndex = ( i + 1 ); if ( pathIndex >= argc ) { OUTPUT( "FBuild: Error: Missing for '-config' argument\n" ); OUTPUT( "Try \"FBuild.exe -help\"\n" ); return FBUILD_BAD_ARGS; } configFile = argv[ pathIndex ]; i++; // skip extra arg we've consumed // add to args we might pass to subprocess args += configFile; args += ' '; continue; } #ifdef DEBUG else if ( thisArg == "-debug" ) { ASSERT( false && "Break due to '-debug' argument - attach debugger!" ); continue; } #endif else if ( thisArg == "-dist" ) { allowDistributed = true; continue; } else if ( thisArg == "-fixuperrorpaths" ) { fixupErrorPaths = true; continue; } else if ( thisArg == "-help" ) { DisplayHelp(); return FBUILD_OK; // exit app } else if ( thisArg.BeginsWith( "-j" ) && sscanf( thisArg.Get(), "-j%i", &numWorkers ) == 1 ) { continue; // 'numWorkers' will contain value now } else if ( thisArg == "-nooutputbuffering" ) { noOutputBuffering = true; continue; } else if ( thisArg == "-noprogress" ) { progressBar = false; continue; } else if ( thisArg == "-report" ) { report = true; continue; } else if ( thisArg == "-showcmds" ) { showCommands = true; continue; } else if ( thisArg == "-summary" ) { showSummary = true; continue; } else if ( thisArg == "-verbose" ) { verbose = true; continue; } else if ( thisArg == "-version" ) { DisplayVersion(); return FBUILD_OK; // exit app } else if ( thisArg == "-vs" ) { progressBar = false; noOutputBuffering = true; fixupErrorPaths = true; wrapperMode = WRAPPER_MODE_MAIN_PROCESS; continue; } else if ( thisArg == "-wait" ) { waitMode = true; continue; } else if ( thisArg == "-wrapper") { wrapperMode = WRAPPER_MODE_MAIN_PROCESS; continue; } else if ( thisArg == "-wrapperintermediate") { wrapperMode = WRAPPER_MODE_INTERMEDIATE_PROCESS; continue; } else if ( thisArg == "-wrapperfinal") { wrapperMode = WRAPPER_MODE_FINAL_PROCESS; continue; } // can't use FLOG_ERROR as FLog is not initialized OUTPUT( "FBuild: Error: Unknown argument '%s'\n", thisArg.Get() ); OUTPUT( "Try \"FBuild.exe -help\"\n" ); return FBUILD_BAD_ARGS; } else { // assume target targets.Append( AStackString<>( thisArg ) ); } } // cache mode environment variable (if not supplied on cmd line) if ( ( useCacheRead == false ) && ( useCacheWrite == false ) ) { AStackString<> cacheMode; if ( Env::GetEnvVariable( "FASTBUILD_CACHE_MODE", cacheMode ) ) { if ( cacheMode == "r" ) { useCacheRead = true; } else if ( cacheMode == "w" ) { useCacheWrite = true; } else if ( cacheMode == "rw" ) { useCacheRead = true; useCacheWrite = true; } else { OUTPUT( "FASTBUILD_CACHE_MODE is invalid (%s)\n", cacheMode.Get() ); return FBUILD_BAD_ARGS; } } } if ( wrapperMode == WRAPPER_MODE_INTERMEDIATE_PROCESS ) { return WrapperIntermediateProcess( args ); } #if defined( __WINDOWS__ ) // TODO:MAC Implement SetPriorityClass // TODO:LINUX Implement SetPriorityClass VERIFY( SetPriorityClass( GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS ) ); #endif if ( noOutputBuffering ) { // prevent buffering, so output in Visual Studio is quick setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } // only 1 instance running at a time if ( ( wrapperMode == WRAPPER_MODE_MAIN_PROCESS ) || ( wrapperMode == WRAPPER_MODE_NONE ) ) { if ( mainProcess.TryLock() == false ) { if ( waitMode == false ) { OUTPUT( "FBuild: Error: Another instance of FASTBuild is already running." ); return FBUILD_ALREADY_RUNNING; } OUTPUT( "FBuild: Waiting for another FASTBuild to terminate due to -wait option.\n" ); while( mainProcess.TryLock() == false ) { Thread::Sleep( 1000 ); if ( FBuild::GetStopBuild() ) { return FBUILD_BUILD_FAILED; } } } } if ( wrapperMode == WRAPPER_MODE_MAIN_PROCESS ) { return WrapperMainProcess( args ); } ASSERT( ( wrapperMode == WRAPPER_MODE_NONE ) || ( wrapperMode == WRAPPER_MODE_FINAL_PROCESS ) ); SharedData * sharedData = nullptr; if ( wrapperMode == WRAPPER_MODE_FINAL_PROCESS ) { while ( !finalProcess.TryLock() ) { OUTPUT( "FBuild: Waiting for another FASTBuild to terminate...\n" ); if ( mainProcess.TryLock() ) { // main process has aborted, terminate return FBUILD_FAILED_TO_SPAWN_WRAPPER_FINAL; } Thread::Sleep( 1000 ); } g_SharedMemory.Open( "FASTBuildSharedMemory", sizeof( SharedData ) ); // signal to "main" process that we have started sharedData = (SharedData *)g_SharedMemory.GetPtr(); if ( sharedData == nullptr ) { // main process was killed while we were waiting return FBUILD_FAILED_TO_SPAWN_WRAPPER_FINAL; } sharedData->Started = true; } FBuildOptions options; options.m_ShowProgress = progressBar; options.m_ShowInfo = verbose; options.m_ShowCommandLines = showCommands; options.m_UseCacheRead = useCacheRead; options.m_UseCacheWrite = useCacheWrite; if ( numWorkers >= 0 ) { options.m_NumWorkerThreads = numWorkers; } options.m_ForceCleanBuild = cleanBuild; options.m_AllowDistributed = allowDistributed; options.m_ShowSummary = showSummary; if ( configFile ) { options.m_ConfigFile = configFile; } options.m_SaveDBOnCompletion = true; options.m_GenerateReport = report; options.m_WrapperChild = ( wrapperMode == WRAPPER_MODE_FINAL_PROCESS ); options.m_FixupErrorPaths = fixupErrorPaths; if ( targets.GetSize() > 1 ) { options.m_StopOnFirstError = false; // when building multiple targets, try to build as much as possible } FBuild fBuild( options ); if ( targets.IsEmpty() ) { FLOG_INFO( "No target specified, defaulting to target 'all'" ); targets.Append( AString( "all" ) ); } // will load the dependency graph if available if ( !fBuild.Initialize() ) { if ( sharedData ) { sharedData->ReturnCode = FBUILD_ERROR_LOADING_BFF; } return FBUILD_ERROR_LOADING_BFF; } bool result = fBuild.Build( targets ); if ( sharedData ) { sharedData->ReturnCode = ( result == true ) ? FBUILD_OK : FBUILD_BUILD_FAILED; } return ( result == true ) ? FBUILD_OK : FBUILD_BUILD_FAILED; } // DisplayHelp //------------------------------------------------------------------------------ void DisplayHelp() { DisplayVersion(); OUTPUT( "----------------------------------------------------------------------\n" "Usage: fbuild.exe [options] [target1]..[targetn]\n" "----------------------------------------------------------------------\n" "Options:\n" " -cache[read|write] Control use of the build cache.\n" " -clean Force a clean build.\n" " -config [path] Explicitly specify the config file to use\n" ); #ifdef DEBUG OUTPUT( " -debug Break at startup, to attach debugger.\n" ); #endif OUTPUT( " -dist Allow distributed compilation.\n" " -fixuperrorpaths Reformat error paths to be VisualStudio friendly.\n" " -help Show this help.\n" " -jX Explicitly set worker thread count X, instead of\n" " default of NUMBER_OF_PROCESSORS. Set to 0 to build\n" " everything in the main thread.\n" " -nooutputbuffering Don't buffer output when writing to stdout and\n" " stderr. Useful if monitoring build output.\n" " -noprogress Don't show the progress bar while building.\n" " -report Ouput a detailed report at the end of the build,\n" " to report.html. This will lengthen the total build\n" " time.\n" " -showcmds Show command lines used to launch external processes.\n" " -summary Show a summary at the end of the build.\n" " -verbose Show detailed diagnostic information. This will slow\n" " down building.\n" " -version Print version and exit. No other work will be\n" " performed.\n" " -vs VisualStudio mode. Same as '-noprogress',\n" " '-nooutputbuffering', '-fixuperrorpaths'\n" " & '-wrapper'.\n" " -wait Wait for a previous build to complete before starting.\n" " (Slower than building both targets in one invocation).\n" " -wrapper Spawn a sub-process to gracefully handle termination\n" " from Visual Studio.\n" "----------------------------------------------------------------------\n" ); } // DisplayVersion //------------------------------------------------------------------------------ void DisplayVersion() { #ifdef DEBUG #define VERSION_CONFIG " (DEBUG) " #else #define VERSION_CONFIG " " #endif OUTPUT( "FASTBuild - " FBUILD_VERSION_STRING " " FBUILD_VERSION_PLATFORM VERSION_CONFIG " - " "Copyright 2012-2014 Franta Fulin - http://www.fastbuild.org\n" ); #undef VERSION_CONFIG } // CtrlHandler //------------------------------------------------------------------------------ #if defined( __WINDOWS__ ) BOOL CtrlHandler( DWORD UNUSED( fdwCtrlType ) ) { // tell FBuild we want to stop the build cleanly FBuild::AbortBuild(); // only printf output for the first break received static bool received = false; if ( received == false ) { received = true; // get the console colours CONSOLE_SCREEN_BUFFER_INFO consoleInfo; VERIFY( GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &consoleInfo ) ); // print a big red msg VERIFY( SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), FOREGROUND_RED ) ); OUTPUT( "<<<< ABORT SIGNAL RECEIVED >>>>\n" ); // put the console back to normal VERIFY( SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), consoleInfo.wAttributes ) ); } return TRUE; // tell Windows we've "handled" it } #endif // WrapperMainProcess //------------------------------------------------------------------------------ int WrapperMainProcess( const AString & args ) { // Create SharedMemory to communicate between Main and Final process SharedMemory sm; g_SharedMemory.Create( "FASTBuildSharedMemory", sizeof( SharedData ) ); SharedData * sd = (SharedData *)g_SharedMemory.GetPtr(); memset( sd, 0, sizeof( SharedData ) ); // launch intermediate process AStackString<> argsCopy( args ); argsCopy += " -wrapperintermediate"; Process p; if ( !p.Spawn( "fbuild.exe", argsCopy.Get(), nullptr, nullptr, true ) ) // true = forward output to our tty { return FBUILD_FAILED_TO_SPAWN_WRAPPER; } // the intermediate process will exit immediately after launching the final // process p.WaitForExit(); // wait for final process to signal as started while ( sd->Started == false ) { Thread::Sleep( 1 ); } // wait for final process to exit for ( ;; ) { if ( finalProcess.TryLock() == true ) { break; // final process has released the mutex } Thread::Sleep( 1 ); } return sd->ReturnCode; } // WrapperIntermediateProcess //------------------------------------------------------------------------------ int WrapperIntermediateProcess( const AString & args ) { // launch final process AStackString<> argsCopy( args ); argsCopy += " -wrapperfinal"; Process p; if ( !p.Spawn( "fbuild.exe", argsCopy.Get(), nullptr, nullptr, true ) ) // true = forward output to our tty { return FBUILD_FAILED_TO_SPAWN_WRAPPER_FINAL; } // don't wait for the final process (the main process will do that) p.Detach(); return FBUILD_OK; } //------------------------------------------------------------------------------