diff --git a/.ecode/project_build.json b/.ecode/project_build.json index ba696adfc..071c66627 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -39,4 +39,4 @@ "build_dir": "${project_root}/make/${os}" } } -} +} \ No newline at end of file diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 5cfb3c938..4999c40dd 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -637,6 +637,8 @@ App::App( const size_t& jobs, const std::vector& args ) : ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) {} App::~App() { + if ( mProjectBuildManager ) + mProjectBuildManager.reset(); mThreadPool.reset(); if ( mFileWatcher ) { Lock l( mWatchesLock ); diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 8c53b716d..bc8677777 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -73,6 +73,78 @@ ProjectBuildSteps ProjectBuild::replaceVars( const ProjectBuildSteps& steps ) co return newSteps; } +json ProjectBuild::serialize( const ProjectBuild::Map& builds ) { + json j; + + for ( const auto& buildCfg : builds ) { + const auto& curBuild = buildCfg.second; + auto& bj = j[buildCfg.first]; + + bj["build"] = json::array(); + auto& jbuild = bj["build"]; + for ( const auto& build : curBuild.buildSteps() ) { + json step; + step["working_dir"] = build.workingDir; + step["args"] = build.args; + step["command"] = build.cmd; + if ( !build.enabled ) + step["enabled"] = build.enabled; + jbuild.push_back( step ); + } + + bj["clean"] = json::array(); + auto& jclean = bj["clean"]; + for ( const auto& build : curBuild.cleanSteps() ) { + json step; + step["working_dir"] = build.workingDir; + step["args"] = build.args; + step["command"] = build.cmd; + if ( !build.enabled ) + step["enabled"] = build.enabled; + jclean.push_back( step ); + } + + bj["build_types"] = curBuild.buildTypes(); + bj["config"]["clear_sys_env"] = curBuild.getConfig().clearSysEnv; + bj["os"] = curBuild.os(); + + if ( !curBuild.vars().empty() ) { + auto& var = bj["var"]; + for ( const auto& v : curBuild.vars() ) + var[v.first] = v.second; + } + + if ( !curBuild.envs().empty() ) { + auto& env = bj["env"]; + for ( const auto& e : curBuild.envs() ) + env[e.first] = e.second; + } + + auto& op = bj["output_parser"]; + auto& opc = op["config"]; + + opc["relative_file_paths"] = curBuild.getOutputParser().useRelativeFilePaths(); + if ( !curBuild.getOutputParser().getPreset().empty() ) + opc["preset"] = curBuild.getOutputParser().getPreset(); + + for ( const auto& opct : curBuild.getOutputParser().getConfig() ) { + std::string type( ProjectBuildOutputParserConfig::typeToString( opct.type ) ); + if ( !op.contains( type ) ) + op[type] = json::array(); + json nopp; + auto& po = nopp["pattern_order"]; + nopp["pattern"] = opct.pattern; + po["col"] = opct.patternOrder.col; + po["line"] = opct.patternOrder.line; + po["file"] = opct.patternOrder.file; + po["message"] = opct.patternOrder.message; + op[type].push_back( nopp ); + } + } + + return j; +} + bool ProjectBuild::isOSSupported( const std::string& os ) const { return mOS.empty() || std::any_of( mOS.begin(), mOS.end(), [&]( const auto& oos ) { return oos == os || oos == "any"; @@ -96,12 +168,27 @@ ProjectBuildManager::ProjectBuildManager( const std::string& projectRoot, } } +void ProjectBuildManager::addNewBuild() { + std::string name = mNewBuild.getName(); + ProjectBuild build = mNewBuild; + mBuilds.insert( + std::make_pair( std::move( name ), std::move( build ) ) ); +} + ProjectBuildManager::~ProjectBuildManager() { if ( mUISceneNode && !SceneManager::instance()->isShuttingDown() && mSidePanel && mTab ) { mSidePanel->removeTab( mTab ); } + if ( mUISceneNode && mUISceneNode->getRoot()->querySelector( "#build_settings_new_name" ) ) + addNewBuild(); + + for ( const auto& cbs : mCbs ) + for ( const auto& cb : cbs.second ) + cbs.first->removeEventListener( cb ); + mShuttingDown = true; mCancelBuild = true; + save(); while ( mLoading ) Sys::sleep( Milliseconds( 0.1f ) ); while ( mBuilding ) @@ -242,33 +329,10 @@ static ProjectOutputParserTypes outputParserType( const std::string& typeStr ) { return ProjectOutputParserTypes::Notice; } -bool ProjectBuildManager::load() { - ScopedOp scopedOp( [this]() { mLoading = true; }, - [this]() { - mLoading = false; - if ( mSidePanel ) - mSidePanel->runOnMainThread( [this]() { buildSidePanelTab(); } ); - } ); - - mProjectFile = mProjectRoot + ".ecode/project_build.json"; - if ( !FileSystem::fileExists( mProjectFile ) ) - return false; - std::string data; - if ( !FileSystem::fileGet( mProjectFile, data ) ) - return false; - json j; - - try { - j = json::parse( data, nullptr, true, true ); - } catch ( const json::exception& e ) { - Log::error( "ProjectBuildManager::load - Error parsing project build config from " - "path %s, error: %s, config file content:\n%s", - mProjectFile.c_str(), e.what(), mProjectFile.c_str() ); - return false; - } - +ProjectBuild::Map ProjectBuild::deserialize( const json& j, const std::string& projectRoot ) { + ProjectBuild::Map prjBuild; for ( const auto& build : j.items() ) { - ProjectBuild b( build.key(), mProjectRoot ); + ProjectBuild b( build.key(), projectRoot ); const auto& buildObj = build.value(); if ( buildObj.contains( "os" ) && buildObj["os"].is_array() ) { @@ -379,13 +443,63 @@ bool ProjectBuildManager::load() { b.mOutputParser = outputParser; } - mBuilds.insert( { build.key(), std::move( b ) } ); + prjBuild.insert( { build.key(), std::move( b ) } ); } + return prjBuild; +} + +bool ProjectBuildManager::load() { + ScopedOp scopedOp( [this]() { mLoading = true; }, + [this]() { + mLoading = false; + if ( mSidePanel ) + mSidePanel->runOnMainThread( [this]() { buildSidePanelTab(); } ); + } ); + + mProjectFile = mProjectRoot + ".ecode/project_build.json"; + if ( !FileSystem::fileExists( mProjectFile ) ) + return false; + std::string data; + if ( !FileSystem::fileGet( mProjectFile, data ) ) + return false; + json j; + + try { + j = json::parse( data, nullptr, true, true ); + } catch ( const json::exception& e ) { + Log::error( "ProjectBuildManager::load - Error parsing project build config from " + "path %s, error: %s, config file content:\n%s", + mProjectFile.c_str(), e.what(), mProjectFile.c_str() ); + return false; + } + + mBuilds = ProjectBuild::deserialize( j, mProjectRoot ); mLoaded = true; + return true; } +bool ProjectBuildManager::save() { + if ( !mLoaded ) + return false; + ScopedOp scopedOp( [this]() { mLoading = true; }, [this]() { mLoading = false; } ); + json j = ProjectBuild::serialize( mBuilds ); + std::string data( j.dump( 2 ) ); + if ( !FileSystem::fileWrite( mProjectFile, data ) ) + return false; + return true; +} + +bool ProjectBuildManager::saveAsync() { + if ( mThreadPool ) { + mThreadPool->run( [this]() { save(); } ); + return true; + } else { + return save(); + } +} + ProjectBuildOutputParser ProjectBuildManager::getOutputParser( const std::string& buildName ) { auto buildIt = mBuilds.find( buildName ); if ( buildIt != mBuilds.end() ) @@ -687,9 +801,28 @@ void ProjectBuildManager::updateSidePanelTab() { return; } auto ret = - mApp->getSplitter()->createWidget( UIBuildSettings::New( mNewBuild, mConfig ), + mApp->getSplitter()->createWidget( UIBuildSettings::New( mNewBuild, mConfig, true ), mApp->i18n( "build_settings", "Build Settings" ) ); - ret.second->asType()->setTab( ret.first ); + auto bs = ret.second->asType(); + bs->setTab( ret.first ); + mCbs[bs].insert( bs->on( Event::OnConfirm, [this, bs]( const Event* event ) { + event->getNode()->removeEventListener( event->getCallbackId() ); + mCbs[bs].erase( event->getCallbackId() ); + std::string name = mNewBuild.getName(); + ProjectBuild build = mNewBuild; + mBuilds.insert( std::make_pair( std::move( name ), + std::move( build ) ) ); + saveAsync(); + updateSidePanelTab(); + } ) ); + mCbs[bs].insert( bs->on( Event::OnClose, [this, bs]( auto ) { mCbs.erase( bs ); } ) ); + bs->on( Event::OnClear, [this]( const Event* event ) { + if ( mBuilds.erase( event->asTextEvent()->getText() ) > 0 ) { + if ( mConfig.buildName == event->asTextEvent()->getText() ) + mConfig.buildName = mBuilds.empty() ? "" : mBuilds.begin()->first; + updateSidePanelTab(); + } + } ); ret.first->setIcon( mApp->findIcon( "hammer" ) ); } ); @@ -704,9 +837,24 @@ void ProjectBuildManager::updateSidePanelTab() { return; } auto ret = mApp->getSplitter()->createWidget( - UIBuildSettings::New( build->second, mConfig ), + UIBuildSettings::New( build->second, mConfig, false ), mApp->i18n( "build_settings", "Build Settings" ) ); - ret.second->asType()->setTab( ret.first ); + auto bs = ret.second->asType(); + bs->setTab( ret.first ); + mCbs[bs].insert( bs->on( Event::OnConfirm, [this, bs]( const Event* event ) { + event->getNode()->removeEventListener( event->getCallbackId() ); + mCbs[bs].erase( event->getCallbackId() ); + saveAsync(); + } ) ); + mCbs[bs].insert( + bs->on( Event::OnClose, [this, bs]( auto ) { mCbs.erase( bs ); } ) ); + bs->on( Event::OnClear, [this]( const Event* event ) { + if ( mBuilds.erase( event->asTextEvent()->getText() ) > 0 ) { + if ( mConfig.buildName == event->asTextEvent()->getText() ) + mConfig.buildName = mBuilds.empty() ? "" : mBuilds.begin()->first; + updateSidePanelTab(); + } + } ); ret.first->setIcon( mApp->findIcon( "hammer" ) ); } } diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index f3736d146..cd8a510a2 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -138,6 +138,7 @@ class ProjectBuildOutputParser { protected: friend class ProjectBuildManager; + friend class ProjectBuild; friend class UIBuildSettings; bool mRelativeFilePaths{ true }; @@ -148,6 +149,8 @@ class ProjectBuildOutputParser { class ProjectBuild { public: + using Map = std::unordered_map; + ProjectBuild( const std::string& name, const std::string& projectRoot ) : mName( name ), mProjectRoot( projectRoot ){}; @@ -157,16 +160,30 @@ class ProjectBuild { const std::string& getName() const { return mName; } - const std::set buildTypes() const { return mBuildTypes; } + const std::set& buildTypes() const { return mBuildTypes; } + + const std::set& os() const { return mOS; } const ProjectBuildOutputParser& getOutputParser() const { return mOutputParser; } + const ProjectBuildSteps& buildSteps() const { return mBuild; } + + const ProjectBuildSteps& cleanSteps() const { return mClean; } + + const ProjectBuildKeyVal& envs() const { return mEnvs; } + + const ProjectBuildKeyVal& vars() const { return mVars; } + bool hasBuild() const { return !mBuild.empty(); } bool hasClean() const { return !mClean.empty(); } ProjectBuildSteps replaceVars( const ProjectBuildSteps& steps ) const; + static json serialize( const ProjectBuild::Map& builds ); + + static ProjectBuild::Map deserialize( const json& j, const std::string& projectRoot ); + protected: friend class ProjectBuildManager; friend class UIBuildSettings; @@ -183,8 +200,6 @@ class ProjectBuild { ProjectBuildOutputParser mOutputParser; }; -using ProjectBuildMap = std::unordered_map; - struct ProjectBuildCommand : public ProjectBuildStep { ProjectBuildKeyVal envs; ProjectBuildConfig config; @@ -240,7 +255,7 @@ class ProjectBuildManager { ProjectBuildOutputParser getOutputParser( const std::string& buildName ); - const ProjectBuildMap& getBuilds() const { return mBuilds; } + const ProjectBuild::Map& getBuilds() const { return mBuilds; } const std::string& getProjectRoot() const { return mProjectRoot; } @@ -271,7 +286,7 @@ class ProjectBuildManager { protected: std::string mProjectRoot; std::string mProjectFile; - ProjectBuildMap mBuilds; + ProjectBuild::Map mBuilds; ProjectBuildConfiguration mConfig; std::shared_ptr mThreadPool; UITabWidget* mSidePanel{ nullptr }; @@ -285,6 +300,7 @@ class ProjectBuildManager { bool mBuilding{ false }; bool mShuttingDown{ false }; bool mCancelBuild{ false }; + std::unordered_map> mCbs; void runBuild( const std::string& buildName, const std::string& buildType, const ProjectBuildi18nFn& i18n, const ProjectBuildCommandsRes& res, @@ -293,11 +309,17 @@ class ProjectBuildManager { bool load(); + bool save(); + + bool saveAsync(); + void buildSidePanelTab(); void updateSidePanelTab(); void updateBuildType(); + + void addNewBuild(); }; } // namespace ecode diff --git a/src/tools/ecode/uibuildsettings.cpp b/src/tools/ecode/uibuildsettings.cpp index a2f3790b3..788082a7b 100644 --- a/src/tools/ecode/uibuildsettings.cpp +++ b/src/tools/ecode/uibuildsettings.cpp @@ -405,8 +405,9 @@ static const auto SETTINGS_PANEL_XML = R"xml( )xml"; -UIBuildSettings* UIBuildSettings::New( ProjectBuild& build, ProjectBuildConfiguration& config ) { - return eeNew( UIBuildSettings, ( build, config ) ); +UIBuildSettings* UIBuildSettings::New( ProjectBuild& build, ProjectBuildConfiguration& config, + bool isNew ) { + return eeNew( UIBuildSettings, ( build, config, isNew ) ); } UIBuildSettings::~UIBuildSettings() { @@ -414,10 +415,13 @@ UIBuildSettings::~UIBuildSettings() { for ( const auto& cb : cbs.second ) cbs.first->removeEventListener( cb ); } + if ( !mCanceled ) + sendCommonEvent( Event::OnConfirm ); } -UIBuildSettings::UIBuildSettings( ProjectBuild& build, ProjectBuildConfiguration& config ) : - mBuild( build ), mConfig( config ), mOldName( mBuild.getName() ) { +UIBuildSettings::UIBuildSettings( ProjectBuild& build, ProjectBuildConfiguration& config, + bool isNew ) : + mBuild( build ), mConfig( config ), mOldName( mBuild.getName() ), mIsNew( isNew ) { addClass( "build_settings" ); mUISceneNode->loadLayoutFromString( SETTINGS_PANEL_XML, this, String::hash( "build_settings" ) ); @@ -522,6 +526,23 @@ UIBuildSettings::UIBuildSettings( ProjectBuild& build, ProjectBuildConfiguration bindTable( "table_envs", "env", mBuild.mEnvs ); bindTable( "table_vars", "var", mBuild.mVars ); + find( "build_del" )->onClick( [this]( auto ) { + UIMessageBox* msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + i18n( "confirm_build_delete", + "Are you sure you want to delete the build configuration?" ) ); + msgBox->setTitle( i18n( "build_settings", "Build Settings" ) ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + msgBox->showWhenReady(); + msgBox->addEventListener( Event::OnConfirm, [this, msgBox]( const Event* ) { + mCanceled = true; + sendTextEvent( Event::OnClear, mBuild.getName() ); + msgBox->closeWindow(); + if ( mTab ) + mTab->removeTab(); + } ); + } ); + find( "build_type_add" )->onClick( [this, buildTypeDropDown, panelBuildTypeDDL]( auto ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, i18n( "build_type_name", "Build Type Name:" ) ); diff --git a/src/tools/ecode/uibuildsettings.hpp b/src/tools/ecode/uibuildsettings.hpp index 0afb5c543..69801ec9a 100644 --- a/src/tools/ecode/uibuildsettings.hpp +++ b/src/tools/ecode/uibuildsettings.hpp @@ -11,7 +11,8 @@ namespace ecode { class UIBuildSettings : public UIRelativeLayout { public: - static UIBuildSettings* New( ProjectBuild& build, ProjectBuildConfiguration& config ); + static UIBuildSettings* New( ProjectBuild& build, ProjectBuildConfiguration& config, + bool isNew ); virtual ~UIBuildSettings(); @@ -29,8 +30,10 @@ class UIBuildSettings : public UIRelativeLayout { String mOldName; std::unordered_map> mCbs; ProjectBuildOutputParserConfig mTmpOpCfg; + bool mIsNew{ false }; + bool mCanceled{ false }; - explicit UIBuildSettings( ProjectBuild& build, ProjectBuildConfiguration& config ); + explicit UIBuildSettings( ProjectBuild& build, ProjectBuildConfiguration& config, bool isNew ); void moveStepUp( size_t stepNum, bool isClea );