#include "./buildactionprivate.h" #include "../webapi/session.h" #include #include #include "reflection/buildaction.h" #include #ifdef LIBREPOMGR_DUMMY_BUILD_ACTION_ENABLED #include #include #include #endif using namespace std; using namespace CppUtilities; using namespace CppUtilities::EscapeCodes; namespace LibRepoMgr { InternalBuildAction::InternalBuildAction(ServiceSetup &setup, const std::shared_ptr &buildAction) : m_setup(setup) , m_buildAction(buildAction) { } static bool isAur(const std::string &dbName) { return dbName == "aur" || dbName == "AUR"; } std::string InternalBuildAction::validateParameter(RequiredDatabases requiredDatabases, RequiredParameters requiredParameters) { if (requiredDatabases & RequiredDatabases::OneOrMoreSources) { if (m_buildAction->sourceDbs.empty()) { return "no source databases specified"; } } else if (requiredDatabases & RequiredDatabases::OneSource) { if (m_buildAction->sourceDbs.size() != 1) { return "not exactly one source database specified"; } } else if (!(requiredDatabases & RequiredDatabases::MaybeSource)) { if (!m_buildAction->sourceDbs.empty()) { return "no source database must be specified"; } } if (requiredDatabases & RequiredDatabases::OneOrMoreDestinations) { if (m_buildAction->destinationDbs.empty()) { return "no destination databases specified"; } } else if (requiredDatabases & RequiredDatabases::OneDestination) { if (m_buildAction->destinationDbs.size() != 1) { return "not exactly one destination database specified"; } } else if (!(requiredDatabases & RequiredDatabases::MaybeDestination)) { if (!m_buildAction->destinationDbs.empty()) { return "no destination database must be specified"; } } for (const auto &sourceDb : m_buildAction->sourceDbs) { if (sourceDb.empty() || sourceDb == "none") { return "empty/invalid source database specified"; } if (isAur(sourceDb)) { if (requiredDatabases & RequiredDatabases::AllowFromAur) { m_fromAur = true; } else { return "source database must not be AUR"; } } } for (const auto &destinationDb : m_buildAction->destinationDbs) { if (destinationDb.empty() || destinationDb == "none") { return "empty/invalid destination database specified"; } if (isAur(destinationDb)) { if (requiredDatabases & RequiredDatabases::AllowToAur) { m_toAur = true; } else { return "destination database must not be AUR"; } } } if (requiredParameters & RequiredParameters::Packages) { if (m_buildAction->packageNames.empty()) { return "no packages specified"; } } else if (!(requiredParameters & RequiredParameters::MaybePackages)) { if (!m_buildAction->packageNames.empty()) { return "no packages must be specified"; } } return string(); } std::string InternalBuildAction::findDatabases() { m_sourceDbs.clear(); m_destinationDbs.clear(); for (const auto &sourceDb : m_buildAction->sourceDbs) { if (isAur(sourceDb)) { continue; } if (auto *const db = m_setup.config.findDatabaseFromDenotation(sourceDb)) { m_sourceDbs.emplace(db); } else { return "source database " % sourceDb + " does not exist"; } } for (const auto &destinationDb : m_buildAction->destinationDbs) { if (isAur(destinationDb)) { continue; } if (auto *const db = m_setup.config.findDatabaseFromDenotation(destinationDb)) { m_destinationDbs.emplace(db); } else { return "destination database " % destinationDb + " does not exist"; } } return string(); } typename InternalBuildAction::InitReturnType InternalBuildAction::init( BuildActionAccess access, RequiredDatabases requiredDatabases, RequiredParameters requiredParameters) { InitReturnType configLock; if (auto error = validateParameter(requiredDatabases, requiredParameters); !error.empty()) { reportError(move(error)); return configLock; } switch (access) { case BuildActionAccess::ReadConfig: configLock = m_setup.config.lockToRead(); break; case BuildActionAccess::WriteConfig: configLock = m_setup.config.lockToWrite(); break; } if (auto error = findDatabases(); !error.empty()) { configLock = monostate(); reportError(move(error)); return configLock; } return configLock; } std::string InternalBuildAction::determineWorkingDirectory(std::string_view name) { const auto workingDirectory = m_setup.building.workingDirectory % '/' % name % '/' + m_buildAction->directory; m_buildAction->appendOutput(Phrases::InfoMessage, "Working directory: " % workingDirectory + '\n'); return workingDirectory; } const std::string &InternalBuildAction::findSetting(const std::string_view &setting) const { if (const auto i = m_buildAction->settings.find(std::string(setting)); i != m_buildAction->settings.end()) { return i->second; } else { static const auto empty = std::string(); return empty; } } void InternalBuildAction::reportError(std::string &&error) { const auto buildActionLock = m_setup.building.lockToWrite(); m_buildAction->resultData = move(error); m_buildAction->conclude(BuildActionResult::Failure); } void InternalBuildAction::reportError() { m_buildAction->conclude(BuildActionResult::Failure); } void InternalBuildAction::reportSuccess() { m_buildAction->conclude(BuildActionResult::Success); } void InternalBuildAction::reportResult(BuildActionResult result) { m_buildAction->conclude(result); } bool InternalBuildAction::reportAbortedIfAborted() { if (!m_buildAction->isAborted()) { return false; } const auto buildActionLock = m_setup.building.lockToWrite(); m_buildAction->conclude(BuildActionResult::Aborted); return true; } BuildAction::BuildAction(IdType id, ServiceSetup *setup) noexcept : id(id) , m_log(this) , m_setup(setup) , m_stopHandler(std::bind(&BuildAction::terminateOngoingBuildProcesses, this)) { } BuildAction::~BuildAction() { } bool BuildAction::haveSucceeded(const std::vector> &buildActions) { for (const auto &buildAction : buildActions) { if (!buildAction->hasSucceeded()) { return false; } } return true; } /*! * \brief Starts the build action. The caller must acquire the lock to write build actions. * \returns Returns immediately. The real work is done in a build action thread. */ void BuildAction::start(ServiceSetup &setup) { if (!isScheduled()) { return; } started = DateTime::gmtNow(); status = BuildActionStatus::Running; m_setup = &setup; switch (type) { case BuildActionType::Invalid: resultData = "type is invalid"; conclude(BuildActionResult::Failure); break; case BuildActionType::RemovePackages: post(); break; case BuildActionType::MovePackages: post(); break; case BuildActionType::CheckForUpdates: post(); break; case BuildActionType::ReloadDatabase: post(); break; case BuildActionType::ReloadLibraryDependencies: post(); break; case BuildActionType::PrepareBuild: post(); break; case BuildActionType::ConductBuild: post(); break; case BuildActionType::MakeLicenseInfo: post(); break; case BuildActionType::ReloadConfiguration: post(); break; case BuildActionType::CheckForProblems: post(); break; case BuildActionType::CleanRepository: post(); break; #ifdef LIBREPOMGR_DUMMY_BUILD_ACTION_ENABLED case BuildActionType::DummyBuildAction: post(); break; #endif case BuildActionType::CustomCommand: post(); break; default: resultData = "not implemented yet or invalid type"; conclude(BuildActionResult::Failure); } } void BuildAction::startAfterOtherBuildActions(ServiceSetup &setup, const std::vector> &startsAfterBuildActions) { auto allSucceeded = true; for (auto &previousBuildAction : startsAfterBuildActions) { if (!previousBuildAction->hasSucceeded()) { previousBuildAction->m_followUpActions.emplace_back(weak_from_this()); allSucceeded = false; } } if (allSucceeded) { start(setup); } } void BuildAction::abort() { m_aborted.store(true); if (m_setup && m_stopHandler) { boost::asio::post(m_setup->building.ioContext.get_executor(), m_stopHandler); } } template void BuildAction::post() { assert(m_setup); m_internalBuildAction = make_unique(*m_setup, shared_from_this()); post(bind(&InternalBuildActionType::run, static_cast(m_internalBuildAction.get()))); } template void BuildAction::post(Callback &&codeToRun) { assert(m_setup); boost::asio::post(m_setup->building.ioContext.get_executor(), forward(codeToRun)); } /*! * \brief Internally called to conclude the build action. */ void BuildAction::conclude(BuildActionResult result) { // set fields accordingly status = BuildActionStatus::Finished; this->result = result; finished = DateTime::gmtNow(); // tell clients waiting for output that it's over const auto outputStreamingLock = std::unique_lock(m_outputStreamingMutex); for (auto i = m_bufferingForSession.begin(); i != m_bufferingForSession.end();) { if (!i->second->currentlySentBuffers.empty() || !i->second->outstandingBuffersToSend.empty()) { ++i; continue; } boost::beast::net::async_write(i->first->socket(), boost::beast::http::make_chunk_last(), std::bind(&WebAPI::Session::responded, i->first, std::placeholders::_1, std::placeholders::_2, true)); i = m_bufferingForSession.erase(i); } // start follow-up actions if succeeded if (result == BuildActionResult::Success && m_setup) { for (auto &maybeStillValidFollowUpAction : m_followUpActions) { auto followUpAction = maybeStillValidFollowUpAction.lock(); if (followUpAction && followUpAction->isScheduled()) { followUpAction->start(*m_setup); } } // note: Not cleaning up the follow-up actions here because at some point I might implement recursive restarting. } if (m_concludeHandler) { m_concludeHandler(); } } #ifdef LIBREPOMGR_DUMMY_BUILD_ACTION_ENABLED DummyBuildAction::DummyBuildAction(ServiceSetup &setup, const std::shared_ptr &buildAction) : InternalBuildAction(setup, buildAction) , m_counter(0) , m_timer(setup.building.ioContext) { m_buildAction->setStopHandler(std::bind(&DummyBuildAction::stop, this)); } void DummyBuildAction::run() { // validate parameter if (auto error = validateParameter(RequiredDatabases::None, RequiredParameters::None); !error.empty()) { reportError(move(error)); return; } if (m_buildAction->directory.empty()) { reportError("Unable to find working directory: no directory name specified"); return; } // find test files auto testApp = TestApplication(); auto scriptPath = std::string(); try { scriptPath = testFilePath("scripts/print_some_data.sh"); } catch (const std::exception &e) { reportError(e.what()); return; } // create working directory m_workingDirectory = "dummy/" + m_buildAction->directory; try { std::filesystem::create_directories(m_workingDirectory); } catch (const std::filesystem::filesystem_error &e) { reportError(argsToString("Unable to make working directory: ", e.what())); return; } // add an artefact auto buildActionsWriteLock = m_setup.building.lockToWrite(); m_buildAction->artefacts.emplace_back(m_workingDirectory + "/some-artefact.txt"); buildActionsWriteLock.unlock(); try { writeFile(m_buildAction->artefacts.back(), "artefact contents\n"); } catch (const std::ios_base::failure &e) { reportError(argsToString("Unable to make artefact: ", e.what())); return; } // launch subprocess producing a logfile m_logProcess = m_buildAction->makeBuildProcess("dummy", m_workingDirectory + "/foo.log", [this](boost::process::child &&child, ProcessResult &&result) { CPP_UTILITIES_UNUSED(child) m_logProcess = nullptr; m_buildAction->appendOutput("log process exited with code: ", result.exitCode, '\n'); if (!result.error.empty()) { m_buildAction->appendOutput("log process error: ", result.error, '\n'); } if (!m_buildAction->isAborted()) { stop(); } }); m_logProcess->launch(scriptPath, "1"); continuePrinting(); } void DummyBuildAction::continuePrinting() { m_timer.expires_from_now(boost::posix_time::seconds(1)); m_timer.async_wait(std::bind(&DummyBuildAction::printLine, this)); } void DummyBuildAction::printLine() { if (m_buildAction->isAborted()) { return; } m_buildAction->appendOutput("Output: ", ++m_counter, '\n'); continuePrinting(); } void DummyBuildAction::stop() { m_buildAction->appendOutput("stopping"sv); boost::system::error_code timerCancelError; m_timer.cancel(timerCancelError); if (timerCancelError.failed()) { m_buildAction->appendOutput("failed to cancel timer: ", timerCancelError.message(), '\n'); } std::error_code terminateError; if (m_logProcess) { m_logProcess->group.terminate(terminateError); if (terminateError) { m_buildAction->appendOutput("failed to terminate logging process: ", terminateError.message(), '\n'); } } reportSuccess(); } #endif // LIBREPOMGR_DUMMY_BUILD_ACTION_ENABLED } // namespace LibRepoMgr