"Fossies" - the Fresh Open Source Software Archive

Member "qt-creator-opensource-src-4.15.1/src/plugins/cmakeprojectmanager/fileapireader.cpp" (8 Jun 2021, 15864 Bytes) of package /linux/misc/qt-creator-opensource-src-4.15.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "fileapireader.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: opensource-src-4.15.0_vs_opensource-src-4.15.1.

    1 /****************************************************************************
    2 **
    3 ** Copyright (C) 2016 The Qt Company Ltd.
    4 ** Contact: https://www.qt.io/licensing/
    5 **
    6 ** This file is part of Qt Creator.
    7 **
    8 ** Commercial License Usage
    9 ** Licensees holding valid commercial Qt licenses may use this file in
   10 ** accordance with the commercial license agreement provided with the
   11 ** Software or, alternatively, in accordance with the terms contained in
   12 ** a written agreement between you and The Qt Company. For licensing terms
   13 ** and conditions see https://www.qt.io/terms-conditions. For further
   14 ** information use the contact form at https://www.qt.io/contact-us.
   15 **
   16 ** GNU General Public License Usage
   17 ** Alternatively, this file may be used under the terms of the GNU
   18 ** General Public License version 3 as published by the Free Software
   19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
   20 ** included in the packaging of this file. Please review the following
   21 ** information to ensure the GNU General Public License requirements will
   22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
   23 **
   24 ****************************************************************************/
   25 
   26 #include "fileapireader.h"
   27 
   28 #include "fileapidataextractor.h"
   29 #include "fileapiparser.h"
   30 #include "projecttreehelper.h"
   31 
   32 #include <coreplugin/messagemanager.h>
   33 
   34 #include <projectexplorer/projectexplorer.h>
   35 
   36 #include <utils/algorithm.h>
   37 #include <utils/runextensions.h>
   38 
   39 #include <QDateTime>
   40 #include <QLoggingCategory>
   41 
   42 using namespace ProjectExplorer;
   43 using namespace Utils;
   44 
   45 namespace CMakeProjectManager {
   46 namespace Internal {
   47 
   48 static Q_LOGGING_CATEGORY(cmakeFileApiMode, "qtc.cmake.fileApiMode", QtWarningMsg);
   49 
   50 using namespace FileApiDetails;
   51 
   52 // --------------------------------------------------------------------
   53 // FileApiReader:
   54 // --------------------------------------------------------------------
   55 
   56 FileApiReader::FileApiReader()
   57     : m_lastReplyTimestamp()
   58 {
   59     QObject::connect(&m_watcher,
   60                      &FileSystemWatcher::directoryChanged,
   61                      this,
   62                      &FileApiReader::replyDirectoryHasChanged);
   63 }
   64 
   65 FileApiReader::~FileApiReader()
   66 {
   67     if (isParsing())
   68         emit errorOccurred(tr("Parsing has been canceled."));
   69     stop();
   70     resetData();
   71 }
   72 
   73 void FileApiReader::setParameters(const BuildDirParameters &p)
   74 {
   75     qCDebug(cmakeFileApiMode)
   76         << "\n\n\n\n\n=============================================================\n";
   77 
   78     // Update:
   79     m_parameters = p;
   80     qCDebug(cmakeFileApiMode) << "Work directory:" << m_parameters.workDirectory.toUserOutput();
   81 
   82     // Reset watcher:
   83     m_watcher.removeFiles(m_watcher.files());
   84     m_watcher.removeDirectories(m_watcher.directories());
   85 
   86     FileApiParser::setupCMakeFileApi(m_parameters.workDirectory, m_watcher);
   87 
   88     resetData();
   89 }
   90 
   91 void FileApiReader::resetData()
   92 {
   93     m_cmakeFiles.clear();
   94     if (!m_parameters.sourceDirectory.isEmpty())
   95         m_cmakeFiles.insert(m_parameters.sourceDirectory.pathAppended("CMakeLists.txt"));
   96 
   97     m_cache.clear();
   98     m_buildTargets.clear();
   99     m_projectParts.clear();
  100     m_rootProjectNode.reset();
  101     m_knownHeaders.clear();
  102 }
  103 
  104 void FileApiReader::parse(bool forceCMakeRun,
  105                           bool forceInitialConfiguration,
  106                           bool forceExtraConfiguration)
  107 {
  108     qCDebug(cmakeFileApiMode) << "Parse called with arguments: ForceCMakeRun:" << forceCMakeRun
  109                               << " - forceConfiguration:" << forceInitialConfiguration
  110                               << " - forceExtraConfiguration:" << forceExtraConfiguration;
  111     startState();
  112 
  113     const QStringList args = (forceInitialConfiguration ? m_parameters.initialCMakeArguments
  114                                                         : QStringList())
  115                              + (forceExtraConfiguration ? m_parameters.extraCMakeArguments
  116                                                         : QStringList());
  117     qCDebug(cmakeFileApiMode) << "Parameters request these CMake arguments:" << args;
  118 
  119     const QFileInfo replyFi = FileApiParser::scanForCMakeReplyFile(m_parameters.workDirectory);
  120     // Only need to update when one of the following conditions is met:
  121     //  * The user forces the cmake run,
  122     //  * The user provided arguments,
  123     //  * There is no reply file,
  124     //  * One of the cmakefiles is newer than the replyFile and the user asked
  125     //    for creator to run CMake as needed,
  126     //  * A query file is newer than the reply file
  127     const bool hasArguments = !args.isEmpty();
  128     const bool replyFileMissing = !replyFi.exists();
  129     const bool cmakeFilesChanged = m_parameters.cmakeTool() && m_parameters.cmakeTool()->isAutoRun()
  130                                    && anyOf(m_cmakeFiles, [&replyFi](const FilePath &f) {
  131                                           return f.toFileInfo().lastModified()
  132                                                  > replyFi.lastModified();
  133                                       });
  134     const bool queryFileChanged = anyOf(FileApiParser::cmakeQueryFilePaths(
  135                                             m_parameters.workDirectory),
  136                                         [&replyFi](const QString &qf) {
  137                                             return QFileInfo(qf).lastModified()
  138                                                    > replyFi.lastModified();
  139                                         });
  140 
  141     const bool mustUpdate = forceCMakeRun || hasArguments || replyFileMissing || cmakeFilesChanged
  142                             || queryFileChanged;
  143     qCDebug(cmakeFileApiMode) << QString("Do I need to run CMake? %1 "
  144                                          "(force: %2 | args: %3 | missing reply: %4 | "
  145                                          "cmakeFilesChanged: %5 | "
  146                                          "queryFileChanged: %6)")
  147                                      .arg(mustUpdate)
  148                                      .arg(forceCMakeRun)
  149                                      .arg(hasArguments)
  150                                      .arg(replyFileMissing)
  151                                      .arg(cmakeFilesChanged)
  152                                      .arg(queryFileChanged);
  153 
  154     if (mustUpdate) {
  155         qCDebug(cmakeFileApiMode) << QString("FileApiReader: Starting CMake with \"%1\".")
  156                                          .arg(args.join("\", \""));
  157         startCMakeState(args);
  158     } else {
  159         endState(replyFi);
  160     }
  161 }
  162 
  163 void FileApiReader::stop()
  164 {
  165     if (m_cmakeProcess)
  166         disconnect(m_cmakeProcess.get(), nullptr, this, nullptr);
  167     m_cmakeProcess.reset();
  168 
  169     if (m_future) {
  170         m_future->cancel();
  171         m_future->waitForFinished();
  172     }
  173     m_future = {};
  174     m_isParsing = false;
  175 }
  176 
  177 bool FileApiReader::isParsing() const
  178 {
  179     return m_isParsing;
  180 }
  181 
  182 QSet<FilePath> FileApiReader::projectFilesToWatch() const
  183 {
  184     return m_cmakeFiles;
  185 }
  186 
  187 QList<CMakeBuildTarget> FileApiReader::takeBuildTargets(QString &errorMessage){
  188     Q_UNUSED(errorMessage)
  189 
  190     auto result = std::move(m_buildTargets);
  191     m_buildTargets.clear();
  192     return result;
  193 }
  194 
  195 CMakeConfig FileApiReader::takeParsedConfiguration(QString &errorMessage)
  196 {
  197     if (m_lastCMakeExitCode != 0)
  198         errorMessage = tr("CMake returned error code: %1").arg(m_lastCMakeExitCode);
  199 
  200     CMakeConfig cache = m_cache;
  201     m_cache.clear();
  202     return cache;
  203 }
  204 
  205 QString FileApiReader::ctestPath() const
  206 {
  207     // if we failed to run cmake we should not offer ctest information either
  208     return m_lastCMakeExitCode == 0 ? m_ctestPath : QString();
  209 }
  210 
  211 bool FileApiReader::isMultiConfig() const
  212 {
  213     return m_isMultiConfig;
  214 }
  215 
  216 bool FileApiReader::usesAllCapsTargets() const
  217 {
  218     return m_usesAllCapsTargets;
  219 }
  220 
  221 std::unique_ptr<CMakeProjectNode> FileApiReader::generateProjectTree(
  222     const QList<const FileNode *> &allFiles, QString &errorMessage, bool includeHeaderNodes)
  223 {
  224     Q_UNUSED(errorMessage)
  225 
  226     if (includeHeaderNodes) {
  227         addHeaderNodes(m_rootProjectNode.get(), m_knownHeaders, allFiles);
  228     }
  229     addFileSystemNodes(m_rootProjectNode.get(), allFiles);
  230     return std::move(m_rootProjectNode);
  231 }
  232 
  233 RawProjectParts FileApiReader::createRawProjectParts(QString &errorMessage)
  234 {
  235     Q_UNUSED(errorMessage)
  236 
  237     RawProjectParts result = std::move(m_projectParts);
  238     m_projectParts.clear();
  239     return result;
  240 }
  241 
  242 void FileApiReader::startState()
  243 {
  244     qCDebug(cmakeFileApiMode) << "FileApiReader: START STATE.";
  245     QTC_ASSERT(!m_isParsing, return );
  246     QTC_ASSERT(!m_future.has_value(), return );
  247 
  248     m_isParsing = true;
  249 
  250     qCDebug(cmakeFileApiMode) << "FileApiReader: CONFIGURATION STARTED SIGNAL";
  251     emit configurationStarted();
  252 }
  253 
  254 void FileApiReader::endState(const QFileInfo &replyFi)
  255 {
  256     qCDebug(cmakeFileApiMode) << "FileApiReader: END STATE.";
  257     QTC_ASSERT(m_isParsing, return );
  258     QTC_ASSERT(!m_future.has_value(), return );
  259 
  260     const FilePath sourceDirectory = m_parameters.sourceDirectory;
  261     const FilePath buildDirectory = m_parameters.workDirectory;
  262     const FilePath topCmakeFile = m_cmakeFiles.size() == 1 ? *m_cmakeFiles.begin() : FilePath{};
  263     const QString cmakeBuildType = m_parameters.cmakeBuildType == "Build" ? "" : m_parameters.cmakeBuildType;
  264 
  265     m_lastReplyTimestamp = replyFi.lastModified();
  266 
  267     m_future = runAsync(ProjectExplorerPlugin::sharedThreadPool(),
  268                         [replyFi, sourceDirectory, buildDirectory, topCmakeFile, cmakeBuildType](
  269                             QFutureInterface<std::shared_ptr<FileApiQtcData>> &fi) {
  270                             auto result = std::make_shared<FileApiQtcData>();
  271                             FileApiData data = FileApiParser::parseData(fi,
  272                                                                         replyFi,
  273                                                                         cmakeBuildType,
  274                                                                         result->errorMessage);
  275                             if (!result->errorMessage.isEmpty()) {
  276                                 *result = generateFallbackData(topCmakeFile,
  277                                                                sourceDirectory,
  278                                                                buildDirectory,
  279                                                                result->errorMessage);
  280                             } else {
  281                                 *result = extractData(data, sourceDirectory, buildDirectory);
  282                             }
  283                             if (!result->errorMessage.isEmpty()) {
  284                                 qWarning() << result->errorMessage;
  285                             }
  286 
  287                             fi.reportResult(result);
  288                         });
  289     onResultReady(m_future.value(),
  290                   this,
  291                   [this, topCmakeFile, sourceDirectory, buildDirectory](
  292                       const std::shared_ptr<FileApiQtcData> &value) {
  293                       m_isParsing = false;
  294                       m_cache = std::move(value->cache);
  295                       m_cmakeFiles = std::move(value->cmakeFiles);
  296                       m_buildTargets = std::move(value->buildTargets);
  297                       m_projectParts = std::move(value->projectParts);
  298                       m_rootProjectNode = std::move(value->rootProjectNode);
  299                       m_knownHeaders = std::move(value->knownHeaders);
  300                       m_ctestPath = std::move(value->ctestPath);
  301                       m_isMultiConfig = std::move(value->isMultiConfig);
  302                       m_usesAllCapsTargets = std::move(value->usesAllCapsTargets);
  303 
  304                       if (value->errorMessage.isEmpty()) {
  305                           emit this->dataAvailable();
  306                       } else {
  307                           emit this->errorOccurred(value->errorMessage);
  308                       }
  309                       m_future = {};
  310                   });
  311 }
  312 
  313 void FileApiReader::makeBackupConfiguration(bool store)
  314 {
  315     FilePath reply = m_parameters.workDirectory.pathAppended(".cmake/api/v1/reply");
  316     FilePath replyPrev = m_parameters.workDirectory.pathAppended(".cmake/api/v1/reply.prev");
  317     if (!store)
  318         std::swap(reply, replyPrev);
  319 
  320     if (reply.exists()) {
  321         if (replyPrev.exists())
  322             FileUtils::removeRecursively(replyPrev);
  323         QDir dir;
  324         if (!dir.rename(reply.toString(), replyPrev.toString()))
  325             Core::MessageManager::writeFlashing(tr("Failed to rename %1 to %2.")
  326                                                 .arg(reply.toString(), replyPrev.toString()));
  327 
  328     }
  329 
  330     FilePath cmakeCacheTxt = m_parameters.workDirectory.pathAppended("CMakeCache.txt");
  331     FilePath cmakeCacheTxtPrev = m_parameters.workDirectory.pathAppended("CMakeCache.txt.prev");
  332     if (!store)
  333         std::swap(cmakeCacheTxt, cmakeCacheTxtPrev);
  334 
  335     if (cmakeCacheTxt.exists())
  336         if (!FileUtils::copyIfDifferent(cmakeCacheTxt, cmakeCacheTxtPrev))
  337             Core::MessageManager::writeFlashing(tr("Failed to copy %1 to %2.")
  338                                                 .arg(cmakeCacheTxt.toString(), cmakeCacheTxtPrev.toString()));
  339 
  340 }
  341 
  342 void FileApiReader::writeConfigurationIntoBuildDirectory(const QStringList &configurationArguments)
  343 {
  344     const FilePath buildDir = m_parameters.workDirectory;
  345     QTC_ASSERT(buildDir.exists(), return );
  346 
  347     const FilePath settingsFile = buildDir.pathAppended("qtcsettings.cmake");
  348 
  349     QByteArray contents;
  350     contents.append("# This file is managed by Qt Creator, do not edit!\n\n");
  351     contents.append(
  352         transform(CMakeConfigItem::itemsFromArguments(configurationArguments),
  353             [](const CMakeConfigItem &item) {
  354                 return item.toCMakeSetLine(nullptr);
  355             })
  356             .join('\n')
  357             .toUtf8());
  358 
  359     QFile file(settingsFile.toString());
  360     QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return );
  361     file.write(contents);
  362 }
  363 
  364 void FileApiReader::startCMakeState(const QStringList &configurationArguments)
  365 {
  366     qCDebug(cmakeFileApiMode) << "FileApiReader: START CMAKE STATE.";
  367     QTC_ASSERT(!m_cmakeProcess, return );
  368 
  369     m_cmakeProcess = std::make_unique<CMakeProcess>();
  370 
  371     connect(m_cmakeProcess.get(), &CMakeProcess::finished, this, &FileApiReader::cmakeFinishedState);
  372 
  373     qCDebug(cmakeFileApiMode) << ">>>>>> Running cmake with arguments:" << configurationArguments;
  374     // Reset watcher:
  375     m_watcher.removeFiles(m_watcher.files());
  376     m_watcher.removeDirectories(m_watcher.directories());
  377 
  378     makeBackupConfiguration(true);
  379     writeConfigurationIntoBuildDirectory(configurationArguments);
  380     m_cmakeProcess->run(m_parameters, configurationArguments);
  381 }
  382 
  383 void FileApiReader::cmakeFinishedState(int code, QProcess::ExitStatus status)
  384 {
  385     qCDebug(cmakeFileApiMode) << "FileApiReader: CMAKE FINISHED STATE.";
  386 
  387     Q_UNUSED(code)
  388     Q_UNUSED(status)
  389 
  390     m_lastCMakeExitCode = m_cmakeProcess->lastExitCode();
  391     m_cmakeProcess.release()->deleteLater();
  392 
  393     if (m_lastCMakeExitCode != 0)
  394         makeBackupConfiguration(false);
  395 
  396     FileApiParser::setupCMakeFileApi(m_parameters.workDirectory, m_watcher);
  397 
  398     endState(FileApiParser::scanForCMakeReplyFile(m_parameters.workDirectory));
  399 }
  400 
  401 void FileApiReader::replyDirectoryHasChanged(const QString &directory) const
  402 {
  403     if (m_isParsing)
  404         return; // This has been triggered by ourselves, ignore.
  405 
  406     const QFileInfo fi = FileApiParser::scanForCMakeReplyFile(m_parameters.workDirectory);
  407     const QString dir = fi.absolutePath();
  408     if (dir.isEmpty())
  409         return; // CMake started to fill the result dir, but has not written a result file yet
  410     QTC_ASSERT(dir == directory, return);
  411 
  412     if (m_lastReplyTimestamp.isValid() && fi.lastModified() > m_lastReplyTimestamp)
  413         emit dirty();
  414 }
  415 
  416 } // namespace Internal
  417 } // namespace CMakeProjectManager