#pragma once #include #include #include #include #include #include #include "src/SignalHandler/SignalHandler.hpp" #include "src/TargetController/TargetController.hpp" #include "src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp" #include "src/Insight/Insight.hpp" #include "src/Helpers/Thread.hpp" #include "src/Logger/Logger.hpp" #include "src/ApplicationConfig.hpp" #include "src/Exceptions/Exception.hpp" #include "src/EventManager/EventListener.hpp" #include "src/EventManager/Events/Events.hpp" namespace Bloom { /** * Bloom - a debug interface for embedded systems development on Linux. * * This is the main entry-point of execution for the Bloom program. The methods within will run on the main * thread. If Insight is enabled, execution will be passed over to Insight::run() upon startup. */ class Application: public Thread { public: static const inline std::string VERSION_STR = "0.4.2"; explicit Application() = default; /** * This mapping is used to map debug server names from project configuration files to polymorphic instances of * the DebugServer class. * * See Application::startDebugServer() for more on this. * * @return */ auto getSupportedDebugServers() { return std::map()>> { { "avr-gdb-rsp", [this] () -> std::unique_ptr { return std::make_unique(this->eventManager); } }, }; }; /** * Main entry-point for the Bloom program. * * @param arguments * A vector of string arguments passed from the user via the cli. * * @return */ int run(const std::vector& arguments); /** * Checks if the current effective user running Bloom has root privileges. * * @return */ static bool isRunningAsRoot(); private: /** * This is the application wide event manager. It should be the only instance of the EventManager class at * any given time. * * It should be injected (by reference) into any other instances of classes that require use * of the EventManager. */ EventManager eventManager = EventManager(); EventListenerPointer applicationEventListener = std::make_shared("ApplicationEventListener"); /** * The SignalHandler deals with any UNIX signals. It runs on a dedicated thread. All other threads * ignore UNIX signals. * * See the SignalHandler class for more on this. */ SignalHandler signalHandler = SignalHandler(this->eventManager); std::thread signalHandlerThread; /** * The TargetController possesses full control of the connect debug tool and target. It runs on a * dedicated thread. * * See the TargetController class for more on this. */ TargetController targetController = TargetController(this->eventManager); std::thread targetControllerThread; /** * The DebugServer exposes an interface to the connected target, to third-party software such as IDEs. It runs * on a dedicated thread. * * See the DebugServer and GdbRspDebugServer class for more on this. */ std::unique_ptr debugServer = nullptr; std::thread debugServerThread; /** * Insight is, effectively, a small Qt application that serves a GUI to the user. It occupies the main thread, * as well as a single worker thread, and possibly other threads created by Qt. * * Insight is optional - users can disable it via their project configuration. * * When the user closes the Insight GUI, control of the main thread is returned to Application::run(). How we * deal with the GUI being closed at this point depends on user configuration. * * See the Insight class for more on this. * * Because Insight is optional, we only construct the Insight object when we need it. We can't use * std::optional here because the Insight class extends QObject, which disables the copy constructor and * the assignment operator. So we use an std::unique_ptr instead, which is perfectly fine for this use case, * as we want to manage the lifetime of the object here. */ std::unique_ptr insight = nullptr; /** * Configuration extracted from the user's project configuration file. * * See ApplicationConfig.hpp for more on this. */ ApplicationConfig applicationConfig; EnvironmentConfig environmentConfig; DebugServerConfig debugServerConfig; InsightConfig insightConfig; /** * The project environment selected by the user. * * If an environment name is not provided as an argument when running Bloom, Bloom will fallback to the * environment named "default". */ std::string selectedEnvironmentName = "default"; /** * Some CLI arguments are interpreted as commands and thus require specific handler methods to be called. * This mapping maps command strings to the appropriate handler methods. The mapped handler method is invoked * when the command is provided as an argument from the CLI. * * See Application::run() for more on this. * * @return */ auto getCommandToHandlerMapping() { return std::map> { { "--help", std::bind(&Application::presentHelpText, this) }, { "-h", std::bind(&Application::presentHelpText, this) }, { "--version", std::bind(&Application::presentVersionText, this) }, { "-v", std::bind(&Application::presentVersionText, this) }, { "init", std::bind(&Application::initProject, this) }, }; } /** * Kicks off the application. * * Will start the TargetController and DebugServer. And register the main application's event handlers. */ void startup(); /** * Will cleanly shutdown the application. This should never fail. */ void shutdown(); /** * Extracts config from the user's JSON config file and generates an ApplicationConfig object. * * @see ApplicationConfig declaration for more on this. * @return */ static ApplicationConfig extractConfig(); /** * Presents application help text to user. * * @return */ int presentHelpText(); /** * Presents the current Bloom version number to user. * * @return */ int presentVersionText(); /** * Initialises a project in the user's working directory. * * @return */ int initProject(); /** * Prepares a dedicated thread for the TargetController and kicks it off. */ void startTargetController(); /** * Invokes a clean shutdown of the TargetController. The TargetController should disconnect from the target * and debug tool in a clean and safe manner, ensuring that both are left in a sensible state. * * This will join the TargetController thread. */ void stopTargetController(); /** * Prepares a dedicated thread for the DebugServer and kicks it off with a call to DebugServer::run(). */ void startDebugServer(); /** * Sends a shutdown request to the DebugServer thread and waits on it to exit. */ void stopDebugServer(); /** * If the TargetController unexpectedly shuts down, the rest of the application will follow. * * @param event */ void onTargetControllerThreadStateChanged(const Events::TargetControllerThreadStateChanged& event); /** * Same goes for the DebugServer - it should never shutdown unless a shutdown request was issued. If it does, * something horrible has happened and so we shutdown the rest of the application in response. * * @param event */ void onDebugServerThreadStateChanged(const Events::DebugServerThreadStateChanged& event); /** * Triggers a shutdown of Bloom and all of its components. */ void onShutdownApplicationRequest(const Events::ShutdownApplication&); }; }