// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "algorithm.h" #include "commandline.h" #include "environment.h" #include "filepath.h" #include "qtcprocess.h" #include #include #include #include #include #include #include #include #include namespace Utils { // Use this facility for cached retrieval of data from a tool that always returns the same // output for the same parameters and is side effect free. // A prime example is version info via a --version switch. template class DataFromProcess { public: class Parameters { public: using OutputParser = std::function< std::optional(const QString & /* stdOut */, const QString & /* stdErr */)>; using ErrorHandler = std::function; using Callback = std::function &)>; Parameters(const CommandLine &cmdLine, const OutputParser &parser) : commandLine(cmdLine) , parser(parser) {} CommandLine commandLine; Environment environment = Environment::systemEnvironment(); std::chrono::seconds timeout = std::chrono::seconds(10); OutputParser parser; ErrorHandler errorHandler; Callback callback; QList allowedResults{ProcessResult::FinishedWithSuccess}; }; // Use the first variant whenever possible. static void provideData(const Parameters ¶ms); static std::optional getData(const Parameters ¶ms); private: using Key = std::tuple; using Value = std::pair, QDateTime>; static std::optional getOrProvideData(const Parameters ¶ms); static std::optional handleProcessFinished(const Parameters ¶ms, const QDateTime &exeTimestamp, const Key &cacheKey, const std::shared_ptr &process); static inline QHash m_cache; static inline QMutex m_cacheMutex; }; template inline void DataFromProcess::provideData(const Parameters ¶ms) { QTC_ASSERT(params.callback, return); getOrProvideData(params); } template inline std::optional DataFromProcess::getData(const Parameters ¶ms) { QTC_ASSERT(!params.callback, return {}); return getOrProvideData(params); } template inline std::optional DataFromProcess::getOrProvideData(const Parameters ¶ms) { if (params.commandLine.executable().isEmpty()) { if (params.callback) params.callback({}); return {}; } const auto key = std::make_tuple(params.commandLine.executable(), params.environment.toStringList(), params.commandLine.arguments()); const QDateTime exeTimestamp = params.commandLine.executable().lastModified(); { QMutexLocker cacheLocker(&m_cacheMutex); const auto it = m_cache.constFind(key); if (it != m_cache.constEnd() && it.value().second == exeTimestamp) return it.value().first; } const auto outputRetriever = std::make_shared(); outputRetriever->setCommand(params.commandLine); if (params.callback) { QObject::connect(outputRetriever.get(), &Process::done, [params, exeTimestamp, key, outputRetriever] { handleProcessFinished(params, exeTimestamp, key, outputRetriever); }); outputRetriever->start(); return {}; } outputRetriever->runBlocking(params.timeout); return handleProcessFinished(params, exeTimestamp, key, outputRetriever); } template inline std::optional DataFromProcess::handleProcessFinished( const Parameters ¶ms, const QDateTime &exeTimestamp, const Key &cacheKey, const std::shared_ptr &process) { // Do not store into cache: The next call might succeed. if (process->result() == ProcessResult::Canceled) { if (params.callback) params.callback({}); return {}; } std::optional data; if (params.allowedResults.contains(process->result())) data = params.parser(process->cleanedStdOut(), process->cleanedStdErr()); else if (params.errorHandler) params.errorHandler(*process); QMutexLocker cacheLocker(&m_cacheMutex); m_cache.insert(cacheKey, std::make_pair(data, exeTimestamp)); if (params.callback) { params.callback(data); return {}; } return data; } } // namespace Utils