// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "compilationdbparser.h" #include "compilationdatabaseconstants.h" #include "compilationdatabaseprojectmanagertr.h" #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace CompilationDatabaseProjectManager::Internal { static QStringList jsonObjectFlags(const QJsonObject &object, QSet &flagsCache) { QStringList flags; const QJsonArray arguments = object["arguments"].toArray(); if (arguments.isEmpty()) { flags = splitCommandLine(object["command"].toString(), flagsCache); } else { flags.reserve(arguments.size()); for (const QJsonValue &arg : arguments) { auto flagIt = flagsCache.insert(arg.toString()); flags.append(*flagIt); } } return flags; } static FilePath jsonObjectFilePath(const QJsonObject &object) { const FilePath workingDir = FilePath::fromUserInput(object["directory"].toString()); return workingDir.resolvePath(object["file"].toString()); } static std::vector readJsonObjects(const QByteArray &projectFileContents) { std::vector result; int objectStart = projectFileContents.indexOf('{'); int objectEnd = projectFileContents.indexOf('}', objectStart + 1); QSet flagsCache; while (objectStart >= 0 && objectEnd >= 0) { const QJsonDocument document = QJsonDocument::fromJson( projectFileContents.mid(objectStart, objectEnd - objectStart + 1)); if (document.isNull()) { // The end was found incorrectly, search for the next one. objectEnd = projectFileContents.indexOf('}', objectEnd + 1); continue; } const QJsonObject object = document.object(); const FilePath filePath = jsonObjectFilePath(object); const QStringList flags = filterFromFileName(jsonObjectFlags(object, flagsCache), filePath.fileName()); result.push_back({flags, filePath, FilePath::fromUserInput(object["directory"].toString())}); objectStart = projectFileContents.indexOf('{', objectEnd + 1); objectEnd = projectFileContents.indexOf('}', objectStart + 1); } return result; } static QStringList readExtraFiles(const QString &filePath) { QStringList result; QFile file(filePath); if (file.open(QFile::ReadOnly)) { QTextStream stream(&file); while (!stream.atEnd()) { const QString line = stream.readLine().trimmed(); if (line.isEmpty() || line.startsWith('#')) continue; result.push_back(line); } } return result; } static DbContents parseProject(const QByteArray &projectFileContents, const FilePath &projectFilePath) { DbContents dbContents; dbContents.entries = readJsonObjects(projectFileContents); dbContents.extraFileName = projectFilePath.path() + Constants::COMPILATIONDATABASEPROJECT_FILES_SUFFIX; dbContents.extras = readExtraFiles(dbContents.extraFileName); std::sort(dbContents.entries.begin(), dbContents.entries.end(), [](const DbEntry &lhs, const DbEntry &rhs) { return std::lexicographical_compare(lhs.flags.begin(), lhs.flags.end(), rhs.flags.begin(), rhs.flags.end()); }); return dbContents; } CompilationDbParser::CompilationDbParser(const QString &projectName, const FilePath &projectPath, const FilePath &rootPath, MimeBinaryCache &mimeBinaryCache, BuildSystem::ParseGuard &&guard, QObject *parent) : QObject(parent) , m_projectName(projectName) , m_projectFilePath(projectPath) , m_rootPath(rootPath) , m_mimeBinaryCache(mimeBinaryCache) , m_guard(std::move(guard)) { connect(&m_parserWatcher, &QFutureWatcher::finished, this, [this] { m_dbContents = m_parserWatcher.result(); parserJobFinished(); }); } CompilationDbParser::~CompilationDbParser() { if (m_treeScanner && !m_treeScanner->isFinished()) { auto future = m_treeScanner->future(); future.cancel(); future.waitForFinished(); } } void CompilationDbParser::start() { // Check hash first. Result fileContents = m_projectFilePath.fileContents(); if (!fileContents) { finish(ParseResult::Failure); return; } m_projectFileContents = *std::move(fileContents); const QByteArray newHash = QCryptographicHash::hash(m_projectFileContents, QCryptographicHash::Sha1); if (m_projectFileHash == newHash) { finish(ParseResult::Cached); return; } m_projectFileHash = newHash; m_runningParserJobs = 0; // Thread 1: Scan disk. if (!m_rootPath.isEmpty()) { m_treeScanner = new TreeScanner(this); m_treeScanner->setFilter([this](const MimeType &mimeType, const FilePath &fn) { // Mime checks requires more resources, so keep it last in check list bool isIgnored = fn.startsWith(m_projectFilePath.path() + ".user") || TreeScanner::isWellKnownBinary(mimeType, fn); // Cache mime check result for speed up if (!isIgnored) { if (auto it = m_mimeBinaryCache.get>( [mimeType](const QHash &cache) -> std::optional { const auto cache_it = cache.find(mimeType.name()); if (cache_it != cache.end()) return *cache_it; return {}; })) { isIgnored = *it; } else { isIgnored = TreeScanner::isMimeBinary(mimeType, fn); m_mimeBinaryCache.writeLocked()->insert(mimeType.name(), isIgnored); } } return isIgnored; }); m_treeScanner->setTypeFactory([](const MimeType &mimeType, const FilePath &fn) { return TreeScanner::genericFileType(mimeType, fn); }); m_treeScanner->asyncScanForFiles(m_rootPath); Core::ProgressManager::addTask(m_treeScanner->future(), Tr::tr("Scan \"%1\" project tree").arg(m_projectName), "CompilationDatabase.Scan.Tree"); ++m_runningParserJobs; connect(m_treeScanner, &TreeScanner::finished, this, &CompilationDbParser::parserJobFinished); } // Thread 2: Parse the project file. const auto future = Utils::asyncRun(parseProject, m_projectFileContents, m_projectFilePath); Core::ProgressManager::addTask(future, Tr::tr("Parse \"%1\" project").arg(m_projectName), "CompilationDatabase.Parse"); ++m_runningParserJobs; m_parserWatcher.setFuture(future); Utils::futureSynchronizer()->addFuture(future); } void CompilationDbParser::stop() { disconnect(); m_parserWatcher.disconnect(); m_parserWatcher.cancel(); if (m_treeScanner) { m_treeScanner->disconnect(); m_treeScanner->future().cancel(); } m_guard = {}; deleteLater(); } QList CompilationDbParser::scannedFiles() const { const bool canceled = m_treeScanner->future().isCanceled(); TreeScanner::Result result = m_treeScanner->release(); return !canceled ? result.takeAllFiles() : QList(); } void CompilationDbParser::parserJobFinished() { if (--m_runningParserJobs == 0) finish(ParseResult::Success); } void CompilationDbParser::finish(ParseResult result) { if (result != ParseResult::Failure) m_guard.markAsSuccess(); emit finished(result); deleteLater(); } } // namespace CompilationDatabaseProjectManager::Internal