diff --git a/include/eepp/system/process.hpp b/include/eepp/system/process.hpp index 76467710f..570a6f6f5 100644 --- a/include/eepp/system/process.hpp +++ b/include/eepp/system/process.hpp @@ -63,6 +63,16 @@ class EE_API Process { const std::unordered_map& environment = {}, const std::string& workingDirectory = "" ); + /** @brief Create a process. + ** @param command Command line to execute for this process. + ** @param args Command line arguments to execute for this process. + ** @param options A bit field of Options's to pass. + ** @return On success true is returned. */ + bool create( const std::string& command, const std::string& args, + const Uint32& options = getDefaultOptions(), + const std::unordered_map& environment = {}, + const std::string& workingDirectory = "" ); + /** @brief Starts a new thread to receive all stdout and stderr data */ void startAsyncRead( ReadFn readStdOut = nullptr, ReadFn readStdErr = nullptr ); diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index 6b67c131e..7cf5783b4 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -80,6 +80,41 @@ bool Process::create( const std::string& command, const Uint32& options, return ret; } +bool Process::create( const std::string& command, const std::string& args, const Uint32& options, + const std::unordered_map& environment, + const std::string& workingDirectory ) { + if ( mProcess ) + return false; + std::vector cmdArr = String::split( args, " ", "", "\"", true ); + std::vector strings; + strings.push_back( command.c_str() ); + for ( size_t i = 0; i < cmdArr.size(); ++i ) + strings.push_back( cmdArr[i].c_str() ); + strings.push_back( NULL ); + mProcess = eeMalloc( sizeof( subprocess_s ) ); + memset( mProcess, 0, sizeof( subprocess_s ) ); + if ( !environment.empty() ) { + std::vector envArr; + std::vector envStrings; + for ( const auto& pair : environment ) { + envArr.push_back( String::format( "%s=%s", pair.first.c_str(), pair.second.c_str() ) ); + envStrings.push_back( envArr[envArr.size() - 1].c_str() ); + } + envStrings.push_back( NULL ); + + auto ret = 0 == subprocess_create_ex( strings.data(), options, envStrings.data(), + !workingDirectory.empty() ? workingDirectory.c_str() + : nullptr, + PROCESS_PTR ); + return ret; + } + auto ret = + 0 == subprocess_create_ex( strings.data(), options, nullptr, + !workingDirectory.empty() ? workingDirectory.c_str() : nullptr, + PROCESS_PTR ); + return ret; +} + size_t Process::readAllStdOut( std::string& buffer ) { size_t bytesRead = 0; size_t totalBytesRead = 0; diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 400e8f3cc..52bf8e9fd 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -1,8 +1,10 @@ #include "projectbuild.hpp" #include "scopedop.hpp" #include +#include #include #include +#include #include using json = nlohmann::json; @@ -59,6 +61,26 @@ ProjectBuildManager::~ProjectBuildManager() { mShuttingDown = true; while ( mLoading ) Sys::sleep( Milliseconds( 0.1f ) ); + while ( mBuilding ) + Sys::sleep( Milliseconds( 0.1f ) ); +} + +ProjectBuildCommandsRes +ProjectBuildManager::run( const std::string& buildName, + std::function i18n, + const std::string& buildType, const ProjectBuildProgressFn& progressFn, + const ProjectBuildDoneFn& doneFn ) { + ProjectBuildCommandsRes res = generateBuildCommands( buildName, i18n, buildType ); + if ( !res.isValid() ) + return res; + if ( !mThreadPool ) { + res.errorMsg = i18n( "no_threads", "Threaded ecode required to compile builds." ); + return res; + } + + mThreadPool->run( [this, res, progressFn, doneFn]() { runBuild( res, progressFn, doneFn ); } ); + + return res; }; static bool isValidType( const std::string& typeStr ) { @@ -76,7 +98,7 @@ static ProjectOutputParserTypes outputParserType( const std::string& typeStr ) { } bool ProjectBuildManager::load() { - ScopedOp op( [this]() { mLoading = true; }, [this]() { mLoading = false; } ); + ScopedOp scopedOp( [this]() { mLoading = true; }, [this]() { mLoading = false; } ); mProjectFile = mProjectRoot + ".ecode/project_build.json"; if ( !FileSystem::fileExists( mProjectFile ) ) @@ -155,17 +177,17 @@ bool ProjectBuildManager::load() { ProjectBuildOutputParser outputParser; - for ( const auto& op : op.items() ) { - if ( op.key() == "config" ) { - const auto& config = op.value(); + for ( const auto& item : op.items() ) { + if ( item.key() == "config" ) { + const auto& config = item.value(); outputParser.mRelativeFilePaths = config.value( "output_parser", true ); } else { - auto typeStr = String::toLower( op.key() ); + auto typeStr = String::toLower( item.key() ); if ( !isValidType( typeStr ) ) continue; - const auto& ptrnCfgs = op.value(); + const auto& ptrnCfgs = item.value(); if ( ptrnCfgs.is_array() ) { for ( const auto& ptrnCfg : ptrnCfgs ) { ProjectBuildOutputParserConfig opc; @@ -232,11 +254,55 @@ ProjectBuildCommandsRes ProjectBuildManager::generateBuildCommands( replaceVar( buildCmd, VAR_NPROC, nproc ); if ( !buildType.empty() ) replaceVar( buildCmd, VAR_BUILD_TYPE, buildType ); - + buildCmd.config = build.mConfig; res.cmds.emplace_back( std::move( buildCmd ) ); } return res; } +void ProjectBuildManager::runBuild( const ProjectBuildCommandsRes& res, + const ProjectBuildProgressFn& progressFn, + const ProjectBuildDoneFn& doneFn ) { + ScopedOp scopedOp( [this]() { mBuilding = true; }, [this]() { mBuilding = false; } ); + Clock clock; + for ( const auto& cmd : res.cmds ) { + Process process; + auto options = Process::SearchUserPath | Process::NoWindow | Process::CombinedStdoutStderr; + if ( !cmd.config.clearSysEnv ) + options |= Process::InheritEnvironment; + if ( process.create( cmd.cmd, cmd.args, options, cmd.envs, cmd.workingDir ) ) { + std::string buffer( 1024, '\0' ); + unsigned bytesRead = 0; + int returnCode; + do { + bytesRead = process.readStdOut( buffer ); + std::string data( buffer.substr( 0, bytesRead ) ); + if ( progressFn ) + progressFn( 50, std::move( data ) ); + } while ( bytesRead != 0 && process.isAlive() && !mShuttingDown ); + + if ( mShuttingDown ) { + process.kill(); + doneFn( EXIT_FAILURE ); + return; + } + + if ( progressFn ) + progressFn( 90, {} ); + + process.join( &returnCode ); + process.destroy(); + + if ( doneFn && returnCode != EXIT_SUCCESS ) { + progressFn( 100, {} ); + doneFn( returnCode ); + return; + } + } + } + + doneFn( EXIT_SUCCESS ); +} + } // namespace ecode diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index 99f62e01e..00f874428 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -137,6 +137,7 @@ using ProjectBuildMap = std::unordered_map; struct ProjectBuildCommand : public ProjectBuildStep { ProjectBuildKeyVal envs; + ProjectBuildConfig config; ProjectBuildCommand( const ProjectBuildStep& step, const ProjectBuildKeyVal& envs ) : ProjectBuildStep( step ), envs( envs ) {} @@ -157,12 +158,21 @@ struct ProjectBuildCommandsRes { bool isValid() { return errorMsg.empty(); } }; +using ProjectBuildProgressFn = std::function; +using ProjectBuildDoneFn = std::function; + class ProjectBuildManager { public: ProjectBuildManager( const std::string& projectRoot, std::shared_ptr pool ); ~ProjectBuildManager(); + ProjectBuildCommandsRes + run( const std::string& buildName, + std::function i18n, + const std::string& buildType = "", const ProjectBuildProgressFn& progressFn = {}, + const ProjectBuildDoneFn& doneFn = {} ); + ProjectBuildCommandsRes generateBuildCommands( const std::string& buildName, std::function i18n, @@ -178,6 +188,8 @@ class ProjectBuildManager { bool loading() const { return mLoading; } + bool isBuilding() const { return mBuilding; } + protected: std::string mProjectRoot; std::string mProjectFile; @@ -185,8 +197,13 @@ class ProjectBuildManager { std::shared_ptr mThreadPool; bool mLoaded{ false }; bool mLoading{ false }; + bool mBuilding{ false }; bool mShuttingDown{ false }; + void runBuild( const ProjectBuildCommandsRes& res, + const ProjectBuildProgressFn& progressFn = {}, + const ProjectBuildDoneFn& doneFn = {} ); + bool load(); }; diff --git a/src/tools/ecode/statusterminalcontroller.cpp b/src/tools/ecode/statusterminalcontroller.cpp index 2678270be..54582ca88 100644 --- a/src/tools/ecode/statusterminalcontroller.cpp +++ b/src/tools/ecode/statusterminalcontroller.cpp @@ -41,6 +41,8 @@ void StatusTerminalController::show() { if ( nullptr == mUITerminal ) { mMainSplitter->updateLayout(); mUITerminal = createTerminal(); + if ( !mUITerminal ) + return; mUITerminal->setId( "terminal" ); mUITerminal->setVisible( false ); }