/**************************************************************************** ** ** 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 "depscanner.h" #include "rulesevaluationcontext.h" #include #include #include #include #include #include #include #include #include namespace qbs { namespace Internal { InputArtifactScannerContext::InputArtifactScannerContext(ScanResultCache *scanResultCache) : scanResultCache(scanResultCache) { } InputArtifactScannerContext::~InputArtifactScannerContext() { } 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(lookupResult))) continue; Artifact * const foundArtifact = dynamic_cast(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 void resolveAbsolutePath(const ScanResultCache::Dependency &dependency, const ResolvedProduct *product, ResolvedDependency *result) { QString absDirPath = 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(lookupResult))) continue; Artifact * const foundArtifact = dynamic_cast(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; } if (FileInfo::exists(dependency.filePath())) result->filePath = dependency.filePath(); } static void scanWithScannerPlugin(DependencyScanner *scanner, Artifact *artifactToBeScanned, ScanResultCache::Result *scanResult) { QStringList dependencies = scanner->collectDependencies(artifactToBeScanned); foreach (const QString &s, dependencies) scanResult->deps += ScanResultCache::Dependency(s); scanResult->valid = 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); ArtifactSet::const_iterator it = m_artifact->transformer->inputs.begin(); for (; it != m_artifact->transformer->inputs.end(); ++it) { Artifact *inputArtifact = *it; scanForFileDependencies(inputArtifact); } } void InputArtifactScanner::scanForFileDependencies(Artifact *inputArtifact) { InputArtifactScannerContext::CacheItem &cacheItem = m_context->cache[inputArtifact->properties]; QSet visitedFilePaths; QList artifactsToScan; artifactsToScan.append(inputArtifact); QSet scanners; TopLevelProject *project = inputArtifact->product->topLevelProject(); ScriptEngine* engine = project->buildData->evaluationContext->engine(); while (!artifactsToScan.isEmpty()) { Artifact* artifactToBeScanned = artifactsToScan.takeFirst(); const QString &filePathToBeScanned = artifactToBeScanned->filePath(); if (visitedFilePaths.contains(filePathToBeScanned)) continue; visitedFilePaths.insert(filePathToBeScanned); scanners.clear(); ResolvedProduct* product = artifactToBeScanned->product.data(); QHash &scannerCache = m_context->scannersCache[product]; foreach (const FileTag &fileTag, artifactToBeScanned->fileTags) { InputArtifactScannerContext::DependencyScannerCacheItem &cache = scannerCache[fileTag]; if (!cache.valid) { cache.valid = true; foreach (ScannerPlugin *scanner, ScannerPluginManager::scannersForFileTag(fileTag)) { PluginDependencyScanner *pluginScanner = new PluginDependencyScanner(scanner); cache.scanners += DependencyScannerPtr(pluginScanner); } foreach (const ResolvedScannerConstPtr &scanner, product->scanners) { if (scanner->inputs.contains(fileTag)) { UserDependencyScanner *userScanner = new UserDependencyScanner(scanner, m_logger, engine); cache.scanners += DependencyScannerPtr(userScanner); break; } } } foreach (const DependencyScannerPtr &scanner, cache.scanners) { scanners += scanner.data(); } } foreach (DependencyScanner *scanner, scanners) { scanForScannerFileDependencies(scanner, inputArtifact, artifactToBeScanned, scanner->recursive() ? &artifactsToScan : 0, cacheItem[scanner->key()]); } } } void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *scanner, Artifact *inputArtifact, Artifact* artifactToBeScanned, QList *artifactsToScan, InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache) { const bool cacheHit = cache.valid; if (!cacheHit) { cache.valid = true; cache.searchPaths = scanner->collectSearchPaths(inputArtifact); } if (m_logger.traceEnabled()) { m_logger.qbsTrace() << "[DEPSCAN] include paths (cache " << (cacheHit ? "hit)" : "miss)"); foreach (const QString &s, cache.searchPaths) m_logger.qbsTrace() << " " << s; } if (m_logger.debugEnabled()) { QString tags = scanner->fileTags().toStringList().join(QString::fromLocal8Bit(",")); m_logger.qbsDebug() << QString::fromLocal8Bit("scanning %1 [%2]\n from %3") .arg(inputArtifact->filePath()).arg(tags) .arg(m_artifact->filePath()); } const QString &filePathToBeScanned = artifactToBeScanned->filePath(); ScanResultCache::Result scanResult = m_context->scanResultCache->value(scanner->key(), filePathToBeScanned); if (!scanResult.valid) { try { scanWithScannerPlugin(scanner, artifactToBeScanned, &scanResult); } catch (const ErrorInfo &error) { m_logger.printWarning(error); return; } m_context->scanResultCache->insert(scanner->key(), filePathToBeScanned, scanResult); } resolveScanResultDependencies(inputArtifact, scanResult, artifactsToScan, cache); } void InputArtifactScanner::resolveScanResultDependencies(const Artifact *inputArtifact, const ScanResultCache::Result &scanResult, QList *artifactsToScan, InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache) { foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) { const QString &dependencyFilePath = dependency.filePath(); ResolvedDependency pristineResolvedDependency; ResolvedDependency *resolvedDependency = &pristineResolvedDependency; InputArtifactScannerContext::ResolvedDependencyCacheItem *cachedResolvedDependencyItem = 0; cachedResolvedDependencyItem = &cache.resolvedDependenciesCache[dependency.dirPath()][dependency.fileName()]; resolvedDependency = &cachedResolvedDependencyItem->resolvedDependency; if (cachedResolvedDependencyItem->valid) { if (resolvedDependency->filePath.isEmpty()) goto unresolved; goto resolved; } cachedResolvedDependencyItem->valid = true; if (FileInfo::isAbsolute(dependencyFilePath)) { resolveAbsolutePath(dependency, inputArtifact->product.data(), resolvedDependency); goto resolved; } // try include paths foreach (const QString &includePath, cache.searchPaths) { resolveWithIncludePath(includePath, dependency, inputArtifact->product.data(), resolvedDependency); if (resolvedDependency->isValid()) goto resolved; } unresolved: if (m_logger.traceEnabled()) { m_logger.qbsWarning() << 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 (artifactsToScan) { Artifact *artifactDependency = dynamic_cast(resolvedDependency->file); if (artifactDependency && artifactDependency->buildState != BuildGraphNode::Building) artifactsToScan->append(artifactDependency); } 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(dependency.file); FileDependency *fileDependency = artifactDependency ? 0 : dynamic_cast(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->nodes.contains(artifactDependency)) insertArtifact(product, artifactDependency, m_logger); safeConnect(m_artifact, artifactDependency, m_logger); m_artifact->childrenAddedByScanner += artifactDependency; m_newDependencyAdded = true; } } InputArtifactScannerContext::DependencyScannerCacheItem::DependencyScannerCacheItem() : valid(false) { } InputArtifactScannerContext::DependencyScannerCacheItem::~DependencyScannerCacheItem() { } } // namespace Internal } // namespace qbs