diff --git a/.ecode/project_build.json b/.ecode/project_build.json new file mode 100644 index 000000000..be9c9ea21 --- /dev/null +++ b/.ecode/project_build.json @@ -0,0 +1,48 @@ +{ + "ecode-debug": { + "build": [ + { + "args": "--with-mojoal --with-debug-symbols gmake", + "command": "premake4", + "working_dir": "${project_root}" + }, + { + "args": "-j$(nproc) config=debug ecode", + "command": "make", + "working_dir": "${build_dir}" + } + ], + "clean": [ + { + "args": "config=debug clean", + "command": "make", + "working_dir": "${build_dir}" + } + ], + "config": { + "clear_sys_env": false + }, + "var": { + "build_dir": "${project_root}/make/linux" + }, + "env": { + "SHELL": "fish" + }, + "output_parser": { + "config": { + "relative_file_paths": true + }, + "error": [ + { + "pattern": "([^:]*):(%d+):(%d+):%s?[%w%s]*error:%s?([^\n]*)", + "pattern_order": { + "col": 3, + "file": 1, + "line": 2, + "message": 4 + } + } + ] + } + } +} diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp new file mode 100644 index 000000000..156680ae2 --- /dev/null +++ b/src/tools/ecode/projectbuild.cpp @@ -0,0 +1,174 @@ +#include "projectbuild.hpp" +#include "scopedop.hpp" +#include +#include +#include +#include +using json = nlohmann::json; + +using namespace EE; + +namespace ecode { + +static const char* PROJECT_ROOT = "${project_root}"; + +void ProjectBuild::replaceVars() { + const std::vector steps{ &mBuild, &mClean }; + auto replaceVar = []( ProjectBuildStep& s, const std::string& var, const std::string& val ) { + String::replaceAll( s.workingDir, var, val ); + String::replaceAll( s.cmd, var, val ); + String::replaceAll( s.args, var, val ); + }; + + for ( auto& step : steps ) { + for ( auto& s : *step ) { + replaceVar( s, PROJECT_ROOT, mProjectRoot ); + for ( auto& var : mVars ) { + std::string varKey( "${" + var.first + "}" ); + String::replaceAll( var.second, PROJECT_ROOT, mProjectRoot ); + replaceVar( s, varKey, var.second ); + } + } + } +} + +ProjectBuildManager::ProjectBuildManager( const std::string& projectRoot, + std::shared_ptr pool ) : + mProjectRoot( projectRoot ), mThreadPool( pool ) { + FileSystem::dirAddSlashAtEnd( mProjectRoot ); + + if ( mThreadPool ) { + mThreadPool->run( [this]() { load(); } ); + } else { + load(); + } +}; + +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; +} + +bool ProjectBuildManager::load() { + ScopedOp op( [this]() { mLoading = true; }, [this]() { mLoading = false; } ); + + 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: ", + mProjectFile.c_str(), e.what() ); + return false; + } + + for ( const auto& build : j.items() ) { + ProjectBuild b( build.key(), mProjectRoot ); + const auto& buildObj = build.value(); + + 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[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[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", "" ); + b.mBuild.emplace_back( std::move( bstep ) ); + } + } + + if ( buildObj.contains( "clean" ) && buildObj["clean"].is_array() ) { + const auto& buildArray = buildObj["clean"]; + for ( const auto& step : buildArray ) { + ProjectBuildStep bstep; + bstep.cmd = step.value( "command", "" ); + bstep.args = step.value( "args", "" ); + bstep.workingDir = step.value( "working_dir", "" ); + b.mClean.emplace_back( std::move( bstep ) ); + } + } + + if ( buildObj.contains( "output_parser" ) && buildObj["output_parser"].is_object() ) { + const auto& op = buildObj["output_parser"]; + + ProjectBuildOutputParser outputParser; + + for ( const auto& op : op.items() ) { + if ( op.key() == "config" ) { + const auto& config = op.value(); + outputParser.mRelativeFilePaths = config.value( "output_parser", true ); + } else { + auto typeStr = String::toLower( op.key() ); + + if ( !isValidType( typeStr ) ) + continue; + + const auto& ptrnCfg = op.value(); + 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(); + if ( po.contains( "col" ) && po["col"].is_number() ) + opc.patternOrder.col = po["col"].get(); + if ( po.contains( "message" ) && po["message"].is_number() ) + opc.patternOrder.message = po["message"].get(); + if ( po.contains( "file" ) && po["file"].is_number() ) + opc.patternOrder.file = po["file"].get(); + } + + outputParser.mConfig.emplace_back( std::move( opc ) ); + } + } + } + + b.replaceVars(); + + mBuilds.insert( { build.key(), std::move( b ) } ); + } + + mLoaded = true; + return true; +} + +void ProjectBuildManager::run( const std::string& buildName ) { + if ( !mLoaded ) + return; +} + +} // namespace ecode diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp new file mode 100644 index 000000000..8128b63dd --- /dev/null +++ b/src/tools/ecode/projectbuild.hpp @@ -0,0 +1,151 @@ +#ifndef ECODE_PROJECTBUILD_HPP +#define ECODE_PROJECTBUILD_HPP + +#include +#include +#include +#include +#include + +using namespace EE::System; + +namespace ecode { + +/** reference: + +{ + "ecode": { + "build": [ + { + "args": "--with-mojoal --with-debug-symbols gmake", + "command": "premake4", + "working_dir": "$PROJECT_ROOT" + }, + { + "args": "-j$(nproc) config=release ecode", + "command": "make", + "working_dir": "${build_dir}" + } + ], + "clean": [ + { + "args": "config=release clean", + "command": "make", + "working_dir": "${build_dir}" + } + ], + "config": { + "clear_sys_env": false + }, + "var": { + "build_dir": "$PROJECT_ROOT/make/linux" + }, + "env": { + "SHELL": "fish" + }, + "output_parser": { + "config": { + "relative_file_paths": true + }, + "error": [ + { + "pattern": "([^:]*):(%d+):(%d+):%s?[%w%s]*error:%s?([^\n]*)", + "pattern_order": { + "col": 3, + "file": 1, + "line": 2, + "message": 4 + } + } + ] + } + } +} +*/ + +struct ProjectBuildStep { + std::string cmd; + std::string args; + std::string workingDir; +}; + +using ProjectBuildSteps = std::vector; +using ProjectBuildKeyVal = std::unordered_map; + +struct ProjectBuildConfig { + bool clearSysEnv{ false }; +}; + +enum class ProjectOutputParserTypes { Error, Warning, Notice }; + +struct ProjectBuildOutputParserConfig { + ProjectOutputParserTypes type; + std::string pattern; + struct { + int file{ 1 }; + int line{ 2 }; + int col{ 3 }; + int message{ 4 }; + } patternOrder; +}; + +class ProjectBuildOutputParser { + protected: + friend class ProjectBuildManager; + + bool mRelativeFilePaths{ true }; + std::vector mConfig; +}; + +class ProjectBuild { + public: + ProjectBuild( const std::string& name, const std::string& projectRoot ) : + mName( name ), mProjectRoot( projectRoot ){}; + + protected: + friend class ProjectBuildManager; + + std::string mName; + std::string mProjectRoot; + ProjectBuildSteps mBuild; + ProjectBuildSteps mClean; + ProjectBuildKeyVal mEnvs; + ProjectBuildKeyVal mVars; + ProjectBuildConfig mConfig; + ProjectBuildOutputParser mOutputParser; + + void replaceVars(); +}; + +using ProjectBuildMap = std::unordered_map; + +class ProjectBuildManager { + public: + ProjectBuildManager( const std::string& projectRoot, std::shared_ptr pool ); + + void run( const std::string& buildName ); + + const ProjectBuildMap& getBuilds() const { return mBuilds; } + + const std::string& getProjectRoot() const { return mProjectRoot; } + + const std::string& getProjectFile() const { return mProjectFile; } + + bool loaded() const { return mLoaded; } + + bool loading() const { return mLoading; } + + protected: + std::string mProjectRoot; + std::string mProjectFile; + ProjectBuildMap mBuilds; + std::shared_ptr mThreadPool; + bool mLoaded{ false }; + bool mLoading{ false }; + + bool load(); +}; + +} // namespace ecode + +#endif