Files
eepp/src/tools/ecode/projectbuild.cpp
Martín Lucas Golini 73a868db55 Allow to drag folder into the editor and it will load the folder (SpartanJ/ecode/314).
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.
2024-08-29 18:20:17 -03:00

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