mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-01 19:16:30 +03:00
Flag the document as modified until saved when changing it's line ending (SpartanJ/ecode/315). Avoid processing document symbols on huge files. Avoid matching brackets on huge files.
1329 lines
42 KiB
C++
1329 lines
42 KiB
C++
#include "projectbuild.hpp"
|
|
#include "ecode.hpp"
|
|
#include "statusbuildoutputcontroller.hpp"
|
|
#include "uibuildsettings.hpp"
|
|
#include <eepp/core/string.hpp>
|
|
#include <eepp/scene/scenemanager.hpp>
|
|
#include <eepp/system/clock.hpp>
|
|
#include <eepp/system/filesystem.hpp>
|
|
#include <eepp/system/log.hpp>
|
|
#include <eepp/system/process.hpp>
|
|
#include <eepp/system/scopedop.hpp>
|
|
#include <eepp/ui/uidropdownlist.hpp>
|
|
#include <eepp/ui/uiiconthememanager.hpp>
|
|
#include <eepp/ui/uiscenenode.hpp>
|
|
#include <nlohmann/json.hpp>
|
|
using json = nlohmann::json;
|
|
|
|
using namespace EE::Scene;
|
|
|
|
namespace ecode {
|
|
|
|
static constexpr auto SidePanelLoadUniqueId = String::hash( "ProjectBuildManager::load::async" );
|
|
|
|
static const char* VAR_PROJECT_ROOT = "${project_root}";
|
|
static const char* VAR_BUILD_TYPE = "${build_type}";
|
|
static const char* VAR_OS = "${os}";
|
|
static const char* VAR_NPROC = "${nproc}";
|
|
static const char* VAR_CURRENT_DOC = "${current_doc}";
|
|
static const char* VAR_CURRENT_DOC_NAME = "${current_doc_name}";
|
|
|
|
static void replaceVar( ProjectBuildStep& s, const std::string& var, const std::string& val ) {
|
|
static std::string slashDup = FileSystem::getOSSlash() + FileSystem::getOSSlash();
|
|
String::replaceAll( s.workingDir, var, val );
|
|
String::replaceAll( s.cmd, var, val );
|
|
String::replaceAll( s.args, var, val );
|
|
String::replaceAll( s.workingDir, slashDup, FileSystem::getOSSlash() );
|
|
FileSystem::dirAddSlashAtEnd( s.workingDir );
|
|
}
|
|
|
|
ProjectBuildStep ProjectBuild::replaceVars( const ProjectBuildStep& step ) const {
|
|
ProjectBuildStep s( step );
|
|
replaceVar( s, VAR_PROJECT_ROOT, mProjectRoot );
|
|
for ( auto& var : mVars ) {
|
|
std::string varKey( "${" + var.first + "}" );
|
|
std::string varVal( var.second );
|
|
String::replaceAll( varVal, VAR_PROJECT_ROOT, mProjectRoot );
|
|
replaceVar( s, varKey, varVal );
|
|
}
|
|
return s;
|
|
}
|
|
|
|
ProjectBuildSteps ProjectBuild::replaceVars( const ProjectBuildSteps& steps ) const {
|
|
ProjectBuildSteps newSteps( steps );
|
|
for ( auto& s : newSteps ) {
|
|
replaceVar( s, VAR_PROJECT_ROOT, mProjectRoot );
|
|
for ( auto& var : mVars ) {
|
|
std::string varKey( "${" + var.first + "}" );
|
|
std::string varVal( var.second );
|
|
String::replaceAll( varVal, VAR_PROJECT_ROOT, mProjectRoot );
|
|
replaceVar( s, varKey, varVal );
|
|
}
|
|
}
|
|
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 );
|
|
}
|
|
|
|
if ( curBuild.hasRun() ) {
|
|
bj["run"] = json::array();
|
|
auto& jrun = bj["run"];
|
|
for ( auto& run : curBuild.mRun ) {
|
|
json step;
|
|
step["name"] = run.name;
|
|
step["working_dir"] = run.workingDir;
|
|
step["args"] = run.args;
|
|
step["command"] = run.cmd;
|
|
if ( !run.enabled )
|
|
step["enabled"] = run.enabled;
|
|
if ( run.runInTerminal )
|
|
step["run_in_terminal"] = run.runInTerminal;
|
|
jrun.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(), [&os]( const auto& oos ) {
|
|
return oos == os || oos == "any";
|
|
} );
|
|
}
|
|
|
|
ProjectBuildManager::ProjectBuildManager( const std::string& projectRoot,
|
|
std::shared_ptr<ThreadPool> pool, UITabWidget* sidePanel,
|
|
App* app ) :
|
|
mProjectRoot( projectRoot ),
|
|
mThreadPool( pool ),
|
|
mSidePanel( sidePanel ),
|
|
mApp( app ),
|
|
mNewBuild( mApp->i18n( "new_name", "New Name" ), mProjectRoot ) {
|
|
FileSystem::dirAddSlashAtEnd( mProjectRoot );
|
|
|
|
if ( mThreadPool ) {
|
|
mThreadPool->run( [this]() { load(); } );
|
|
} else {
|
|
load();
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::addNewBuild() {
|
|
std::string name = mNewBuild.getName();
|
|
ProjectBuild newBuild = mNewBuild;
|
|
bool found;
|
|
do {
|
|
found = false;
|
|
for ( const auto& b : mBuilds ) {
|
|
if ( b.first == name ) {
|
|
name += " (" + mApp->i18n( "copy", "Copy" ) + ")";
|
|
found = true;
|
|
}
|
|
}
|
|
} while ( found );
|
|
newBuild.mName = name;
|
|
mBuilds.insert(
|
|
std::make_pair<std::string, ProjectBuild>( std::move( name ), std::move( newBuild ) ) );
|
|
}
|
|
|
|
bool ProjectBuildManager::cloneBuild( const std::string& build, std::string newBuildName ) {
|
|
auto oldBuild = mBuilds.find( build );
|
|
if ( oldBuild == mBuilds.end() )
|
|
return false;
|
|
ProjectBuild newBuild = oldBuild->second;
|
|
newBuild.mName = newBuildName;
|
|
mBuilds.insert( std::make_pair<std::string, ProjectBuild>( std::move( newBuildName ),
|
|
std::move( newBuild ) ) );
|
|
return true;
|
|
}
|
|
|
|
void ProjectBuildManager::addBuild( UIWidget* buildTab ) {
|
|
mNewBuild = ProjectBuild( mApp->i18n( "new_name", "New Name" ), mProjectRoot );
|
|
std::string hashName = String::toString( String::hash( "new_name" ) );
|
|
UIWidget* widget = nullptr;
|
|
if ( ( widget = buildTab->getUISceneNode()->getRoot()->querySelector( "#build_settings_" +
|
|
hashName ) ) ) {
|
|
widget->asType<UITab>()->select();
|
|
return;
|
|
}
|
|
auto ret = mApp->getSplitter()->createWidget(
|
|
UIBuildSettings::New( mNewBuild, mConfig, true, nullptr ),
|
|
mApp->i18n( "build_settings", "Build Settings" ) );
|
|
auto bs = ret.second->asType<UIBuildSettings>();
|
|
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() );
|
|
addNewBuild();
|
|
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" ) );
|
|
}
|
|
|
|
void ProjectBuildManager::editBuild( std::string buildName, UIWidget* buildTab ) {
|
|
if ( buildName.empty() )
|
|
return;
|
|
|
|
auto build = mBuilds.find( buildName );
|
|
if ( build == mBuilds.end() )
|
|
return;
|
|
|
|
std::string hashName = String::toString( String::hash( buildName ) );
|
|
UIWidget* widget = nullptr;
|
|
if ( ( widget = buildTab->getUISceneNode()->getRoot()->querySelector( "#build_settings_" +
|
|
hashName ) ) ) {
|
|
widget->asType<UITab>()->select();
|
|
return;
|
|
}
|
|
auto ret = mApp->getSplitter()->createWidget(
|
|
UIBuildSettings::New( build->second, mConfig, false,
|
|
[this]( const std::string& oldName, const std::string& newName ) {
|
|
auto buildIt = mBuilds.find( oldName );
|
|
if ( buildIt != mBuilds.end() ) {
|
|
auto build = mBuilds.extract( buildIt );
|
|
build.key() = newName;
|
|
mBuilds.insert( std::move( build ) );
|
|
}
|
|
} ),
|
|
mApp->i18n( "build_settings", "Build Settings" ) );
|
|
auto bs = ret.second->asType<UIBuildSettings>();
|
|
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();
|
|
}
|
|
} );
|
|
bs->on( Event::OnCopy, [this, buildName, buildTab]( const Event* event ) {
|
|
std::string clonedName( event->asTextEvent()->getText() );
|
|
if ( mBuilds.find( clonedName ) != mBuilds.end() ) {
|
|
UIMessageBox::New(
|
|
UIMessageBox::OK,
|
|
mApp->i18n( "cloned_name_must_be_different",
|
|
"Cloned name must be different from any existing build name." ) )
|
|
->show();
|
|
} else {
|
|
cloneBuild( buildName, clonedName );
|
|
updateSidePanelTab();
|
|
editBuild( clonedName, buildTab );
|
|
}
|
|
} );
|
|
ret.first->setIcon( mApp->findIcon( "hammer" ) );
|
|
}
|
|
|
|
void ProjectBuildManager::editCurrentBuild() {
|
|
UIWidget* buildTab = mTab->getOwnedWidget()->find<UIWidget>( "build_tab_view" );
|
|
if ( buildTab == nullptr )
|
|
return;
|
|
if ( !mConfig.buildName.empty() && !mBuilds.empty() )
|
|
editBuild( mConfig.buildName, buildTab );
|
|
else
|
|
mTab->setTabSelected();
|
|
}
|
|
|
|
void ProjectBuildManager::selectTab() {
|
|
mTab->setTabSelected();
|
|
}
|
|
|
|
ProjectBuildManager::~ProjectBuildManager() {
|
|
mSidePanel->removeActionsByTag( SidePanelLoadUniqueId );
|
|
|
|
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;
|
|
mCancelRun = true;
|
|
save();
|
|
while ( mLoading )
|
|
Sys::sleep( Milliseconds( 0.1f ) );
|
|
while ( mBuilding )
|
|
Sys::sleep( Milliseconds( 0.1f ) );
|
|
}
|
|
|
|
void ProjectBuildManager::replaceDynamicVars( ProjectBuildCommand& cmd ) {
|
|
std::string currentOS = String::toLower( Sys::getPlatform() );
|
|
std::string nproc = String::format( "%d", Sys::getCPUCount() );
|
|
std::string curDoc = getCurrentDocument();
|
|
std::string curDocName =
|
|
FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( curDoc ) );
|
|
replaceVar( cmd, VAR_OS, currentOS );
|
|
replaceVar( cmd, VAR_NPROC, nproc );
|
|
replaceVar( cmd, VAR_CURRENT_DOC, curDoc );
|
|
replaceVar( cmd, VAR_CURRENT_DOC_NAME, curDocName );
|
|
if ( cmd.workingDir.empty() )
|
|
cmd.workingDir = mProjectRoot;
|
|
}
|
|
|
|
ProjectBuildCommandsRes ProjectBuildManager::generateBuildCommands( const std::string& buildName,
|
|
const ProjectBuildi18nFn& i18n,
|
|
const std::string& buildType,
|
|
bool isClean ) {
|
|
const auto& buildIt = mBuilds.find( buildName );
|
|
|
|
if ( buildIt == mBuilds.end() )
|
|
return { i18n( "build_name_not_found", "Build name not found!" ) };
|
|
|
|
const auto& build = buildIt->second;
|
|
|
|
if ( !build.mBuildTypes.empty() && buildType.empty() )
|
|
return { i18n( "build_type_required", "Build type must be set!" ) };
|
|
|
|
std::string currentOS = String::toLower( Sys::getPlatform() );
|
|
|
|
if ( !build.isOSSupported( currentOS ) )
|
|
return {
|
|
i18n( "build_os_not_supported", "Operating System not supported for this build!" ) };
|
|
|
|
auto finalBuild( build.replaceVars( isClean ? build.mClean : build.mBuild ) );
|
|
|
|
ProjectBuildCommandsRes res;
|
|
for ( const auto& step : finalBuild ) {
|
|
ProjectBuildCommand buildCmd( step );
|
|
replaceDynamicVars( buildCmd );
|
|
if ( !buildType.empty() )
|
|
replaceVar( buildCmd, VAR_BUILD_TYPE, buildType );
|
|
buildCmd.config = build.mConfig;
|
|
res.cmds.emplace_back( std::move( buildCmd ) );
|
|
}
|
|
|
|
res.envs = build.mEnvs;
|
|
|
|
return res;
|
|
}
|
|
|
|
ProjectBuildCommandsRes
|
|
ProjectBuildManager::build( const std::string& buildName, const ProjectBuildi18nFn& i18n,
|
|
const std::string& buildType, const ProjectBuildProgressFn& progressFn,
|
|
const ProjectBuildDoneFn& doneFn, bool isClean ) {
|
|
ProjectBuildCommandsRes res = generateBuildCommands( buildName, i18n, buildType, isClean );
|
|
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, i18n, buildName, buildType]() {
|
|
runBuild( buildName, buildType, i18n, res, progressFn, doneFn );
|
|
} );
|
|
|
|
return res;
|
|
}
|
|
|
|
ProjectBuildCommandsRes ProjectBuildManager::run( const ProjectBuildCommand& runData,
|
|
const ProjectBuildi18nFn& i18n,
|
|
const ProjectBuildProgressFn& progressFn,
|
|
const ProjectBuildDoneFn& doneFn ) {
|
|
ProjectBuildCommandsRes res;
|
|
|
|
if ( !mThreadPool ) {
|
|
res.errorMsg = i18n( "no_threads", "Threaded ecode required to run builds." );
|
|
return res;
|
|
}
|
|
|
|
mThreadPool->run( [this, res, progressFn, doneFn, i18n, runData]() {
|
|
runApp( runData, i18n, res, progressFn, doneFn );
|
|
} );
|
|
|
|
return res;
|
|
}
|
|
|
|
static bool isValidType( const std::string& typeStr ) {
|
|
return "error" == typeStr || "warning" == typeStr || "notice" == typeStr;
|
|
}
|
|
|
|
static ProjectOutputParserTypes outputParserType( const std::string& typeStr ) {
|
|
if ( "error" == typeStr )
|
|
return ProjectOutputParserTypes::Error;
|
|
if ( "warning" == typeStr )
|
|
return ProjectOutputParserTypes::Warning;
|
|
if ( "notice" == typeStr )
|
|
return ProjectOutputParserTypes::Notice;
|
|
return ProjectOutputParserTypes::Notice;
|
|
}
|
|
|
|
ProjectBuild::Map ProjectBuild::deserialize( const json& j, const std::string& projectRoot ) {
|
|
ProjectBuild::Map prjBuild;
|
|
for ( const auto& build : j.items() ) {
|
|
ProjectBuild b( build.key(), projectRoot );
|
|
const auto& buildObj = build.value();
|
|
|
|
if ( buildObj.contains( "os" ) && buildObj["os"].is_array() ) {
|
|
const auto& oss = buildObj["os"];
|
|
for ( const auto& os : oss )
|
|
b.mOS.emplace( os );
|
|
}
|
|
|
|
if ( buildObj.contains( "build_types" ) && buildObj["build_types"].is_array() ) {
|
|
const auto& bts = buildObj["build_types"];
|
|
for ( const auto& bt : bts )
|
|
b.mBuildTypes.emplace( bt );
|
|
}
|
|
|
|
if ( buildObj.contains( "config" ) && buildObj["config"].is_object() ) {
|
|
b.mConfig.clearSysEnv = buildObj.value( "clear_sys_env", false );
|
|
}
|
|
|
|
if ( buildObj.contains( "var" ) && buildObj["var"].is_object() ) {
|
|
const auto& vars = buildObj["var"];
|
|
for ( const auto& var : vars.items() )
|
|
b.mVars.push_back( std::make_pair( var.key(), var.value() ) );
|
|
}
|
|
|
|
if ( buildObj.contains( "env" ) && buildObj["env"].is_object() ) {
|
|
const auto& vars = buildObj["env"];
|
|
for ( const auto& var : vars.items() )
|
|
b.mEnvs.push_back( std::make_pair( var.key(), var.value() ) );
|
|
}
|
|
|
|
if ( buildObj.contains( "build" ) && buildObj["build"].is_array() ) {
|
|
const auto& buildArray = buildObj["build"];
|
|
for ( const auto& step : buildArray ) {
|
|
ProjectBuildStep bstep;
|
|
bstep.cmd = step.value( "command", "" );
|
|
bstep.args = step.value( "args", "" );
|
|
bstep.workingDir = step.value( "working_dir", "" );
|
|
bstep.enabled = step.value( "enabled", true );
|
|
b.mBuild.emplace_back( std::move( bstep ) );
|
|
}
|
|
}
|
|
|
|
if ( buildObj.contains( "clean" ) && buildObj["clean"].is_array() ) {
|
|
const auto& cleanArray = buildObj["clean"];
|
|
for ( const auto& step : cleanArray ) {
|
|
ProjectBuildStep cstep;
|
|
cstep.cmd = step.value( "command", "" );
|
|
cstep.args = step.value( "args", "" );
|
|
cstep.workingDir = step.value( "working_dir", "" );
|
|
cstep.enabled = step.value( "enabled", true );
|
|
b.mClean.emplace_back( std::move( cstep ) );
|
|
}
|
|
}
|
|
|
|
if ( buildObj.contains( "run" ) && buildObj["run"].is_array() ) {
|
|
const auto& runArray = buildObj["run"];
|
|
for ( const auto& step : runArray ) {
|
|
ProjectBuildStep rstep;
|
|
rstep.name = step.value( "name", "" );
|
|
rstep.cmd = step.value( "command", "" );
|
|
rstep.args = step.value( "args", "" );
|
|
rstep.workingDir = step.value( "working_dir", "" );
|
|
rstep.enabled = step.value( "enabled", true );
|
|
rstep.runInTerminal = step.value( "run_in_terminal", false );
|
|
b.mRun.emplace_back( std::move( rstep ) );
|
|
}
|
|
}
|
|
|
|
if ( buildObj.contains( "output_parser" ) && buildObj["output_parser"].is_object() ) {
|
|
const auto& op = buildObj["output_parser"];
|
|
|
|
ProjectBuildOutputParser outputParser;
|
|
|
|
if ( op.contains( "config" ) ) {
|
|
const auto& config = op["config"];
|
|
outputParser.mRelativeFilePaths = config.value( "relative_file_paths", true );
|
|
|
|
if ( config.contains( "preset" ) ) {
|
|
auto preset = config.value( "preset", "" );
|
|
if ( !preset.empty() ) {
|
|
outputParser.mPreset = preset;
|
|
auto presets = ProjectBuildOutputParser::getPresets();
|
|
if ( presets.find( preset ) != presets.end() ) {
|
|
outputParser.mPresetConfig = presets[preset].mConfig;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( const auto& item : op.items() ) {
|
|
if ( item.key() != "config" ) {
|
|
auto typeStr = String::toLower( item.key() );
|
|
|
|
if ( !isValidType( typeStr ) )
|
|
continue;
|
|
|
|
const auto& ptrnCfgs = item.value();
|
|
if ( ptrnCfgs.is_array() ) {
|
|
for ( const auto& ptrnCfg : ptrnCfgs ) {
|
|
ProjectBuildOutputParserConfig opc;
|
|
opc.type = outputParserType( typeStr );
|
|
opc.pattern = ptrnCfg.value( "pattern", "" );
|
|
|
|
if ( ptrnCfg.contains( "pattern_order" ) ) {
|
|
const auto& po = ptrnCfg["pattern_order"];
|
|
if ( po.contains( "line" ) && po["line"].is_number() )
|
|
opc.patternOrder.line = po["line"].get<int>();
|
|
if ( po.contains( "col" ) && po["col"].is_number() )
|
|
opc.patternOrder.col = po["col"].get<int>();
|
|
if ( po.contains( "message" ) && po["message"].is_number() )
|
|
opc.patternOrder.message = po["message"].get<int>();
|
|
if ( po.contains( "file" ) && po["file"].is_number() )
|
|
opc.patternOrder.file = po["file"].get<int>();
|
|
}
|
|
|
|
outputParser.mConfig.emplace_back( std::move( opc ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
b.mOutputParser = outputParser;
|
|
}
|
|
|
|
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(); },
|
|
Time::Zero, SidePanelLoadUniqueId );
|
|
} );
|
|
|
|
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 );
|
|
mLoadedWithBuilds = !mBuilds.empty();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ProjectBuildManager::save() {
|
|
ScopedOp scopedOp( [this]() { mLoading = true; }, [this]() { mLoading = false; } );
|
|
json j = ProjectBuild::serialize( mBuilds );
|
|
std::string data( j.empty() ? "{}" : j.dump( 2 ) );
|
|
FileInfo file( mProjectFile );
|
|
bool shouldSave = mLoadedWithBuilds || !mBuilds.empty();
|
|
|
|
if ( shouldSave && !file.exists() && !FileSystem::fileExists( file.getDirectoryPath() ) )
|
|
FileSystem::makeDir( file.getDirectoryPath() );
|
|
|
|
if ( shouldSave && !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() )
|
|
return buildIt->second.mOutputParser;
|
|
return {};
|
|
}
|
|
|
|
ProjectBuild* ProjectBuildManager::getBuild( const std::string& buildName ) {
|
|
auto buildIt = mBuilds.find( buildName );
|
|
if ( buildIt != mBuilds.end() )
|
|
return &buildIt->second;
|
|
return nullptr;
|
|
}
|
|
|
|
bool ProjectBuildManager::hasBuildCommands( const std::string& name ) {
|
|
auto buildIt = mBuilds.find( name );
|
|
if ( buildIt != mBuilds.end() )
|
|
return buildIt->second.hasBuild();
|
|
return false;
|
|
}
|
|
|
|
bool ProjectBuildManager::hasCleanCommands( const std::string& name ) {
|
|
auto buildIt = mBuilds.find( name );
|
|
if ( buildIt != mBuilds.end() )
|
|
return buildIt->second.hasClean();
|
|
return false;
|
|
}
|
|
|
|
void ProjectBuildManager::cancelBuild() {
|
|
mCancelBuild = true;
|
|
if ( mProcess ) {
|
|
mProcess->destroy();
|
|
mProcess->kill();
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::cancelRun() {
|
|
mCancelRun = true;
|
|
if ( mProcess ) {
|
|
mProcess->destroy();
|
|
mProcess->kill();
|
|
}
|
|
}
|
|
|
|
ProjectBuildConfiguration ProjectBuildManager::getConfig() const {
|
|
return mConfig;
|
|
}
|
|
|
|
void ProjectBuildManager::setConfig( const ProjectBuildConfiguration& config ) {
|
|
if ( mConfig.buildName != config.buildName ) {
|
|
mConfig.buildName = config.buildName;
|
|
updateSidePanelTab();
|
|
}
|
|
|
|
if ( mConfig.buildType != config.buildType ) {
|
|
mConfig.buildType = config.buildType;
|
|
updateBuildType();
|
|
}
|
|
|
|
if ( mConfig.runName != config.runName ) {
|
|
mConfig.runName = config.runName;
|
|
updateRunConfig();
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::buildCurrentConfig( StatusBuildOutputController* sboc,
|
|
std::function<void( int exitStatus )> doneFn ) {
|
|
if ( sboc && !isBuilding() && !getBuilds().empty() ) {
|
|
const ProjectBuild* build = getBuild( mConfig.buildName );
|
|
if ( build ) {
|
|
mApp->saveAll();
|
|
sboc->runBuild( build->getName(), mConfig.buildType,
|
|
getOutputParser( build->getName() ), false, doneFn );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::cleanCurrentConfig( StatusBuildOutputController* sboc ) {
|
|
if ( sboc && !isBuilding() && !getBuilds().empty() ) {
|
|
const ProjectBuild* build = getBuild( mConfig.buildName );
|
|
if ( build )
|
|
sboc->runBuild( build->getName(), mConfig.buildType,
|
|
getOutputParser( build->getName() ), true );
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::runCurrentConfig( StatusAppOutputController* saoc, bool build,
|
|
StatusBuildOutputController* sboc ) {
|
|
if ( build ) {
|
|
buildCurrentConfig( sboc, [this, saoc]( int exitStatus ) {
|
|
if ( EXIT_SUCCESS == exitStatus )
|
|
mApp->getUISceneNode()->runOnMainThread( [saoc, this] { runConfig( saoc ); } );
|
|
} );
|
|
} else {
|
|
runConfig( saoc );
|
|
}
|
|
}
|
|
|
|
bool ProjectBuildManager::hasBuildConfig() {
|
|
return !getBuilds().empty() && !mConfig.buildName.empty();
|
|
}
|
|
|
|
bool ProjectBuildManager::hasRunConfig() {
|
|
if ( hasBuildConfig() ) {
|
|
auto build = getBuild( mConfig.buildName );
|
|
return build != nullptr && build->hasRun();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ProjectBuildManager::runConfig( StatusAppOutputController* saoc ) {
|
|
if ( !isRunningApp() && !getBuilds().empty() ) {
|
|
BoolScopedOp op( mRunning, true );
|
|
const ProjectBuild* build = getBuild( mConfig.buildName );
|
|
|
|
if ( nullptr == build || !build->hasRun() )
|
|
return;
|
|
|
|
const ProjectBuildStep* run = nullptr;
|
|
for ( const auto& crun : build->mRun ) {
|
|
if ( crun.name == mConfig.runName || mConfig.runName.empty() ) {
|
|
run = &crun;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( nullptr == run ) {
|
|
Log::info( "No run configuration found to run with runName: %s", mConfig.runName );
|
|
return;
|
|
}
|
|
|
|
ProjectBuildCommand finalBuild( build->replaceVars( *run ) );
|
|
replaceDynamicVars( finalBuild );
|
|
String::trimInPlace( finalBuild.cmd );
|
|
if ( finalBuild.cmd.find_first_of( "\\/" ) == std::string::npos &&
|
|
Sys::which( finalBuild.cmd ).empty() ) {
|
|
FileSystem::dirAddSlashAtEnd( finalBuild.workingDir );
|
|
finalBuild.cmd = finalBuild.workingDir + finalBuild.cmd;
|
|
}
|
|
|
|
auto cmd = finalBuild.cmd + " " + finalBuild.args;
|
|
if ( finalBuild.runInTerminal ) {
|
|
UITerminal* term = mApp->getTerminalManager()->createTerminalInSplitter(
|
|
finalBuild.workingDir, false );
|
|
Log::info( "Running \"%s\" in terminal", cmd );
|
|
if ( term == nullptr || term->getTerm() == nullptr ) {
|
|
mApp->getTerminalManager()->openInExternalTerminal( cmd, finalBuild.workingDir );
|
|
} else {
|
|
term->executeFile( cmd );
|
|
}
|
|
} else {
|
|
Log::info( "Running \"%s\" in app", cmd );
|
|
finalBuild.config = build->getConfig();
|
|
saoc->run( finalBuild, {} );
|
|
}
|
|
} else {
|
|
Log::info( "ProjectBuildManager::runConfig is already running or isRunningApp() is true" );
|
|
}
|
|
}
|
|
|
|
std::string ProjectBuildManager::getCurrentDocument() {
|
|
return mApp->getSplitter() && mApp->getSplitter()->getCurEditor() &&
|
|
mApp->getSplitter()->getCurEditor()->getDocument().hasFilepath()
|
|
? mApp->getSplitter()->getCurEditor()->getDocument().getFilePath()
|
|
: "";
|
|
}
|
|
|
|
static std::unordered_map<std::string, std::string>
|
|
toUnorderedMap( const ProjectBuildKeyVal& vec ) {
|
|
std::unordered_map<std::string, std::string> map;
|
|
for ( const auto& v : vec )
|
|
map[v.first] = v.second;
|
|
return map;
|
|
}
|
|
|
|
void ProjectBuildManager::runBuild( const std::string& buildName, const std::string& buildType,
|
|
const ProjectBuildi18nFn& i18n,
|
|
const ProjectBuildCommandsRes& res,
|
|
const ProjectBuildProgressFn& progressFn,
|
|
const ProjectBuildDoneFn& doneFn ) {
|
|
BoolScopedOp scopedOp( mBuilding, true );
|
|
Clock clock;
|
|
|
|
auto printElapsed = [&clock, &i18n, &progressFn]() {
|
|
if ( progressFn ) {
|
|
progressFn(
|
|
100,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format(
|
|
i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(),
|
|
clock.getElapsedTime().toString().c_str() ),
|
|
nullptr );
|
|
}
|
|
};
|
|
|
|
if ( progressFn ) {
|
|
progressFn( 0,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format( i18n( "running_steps_for_project_ellipsis",
|
|
"Running steps for project %s...\n" )
|
|
.toUtf8()
|
|
.c_str(),
|
|
buildName.c_str() ),
|
|
nullptr );
|
|
|
|
if ( !buildType.empty() )
|
|
progressFn(
|
|
0,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format(
|
|
i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(),
|
|
buildType.c_str() ),
|
|
nullptr );
|
|
}
|
|
|
|
int c = 0;
|
|
int totSteps = 0;
|
|
for ( const auto& cmd : res.cmds )
|
|
totSteps += cmd.enabled ? 1 : 0;
|
|
|
|
for ( const auto& cmd : res.cmds ) {
|
|
int progress = c > 0 ? c / (Float)totSteps : 0;
|
|
mProcess = std::make_unique<Process>();
|
|
auto options = Process::SearchUserPath | Process::NoWindow | Process::CombinedStdoutStderr;
|
|
if ( !cmd.config.clearSysEnv )
|
|
options |= Process::InheritEnvironment;
|
|
|
|
if ( !cmd.enabled ) {
|
|
c++;
|
|
continue;
|
|
}
|
|
|
|
if ( progressFn ) {
|
|
progressFn(
|
|
progress,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format( i18n( "starting_process", "Starting %s %s\n" ).toUtf8().c_str(),
|
|
cmd.cmd.c_str(), cmd.args.c_str() ),
|
|
nullptr );
|
|
|
|
progressFn(
|
|
progress,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format( i18n( "working_dir_at", "Working Dir %s\n" ).toUtf8().c_str(),
|
|
cmd.workingDir.c_str() ),
|
|
nullptr );
|
|
}
|
|
|
|
if ( mProcess->create( cmd.cmd, cmd.args, options, toUnorderedMap( res.envs ),
|
|
cmd.workingDir ) ) {
|
|
std::string buffer( 4096, '\0' );
|
|
unsigned bytesRead = 0;
|
|
int returnCode;
|
|
do {
|
|
bytesRead = mProcess->readStdOut( buffer );
|
|
std::string data( buffer.substr( 0, bytesRead ) );
|
|
if ( progressFn )
|
|
progressFn( progress, std::move( data ), &cmd );
|
|
} while ( bytesRead != 0 && mProcess->isAlive() && !mShuttingDown && !mCancelBuild );
|
|
|
|
if ( mShuttingDown || mCancelBuild ) {
|
|
mProcess->kill();
|
|
mCancelBuild = false;
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_FAILURE, &cmd );
|
|
return;
|
|
}
|
|
|
|
mProcess->join( &returnCode );
|
|
mProcess->destroy();
|
|
|
|
if ( returnCode != EXIT_SUCCESS ) {
|
|
if ( progressFn ) {
|
|
progressFn( 100,
|
|
String::format( i18n( "process_exited_with_errors",
|
|
"The process \"%s\" exited with errors.\n" )
|
|
.toUtf8()
|
|
.c_str(),
|
|
cmd.cmd.c_str() ),
|
|
nullptr );
|
|
}
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( returnCode, &cmd );
|
|
return;
|
|
} else {
|
|
if ( progressFn ) {
|
|
progressFn( progress,
|
|
String::format( i18n( "process_exited_normally",
|
|
"The process \"%s\" exited normally.\n" )
|
|
.toUtf8()
|
|
.c_str(),
|
|
cmd.cmd.c_str() ),
|
|
nullptr );
|
|
}
|
|
}
|
|
} else {
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_FAILURE, nullptr );
|
|
return;
|
|
}
|
|
|
|
c++;
|
|
if ( c == (int)res.cmds.size() ) {
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_SUCCESS, &cmd );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::runApp( const ProjectBuildCommand& cmd, const ProjectBuildi18nFn& i18n,
|
|
const ProjectBuildCommandsRes& res,
|
|
const ProjectBuildProgressFn& progressFn,
|
|
const ProjectBuildDoneFn& doneFn ) {
|
|
BoolScopedOp op( mRunning, true );
|
|
Clock clock;
|
|
|
|
auto printElapsed = [&clock, &i18n, &progressFn]() {
|
|
if ( progressFn ) {
|
|
progressFn(
|
|
100,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format(
|
|
i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(),
|
|
clock.getElapsedTime().toString().c_str() ),
|
|
nullptr );
|
|
}
|
|
};
|
|
|
|
mProcess = std::make_unique<Process>();
|
|
|
|
auto options = Process::SearchUserPath | Process::CombinedStdoutStderr | Process::NoWindow;
|
|
if ( !cmd.config.clearSysEnv )
|
|
options |= Process::InheritEnvironment;
|
|
|
|
if ( progressFn ) {
|
|
progressFn(
|
|
0,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format( i18n( "starting_process", "Starting %s %s\n" ).toUtf8().c_str(),
|
|
cmd.cmd.c_str(), cmd.args.c_str() ),
|
|
nullptr );
|
|
|
|
progressFn(
|
|
0,
|
|
Sys::getDateTimeStr() + ": " +
|
|
String::format( i18n( "working_dir_at", "Working Dir %s\n" ).toUtf8().c_str(),
|
|
cmd.workingDir.c_str() ),
|
|
nullptr );
|
|
}
|
|
|
|
if ( mProcess->create( cmd.cmd, cmd.args, options, toUnorderedMap( res.envs ),
|
|
cmd.workingDir ) ) {
|
|
std::string buffer( 4096, '\0' );
|
|
unsigned bytesRead = 0;
|
|
int returnCode = 0;
|
|
do {
|
|
bytesRead = mProcess->readStdOut( buffer );
|
|
std::string data( buffer.substr( 0, bytesRead ) );
|
|
if ( progressFn )
|
|
progressFn( 0, std::move( data ), &cmd );
|
|
} while ( bytesRead != 0 && mProcess->isAlive() && !mShuttingDown && !mCancelRun );
|
|
|
|
if ( mShuttingDown || mCancelRun ) {
|
|
if ( mProcess )
|
|
mProcess->kill();
|
|
mCancelRun = false;
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_FAILURE, &cmd );
|
|
return;
|
|
}
|
|
|
|
if ( mProcess ) {
|
|
mProcess->join( &returnCode );
|
|
mProcess->destroy();
|
|
}
|
|
|
|
if ( returnCode != EXIT_SUCCESS ) {
|
|
if ( progressFn ) {
|
|
progressFn( 100,
|
|
String::format( i18n( "process_exited_with_errors",
|
|
"The process \"%s\" exited with errors.\n" )
|
|
.toUtf8()
|
|
.c_str(),
|
|
cmd.cmd.c_str() ),
|
|
nullptr );
|
|
}
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( returnCode, &cmd );
|
|
return;
|
|
} else {
|
|
if ( progressFn ) {
|
|
progressFn( 100,
|
|
String::format( i18n( "process_exited_normally",
|
|
"The process \"%s\" exited normally.\n" )
|
|
.toUtf8()
|
|
.c_str(),
|
|
cmd.cmd.c_str() ),
|
|
nullptr );
|
|
}
|
|
}
|
|
} else {
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_FAILURE, nullptr );
|
|
return;
|
|
}
|
|
|
|
printElapsed();
|
|
if ( doneFn )
|
|
doneFn( EXIT_SUCCESS, &cmd );
|
|
}
|
|
|
|
void ProjectBuildManager::buildSidePanelTab() {
|
|
mUISceneNode = mSidePanel->getUISceneNode();
|
|
UIIcon* icon = mUISceneNode->findIcon( "symbol-property" );
|
|
UIWidget* node = mUISceneNode->loadLayoutFromString(
|
|
R"html(
|
|
<ScrollView id="build_tab_view" lw="mp" lh="mp">
|
|
<vbox lw="mp" lh="wc" padding="4dp">
|
|
<TextView text="@string(build_settings, Build Settings)" font-size="15dp" focusable="false" />
|
|
<TextView text="@string(build_configuration, Build Configuration)" focusable="false" />
|
|
<hbox lw="mp" lh="wc" margin-top="2dp" margin-bottom="4dp">
|
|
<DropDownList id="build_list" layout_width="0" lw8="1" layout_height="wrap_content" />
|
|
<PushButton id="build_edit" id="build_edit" text="@string(edit_build, Edit Build)" tooltip="@string(edit_build, Edit Build)" text-as-fallback="true" icon="icon(file-edit, 12dp)" margin-left="2dp" />
|
|
<PushButton id="build_add" id="build_add" text="@string(add_build, Add Build)" tooltip="@string(add_build, Add Build)" text-as-fallback="true" icon="icon(add, 12dp)" margin-left="2dp" />
|
|
</hbox>
|
|
<TextView text="@string(build_target, Build Target)" margin-top="8dp" focusable="false" />
|
|
<DropDownList lw="mp" id="build_type_list" margin-top="2dp" />
|
|
<PushButton id="build_button" lw="mp" lh="wc" text="@string(build, Build)" margin-top="8dp" icon="icon(hammer, 12dp)" />
|
|
<PushButton id="clean_button" lw="mp" lh="wc" text="@string(clean, Clean)" margin-top="8dp" icon="icon(eraser, 12dp)" />
|
|
<TextView text="@string(run_target, Run Target)" margin-top="8dp" focusable="false" />
|
|
<DropDownList lw="mp" id="run_config_list" margin-top="2dp" />
|
|
<PushButton id="run_button" lw="mp" lh="wc" text="@string(run, Run)" margin-top="8dp" icon="icon(play, 12dp)" />
|
|
</vbox>
|
|
</ScrollView>
|
|
)html" );
|
|
mTab = mSidePanel->add( mUISceneNode->i18n( "build", "Build" ), node,
|
|
icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
|
mTab->setId( "build_tab" );
|
|
mTab->setTextAsFallback( true );
|
|
|
|
auto tabIndex = mSidePanel->getTabIndex( mTab );
|
|
if ( tabIndex > 0 ) {
|
|
auto prevTab = mSidePanel->getTab( tabIndex - 1 );
|
|
if ( prevTab && prevTab->getId() != "treeview_tab" )
|
|
mSidePanel->swapTabs( mTab, prevTab );
|
|
}
|
|
|
|
updateSidePanelTab();
|
|
}
|
|
|
|
void ProjectBuildManager::updateSidePanelTab() {
|
|
if ( mTab == nullptr )
|
|
return;
|
|
UIWidget* buildTab = mTab->getOwnedWidget()->find<UIWidget>( "build_tab_view" );
|
|
if ( buildTab == nullptr )
|
|
return;
|
|
UIDropDownList* buildList = buildTab->find<UIDropDownList>( "build_list" );
|
|
UIPushButton* buildButton = buildTab->find<UIPushButton>( "build_button" );
|
|
UIPushButton* cleanButton = buildTab->find<UIPushButton>( "clean_button" );
|
|
UIPushButton* runButton = buildTab->find<UIPushButton>( "run_button" );
|
|
UIPushButton* buildAdd = buildTab->find<UIPushButton>( "build_add" );
|
|
UIPushButton* buildEdit = buildTab->find<UIPushButton>( "build_edit" );
|
|
|
|
buildList->getListBox()->clear();
|
|
|
|
String first = mConfig.buildName;
|
|
std::vector<String> buildNamesItems;
|
|
for ( const auto& tbuild : mBuilds ) {
|
|
buildNamesItems.push_back( tbuild.first );
|
|
if ( first.empty() )
|
|
first = tbuild.first;
|
|
}
|
|
|
|
std::sort( buildNamesItems.begin(), buildNamesItems.end() );
|
|
|
|
buildList->getListBox()->addListBoxItems( buildNamesItems );
|
|
|
|
if ( !first.empty() && buildList->getListBox()->getItemIndex( first ) != eeINDEX_NOT_FOUND ) {
|
|
buildList->getListBox()->setSelected( first );
|
|
mConfig.buildName = first;
|
|
} else if ( !buildList->getListBox()->isEmpty() ) {
|
|
buildList->getListBox()->setSelected( 0L );
|
|
mConfig.buildName = buildList->getListBox()->getItemSelectedText();
|
|
}
|
|
|
|
buildList->setEnabled( !buildList->getListBox()->isEmpty() );
|
|
buildEdit->setEnabled( !buildList->getListBox()->isEmpty() );
|
|
|
|
updateBuildType();
|
|
|
|
updateRunConfig();
|
|
|
|
if ( !buildList->hasEventsOfType( Event::OnItemSelected ) ) {
|
|
buildList->on( Event::OnItemSelected, [this, buildEdit, buildList]( const Event* ) {
|
|
mConfig.buildName = buildList->getListBox()->getItemSelectedText();
|
|
mConfig.buildType = "";
|
|
mConfig.runName = "";
|
|
buildEdit->setEnabled( true );
|
|
updateBuildType();
|
|
updateRunConfig();
|
|
} );
|
|
}
|
|
|
|
buildButton->setEnabled( !mConfig.buildName.empty() && hasBuild( mConfig.buildName ) &&
|
|
hasBuildCommands( mConfig.buildName ) );
|
|
|
|
cleanButton->setEnabled( !mConfig.buildName.empty() && hasBuild( mConfig.buildName ) &&
|
|
hasCleanCommands( mConfig.buildName ) );
|
|
|
|
if ( !buildButton->hasEventsOfType( Event::MouseClick ) ) {
|
|
buildButton->onClick( [this]( auto ) {
|
|
if ( isBuilding() ) {
|
|
cancelBuild();
|
|
} else {
|
|
buildCurrentConfig( mApp->getStatusBuildOutputController() );
|
|
}
|
|
} );
|
|
}
|
|
|
|
if ( !cleanButton->hasEventsOfType( Event::MouseClick ) ) {
|
|
cleanButton->onClick( [this]( auto ) {
|
|
if ( isBuilding() ) {
|
|
cancelBuild();
|
|
} else {
|
|
cleanCurrentConfig( mApp->getStatusBuildOutputController() );
|
|
}
|
|
} );
|
|
}
|
|
|
|
if ( !runButton->hasEventsOfType( Event::MouseClick ) ) {
|
|
runButton->onClick( [this]( auto ) {
|
|
if ( isRunningApp() ) {
|
|
cancelRun();
|
|
} else {
|
|
runCurrentConfig( mApp->getStatusAppOutputController(), false );
|
|
}
|
|
} );
|
|
}
|
|
|
|
if ( !buildAdd->hasEventsOfType( Event::MouseClick ) ) {
|
|
buildAdd->onClick( [this, buildTab]( auto ) { addBuild( buildTab ); } );
|
|
}
|
|
|
|
if ( !buildEdit->hasEventsOfType( Event::MouseClick ) ) {
|
|
buildEdit->onClick(
|
|
[this, buildTab]( auto ) { editBuild( mConfig.buildName, buildTab ); } );
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::updateBuildType() {
|
|
if ( mTab == nullptr )
|
|
return;
|
|
UIWidget* buildTab = mTab->getOwnedWidget()->find<UIWidget>( "build_tab_view" );
|
|
if ( buildTab == nullptr )
|
|
return;
|
|
UIDropDownList* buildList = buildTab->find<UIDropDownList>( "build_list" );
|
|
UIDropDownList* buildTypeList = buildTab->find<UIDropDownList>( "build_type_list" );
|
|
|
|
buildTypeList->getListBox()->clear();
|
|
|
|
String first = buildList->getListBox()->getItemSelectedText();
|
|
if ( !first.empty() ) {
|
|
auto foundIt = mBuilds.find( first );
|
|
if ( foundIt != mBuilds.end() ) {
|
|
const auto& buildTypes = foundIt->second.buildTypes();
|
|
std::vector<String> buildTypesItems;
|
|
for ( const auto& buildType : buildTypes )
|
|
buildTypesItems.emplace_back( buildType );
|
|
buildTypeList->getListBox()->addListBoxItems( buildTypesItems );
|
|
if ( buildTypeList->getListBox()->getItemIndex( mConfig.buildType ) !=
|
|
eeINDEX_NOT_FOUND ) {
|
|
buildTypeList->getListBox()->setSelected( mConfig.buildType );
|
|
} else if ( !buildTypeList->getListBox()->isEmpty() ) {
|
|
buildTypeList->getListBox()->setSelected( 0 );
|
|
mConfig.buildType = buildTypeList->getListBox()->getItemSelectedText();
|
|
}
|
|
}
|
|
}
|
|
buildTypeList->setEnabled( !buildTypeList->getListBox()->isEmpty() );
|
|
|
|
if ( !buildTypeList->hasEventsOfType( Event::OnItemSelected ) ) {
|
|
buildTypeList->on( Event::OnItemSelected, [this, buildTypeList]( const Event* ) {
|
|
mConfig.buildType = buildTypeList->getListBox()->getItemSelectedText();
|
|
} );
|
|
}
|
|
}
|
|
|
|
void ProjectBuildManager::updateRunConfig() {
|
|
if ( mTab == nullptr )
|
|
return;
|
|
UIWidget* buildTab = mTab->getOwnedWidget()->find<UIWidget>( "build_tab_view" );
|
|
if ( buildTab == nullptr )
|
|
return;
|
|
UIDropDownList* buildList = buildTab->find<UIDropDownList>( "build_list" );
|
|
UIDropDownList* runConfigList = buildTab->find<UIDropDownList>( "run_config_list" );
|
|
|
|
auto runName = mConfig.runName;
|
|
runConfigList->getListBox()->clear();
|
|
mConfig.runName = runName;
|
|
|
|
String first = buildList->getListBox()->getItemSelectedText();
|
|
if ( !first.empty() ) {
|
|
auto foundIt = mBuilds.find( first );
|
|
if ( foundIt != mBuilds.end() ) {
|
|
const auto& runConfigs = foundIt->second.runConfigs();
|
|
std::vector<String> items;
|
|
size_t i = 1;
|
|
for ( const auto& run : runConfigs ) {
|
|
auto name = run.name.empty() ? String::format( mApp->i18n( "custom_executable_num",
|
|
"Custom Executable %d" )
|
|
.toUtf8(),
|
|
i )
|
|
: run.name;
|
|
items.emplace_back( name );
|
|
i++;
|
|
}
|
|
runConfigList->getListBox()->addListBoxItems( items );
|
|
if ( runConfigList->getListBox()->getItemIndex( mConfig.runName ) !=
|
|
eeINDEX_NOT_FOUND ) {
|
|
runConfigList->getListBox()->setSelected( mConfig.runName );
|
|
} else if ( !runConfigList->getListBox()->isEmpty() ) {
|
|
runConfigList->getListBox()->setSelected( 0 );
|
|
mConfig.runName = runConfigList->getListBox()->getItemSelectedText();
|
|
}
|
|
}
|
|
}
|
|
runConfigList->setEnabled( !runConfigList->getListBox()->isEmpty() );
|
|
buildTab->find( "run_button" )->setEnabled( !runConfigList->getListBox()->isEmpty() );
|
|
|
|
if ( !runConfigList->hasEventsOfType( Event::OnItemSelected ) ) {
|
|
runConfigList->on( Event::OnItemSelected, [this, runConfigList, buildTab]( const Event* ) {
|
|
mConfig.runName = runConfigList->getListBox()->getItemSelectedText();
|
|
buildTab->find( "run_button" )->setEnabled( !runConfigList->getListBox()->isEmpty() );
|
|
} );
|
|
}
|
|
if ( !runConfigList->hasEventsOfType( Event::OnClear ) ) {
|
|
runConfigList->on( Event::OnClear, [this, runConfigList, buildTab]( const Event* ) {
|
|
mConfig.runName = "";
|
|
buildTab->find( "run_button" )->setEnabled( !runConfigList->getListBox()->isEmpty() );
|
|
} );
|
|
}
|
|
}
|
|
|
|
std::map<std::string, ProjectBuildOutputParser> ProjectBuildOutputParser::getPresets() {
|
|
std::map<std::string, ProjectBuildOutputParser> presets;
|
|
presets["generic"] = getGeneric();
|
|
return presets;
|
|
}
|
|
|
|
bool ProjectBuildOutputParser::existsPreset( const std::string& name ) {
|
|
auto presets = getPresets();
|
|
return presets.find( name ) != presets.end();
|
|
}
|
|
|
|
ProjectBuildOutputParser ProjectBuildOutputParser::getGeneric() {
|
|
ProjectBuildOutputParser parser;
|
|
parser.mConfig.reserve( 6 );
|
|
|
|
ProjectBuildOutputParserConfig cfg;
|
|
cfg.pattern = "([^:]+):(%d+):(%d+):%s?[%w%s]*error:%s?(.*)";
|
|
cfg.patternOrder.col = 3;
|
|
cfg.patternOrder.file = 1;
|
|
cfg.patternOrder.line = 2;
|
|
cfg.patternOrder.message = 4;
|
|
cfg.type = ProjectOutputParserTypes::Error;
|
|
parser.mConfig.push_back( cfg );
|
|
|
|
cfg.pattern = "([^:]+):(%d+):%s?[%w%s]*error:%s?(.*)";
|
|
cfg.patternOrder.col = 0;
|
|
cfg.type = ProjectOutputParserTypes::Error;
|
|
parser.mConfig.push_back( cfg );
|
|
|
|
cfg.pattern = "([^:]+):(%d+):(%d+):%s?[%w%s]*warning:%s?(.*)";
|
|
cfg.patternOrder.col = 3;
|
|
cfg.type = ProjectOutputParserTypes::Warning;
|
|
parser.mConfig.push_back( cfg );
|
|
|
|
cfg.pattern = "([^:]+):(%d+):%s?[%w%s]*warning:%s?(.*)";
|
|
cfg.patternOrder.col = 0;
|
|
cfg.type = ProjectOutputParserTypes::Warning;
|
|
parser.mConfig.push_back( cfg );
|
|
|
|
cfg.pattern = "([^:]+):(%d+):(%d+):%s?[%w%s]*notice:%s?(.*)";
|
|
cfg.patternOrder.col = 3;
|
|
cfg.type = ProjectOutputParserTypes::Notice;
|
|
parser.mConfig.emplace_back( cfg );
|
|
|
|
cfg.pattern = "([^:]+):(%d+):%s?[%w%s]*notice:%s?(.*)";
|
|
cfg.patternOrder.col = 0;
|
|
cfg.type = ProjectOutputParserTypes::Notice;
|
|
parser.mConfig.emplace_back( cfg );
|
|
|
|
return parser;
|
|
}
|
|
|
|
} // namespace ecode
|