aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/buildgraph/inputartifactscanner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/corelib/buildgraph/inputartifactscanner.cpp')
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp369
1 files changed, 369 insertions, 0 deletions
diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
new file mode 100644
index 000000000..d5a0fabb6
--- /dev/null
+++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
@@ -0,0 +1,369 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "inputartifactscanner.h"
+
+#include "artifact.h"
+#include "buildgraph.h"
+#include "productbuilddata.h"
+#include "projectbuilddata.h"
+#include "transformer.h"
+
+#include <language/language.h>
+#include <tools/fileinfo.h>
+#include <tools/scannerpluginmanager.h>
+#include <tools/qbsassert.h>
+
+#include <QDir>
+#include <QSet>
+#include <QStringList>
+#include <QVariantMap>
+
+namespace qbs {
+namespace Internal {
+
+InputArtifactScannerContext::InputArtifactScannerContext(ScanResultCache *scanResultCache)
+ : scanResultCache(scanResultCache)
+{
+}
+
+InputArtifactScannerContext::~InputArtifactScannerContext()
+{
+}
+
+static void collectIncludePaths(const QVariantMap &modules, QSet<QString> *collectedPaths)
+{
+ QMapIterator<QString, QVariant> iterator(modules);
+ while (iterator.hasNext()) {
+ iterator.next();
+ if (iterator.key() == "cpp") {
+ QVariant includePathsVariant = iterator .value().toMap().value("includePaths");
+ if (includePathsVariant.isValid())
+ collectedPaths->unite(QSet<QString>::fromList(includePathsVariant.toStringList()));
+ } else {
+ collectIncludePaths(iterator.value().toMap().value("modules").toMap(), collectedPaths);
+ }
+ }
+}
+
+static QStringList collectIncludePaths(const QVariantMap &modules)
+{
+ QSet<QString> collectedPaths;
+
+ collectIncludePaths(modules, &collectedPaths);
+ return QStringList(collectedPaths.toList());
+}
+
+static void resolveWithIncludePath(const QString &includePath,
+ const ScanResultCache::Dependency &dependency, const ResolvedProduct *product,
+ ResolvedDependency *result)
+{
+ QString absDirPath = dependency.dirPath().isEmpty() ? includePath : FileInfo::resolvePath(includePath, dependency.dirPath());
+ if (!dependency.isClean())
+ absDirPath = QDir::cleanPath(absDirPath);
+
+ ResolvedProject *project = product->project.data();
+ FileDependency *fileDependencyArtifact = 0;
+ Artifact *dependencyInProduct = 0;
+ Artifact *dependencyInOtherProduct = 0;
+ foreach (FileResourceBase *lookupResult, project->topLevelProject()
+ ->buildData->lookupFiles(absDirPath, dependency.fileName())) {
+ if ((fileDependencyArtifact = dynamic_cast<FileDependency *>(lookupResult)))
+ continue;
+ Artifact * const foundArtifact = dynamic_cast<Artifact *>(lookupResult);
+ if (foundArtifact->product == product)
+ dependencyInProduct = foundArtifact;
+ else
+ dependencyInOtherProduct = foundArtifact;
+ }
+
+ // prioritize found artifacts
+ if ((result->file = dependencyInProduct)
+ || (result->file = dependencyInOtherProduct)
+ || (result->file = fileDependencyArtifact))
+ {
+ result->filePath = result->file->filePath();
+ return;
+ }
+
+ QString absFilePath = absDirPath + QLatin1Char('/') + dependency.fileName();
+ if (FileInfo::exists(absFilePath))
+ result->filePath = absFilePath;
+}
+
+static bool scanWithScannerPlugin(ScannerPlugin *scannerPlugin,
+ const QString &filePathToBeScanned,
+ ScanResultCache::Result *scanResult)
+{
+ void *scannerHandle = scannerPlugin->open(filePathToBeScanned.utf16(), ScanForDependenciesFlag);
+ if (!scannerHandle)
+ return false;
+ while (true) {
+ int flags = 0;
+ int length = 0;
+ const char *szOutFilePath = scannerPlugin->next(scannerHandle, &length, &flags);
+ if (szOutFilePath == 0)
+ break;
+ QString outFilePath = QString::fromLocal8Bit(szOutFilePath, length);
+ if (outFilePath.isEmpty())
+ continue;
+ bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG);
+ scanResult->deps += ScanResultCache::Dependency(outFilePath, isLocalInclude);
+ }
+ scannerPlugin->close(scannerHandle);
+ scanResult->valid = true;
+ return true;
+}
+
+
+InputArtifactScanner::InputArtifactScanner(Artifact *artifact, InputArtifactScannerContext *ctx,
+ const Logger &logger)
+ : m_artifact(artifact), m_context(ctx), m_newDependencyAdded(false), m_logger(logger)
+{
+}
+
+void InputArtifactScanner::scan()
+{
+ if (m_artifact->inputsScanned)
+ return;
+
+ m_artifact->inputsScanned = true;
+
+ // clear file dependencies; they will be regenerated
+ m_artifact->fileDependencies.clear();
+
+ // Remove all connections to children that were added by the dependency scanner.
+ // They will be regenerated.
+ foreach (Artifact *dependency, m_artifact->childrenAddedByScanner)
+ disconnect(m_artifact, dependency, m_logger);
+
+ ArtifactList::const_iterator it = m_artifact->transformer->inputs.begin();
+ for (; it != m_artifact->transformer->inputs.end(); ++it) {
+ Artifact *inputArtifact = *it;
+ QStringList includePaths;
+ bool mustCollectIncludePaths = false;
+
+ QSet<ScannerPlugin *> scanners;
+ foreach (const FileTag &fileTag, inputArtifact->fileTags) {
+ foreach (ScannerPlugin *scanner, ScannerPluginManager::scannersForFileTag(fileTag)) {
+ scanners += scanner;
+ if (scanner->flags & ScannerUsesCppIncludePaths)
+ mustCollectIncludePaths = true;
+ }
+ }
+
+ InputArtifactScannerContext::CacheItem &cacheItem = m_context->cache[inputArtifact->properties];
+ if (mustCollectIncludePaths) {
+ const bool cacheHit = cacheItem.valid;
+ if (cacheHit) {
+ includePaths = cacheItem.includePaths;
+ } else {
+ includePaths = collectIncludePaths(inputArtifact->properties->value().value("modules").toMap());
+ cacheItem.includePaths = includePaths;
+ cacheItem.valid = true;
+ }
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace()
+ << "[DEPSCAN] include paths (cache " << (cacheHit ? "hit)" : "miss)");
+ foreach (const QString &s, includePaths)
+ m_logger.qbsTrace() << " " << s;
+ }
+ }
+
+ const QStringList emptyIncludePaths;
+ foreach (ScannerPlugin *scanner, scanners) {
+ scanForFileDependencies(scanner,
+ (scanner->flags & ScannerUsesCppIncludePaths)
+ ? includePaths : emptyIncludePaths,
+ inputArtifact,
+ cacheItem.resolvedDependenciesCache[scanner]);
+ }
+ }
+}
+
+void InputArtifactScanner::scanForFileDependencies(ScannerPlugin *scannerPlugin,
+ const QStringList &includePaths, Artifact *inputArtifact, InputArtifactScannerContext::ResolvedDependenciesCache &resolvedDependenciesCache)
+{
+ if (m_logger.debugEnabled()) {
+ m_logger.qbsDebug() << QString::fromLocal8Bit("scanning %1 [%2]\n from %3")
+ .arg(inputArtifact->filePath()).arg(scannerPlugin->fileTag)
+ .arg(m_artifact->filePath());
+ }
+
+ QSet<QString> visitedFilePaths;
+ QStringList filePathsToScan;
+ filePathsToScan.append(inputArtifact->filePath());
+ QStringList * const filePathsToScanPtr =
+ (scannerPlugin->flags & ScannerRecursiveDependencies) ? &filePathsToScan : 0;
+
+ while (!filePathsToScan.isEmpty()) {
+ const QString filePathToBeScanned = filePathsToScan.takeFirst();
+ if (visitedFilePaths.contains(filePathToBeScanned))
+ continue;
+ visitedFilePaths.insert(filePathToBeScanned);
+
+ ScanResultCache::Result scanResult = m_context->scanResultCache->value(filePathToBeScanned);
+ if (!scanResult.valid) {
+ bool successfulScan = scanWithScannerPlugin(scannerPlugin, filePathToBeScanned, &scanResult);
+ if (!successfulScan)
+ continue;
+ m_context->scanResultCache->insert(filePathToBeScanned, scanResult);
+ }
+
+ resolveScanResultDependencies(includePaths, inputArtifact, scanResult, filePathToBeScanned,
+ filePathsToScanPtr, resolvedDependenciesCache);
+ }
+}
+
+void InputArtifactScanner::resolveScanResultDependencies(const QStringList &includePaths,
+ const Artifact *inputArtifact, const ScanResultCache::Result &scanResult,
+ const QString &filePathToBeScanned, QStringList *filePathsToScan, InputArtifactScannerContext::ResolvedDependenciesCache &resolvedDependenciesCache)
+{
+ QString baseDirOfInFilePath;
+ foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) {
+ const QString &dependencyFilePath = dependency.filePath();
+ ResolvedDependency pristineResolvedDependency;
+ ResolvedDependency *resolvedDependency = &pristineResolvedDependency;
+ InputArtifactScannerContext::ResolvedDependencyCacheItem *cachedResolvedDependencyItem = 0;
+
+ if (FileInfo::isAbsolute(dependencyFilePath)) {
+ resolvedDependency->filePath = dependencyFilePath;
+ goto resolved;
+ }
+
+ if (dependency.isLocal()) {
+ // try base directory of source file
+ if (baseDirOfInFilePath.isNull())
+ baseDirOfInFilePath = FileInfo::path(filePathToBeScanned);
+ resolveWithIncludePath(baseDirOfInFilePath, dependency, inputArtifact->product.data(),
+ resolvedDependency);
+ if (resolvedDependency->isValid())
+ goto resolved;
+ }
+
+ cachedResolvedDependencyItem = &resolvedDependenciesCache[dependency.fileName()][dependency.dirPath()];
+ resolvedDependency = &cachedResolvedDependencyItem->resolvedDependency;
+ if (cachedResolvedDependencyItem->valid) {
+// qDebug() << "RESCACHE HIT" << dependency.filePath();
+ if (resolvedDependency->filePath.isEmpty())
+ goto unresolved;
+ goto resolved;
+ }
+// qDebug() << "RESCACHE MISS";
+ cachedResolvedDependencyItem->valid = true;
+
+ // try include paths
+ foreach (const QString &includePath, includePaths) {
+ resolveWithIncludePath(includePath, dependency, inputArtifact->product.data(),
+ resolvedDependency);
+ if (resolvedDependency->isValid())
+ goto resolved;
+ }
+
+unresolved:
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] unresolved '%1'")
+ .arg(dependencyFilePath);
+ }
+ continue;
+
+resolved:
+ // Do not scan artifacts that are being built. Otherwise we might read an incomplete
+ // file or conflict with the writing process.
+ if (filePathsToScan) {
+ Artifact *artifactDependency = dynamic_cast<Artifact *>(resolvedDependency->file);
+ if (!artifactDependency || artifactDependency->buildState != Artifact::Building)
+ filePathsToScan->append(resolvedDependency->filePath);
+ }
+ handleDependency(*resolvedDependency);
+ }
+}
+
+void InputArtifactScanner::handleDependency(ResolvedDependency &dependency)
+{
+ const ResolvedProductPtr product = m_artifact->product;
+ bool insertIntoProduct = true;
+ QBS_CHECK(m_artifact->artifactType == Artifact::Generated);
+ QBS_CHECK(product);
+
+ Artifact *artifactDependency = dynamic_cast<Artifact *>(dependency.file);
+ FileDependency *fileDependency
+ = artifactDependency ? 0 : dynamic_cast<FileDependency *>(dependency.file);
+
+ if (!dependency.file) {
+ // The dependency is an existing file but does not exist in the build graph.
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] + '%1'")
+ .arg(dependency.filePath);
+ }
+ fileDependency = new FileDependency();
+ dependency.file = fileDependency;
+ fileDependency->setFilePath(dependency.filePath);
+ product->topLevelProject()->buildData->insertFileDependency(fileDependency);
+ } else if (fileDependency) {
+ // The dependency exists in the project's list of file dependencies.
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] ok in deps '%1'")
+ .arg(dependency.filePath);
+ }
+ } else if (artifactDependency->product == product) {
+ // The dependency is in our product.
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] ok in product '%1'")
+ .arg(dependency.filePath);
+ }
+ insertIntoProduct = false;
+ } else {
+ // The dependency is in some other product.
+ ResolvedProduct * const otherProduct = artifactDependency->product;
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] found in product '%1': '%2'")
+ .arg(otherProduct->name, dependency.filePath);
+ }
+ insertIntoProduct = false;
+ }
+
+ if (m_artifact == dependency.file)
+ return;
+
+ if (fileDependency) {
+ m_artifact->fileDependencies.insert(fileDependency);
+ } else {
+ if (m_artifact->children.contains(artifactDependency))
+ return;
+ if (insertIntoProduct && !product->buildData->artifacts.contains(artifactDependency))
+ insertArtifact(product, artifactDependency, m_logger);
+ safeConnect(m_artifact, artifactDependency, m_logger);
+ m_artifact->childrenAddedByScanner += artifactDependency;
+ m_newDependencyAdded = true;
+ }
+}
+
+} // namespace Internal
+} // namespace qbs