diff options
author | Joerg Bornemann <joerg.bornemann@digia.com> | 2014-02-10 18:08:01 +0100 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@digia.com> | 2014-02-13 15:33:27 +0100 |
commit | e73f60919079fc7cb0f0ad6d50b1a364c7b0d2a6 (patch) | |
tree | 3ce15f1f9b336063d591999ce325ce28a6e21320 | |
parent | a3634a6bbb193c47cdec887a6b29356c979961aa (diff) |
support transformers with an unknown number of outputs
To support different types of nodes in the build graph, we introduce
the base class BuildGraphNode. Artifact now derives from BuildGraphNode.
A RuleNode class is introduced that represents a rule in the build graph.
Rules are applied in the build phase and not in a pre-build phase
anymore.
The handling of moc has been revisited. The fixed automoc pre-build
phase is no more.
This is the squashed merge of a feature branch.
Task-number: QBS-370
Change-Id: If27cdc51cba8c9542e4282c2caa456faa723aeff
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
68 files changed, 2699 insertions, 1232 deletions
diff --git a/doc/reference/items/rule.qdoc b/doc/reference/items/rule.qdoc index 35fc2aa5e..05fc22643 100644 --- a/doc/reference/items/rule.qdoc +++ b/doc/reference/items/rule.qdoc @@ -116,6 +116,13 @@ Unlike \a{inputs}, the property \a{auxiliaryInputs} has no effect on the content of the \a{inputs} variable in the \a{prepare} script. \row + \li excludedAuxiliaryInputs + \li string list + \li undefined + \li A list of file tags. Connections to rules that produce these file tags are prevented. + This property has no effect on the content of the \a{inputs} variable in the \a{prepare} + script. + \row \li usings \li string list \li undefined @@ -131,6 +138,28 @@ script. Also, each output artifact of this rule will be dependent on those artifacts. \row + \li outputArtifacts + \li array of objects + \li undefined + \li An array of output artifacts, specified as JavaScript objects. + Example: + \code + outputArtifacts: [{filePath: "myfile.txt", fileTags: ["foo", "bar"]}] + \endcode + For a description of the possible properties, see the documentation of the Artifact + item. + Output artifacts can be specified either by \c{Rule.outputArtifacts} or by \c{Artifact} + items. Use \c{Rule.outputArtifacts} if the set of outputs is not fixed but dependent on + the input's content. + The user may set the property \c{explicitlyDependsOn} on artifact objects, which is + similar to \c{Rule.explicitlyDependsOn}. + \row + \li outputFileTags + \li string list + \li undefined + \li If output artifacts are specified by \c{Rule.outputArtifacts}, then + \c{Rule.outputFileTags} must be a list of file tags the rule potentially produces. + \row \li condition \li bool \li true diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index b9e9b4583..99b2eeac0 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -438,18 +438,10 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group groupContext.resolvedGroup->files << artifact; } if (groupContext.resolvedProduct->enabled) { - ArtifactsPerFileTagMap artifactsPerFileTag; foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) { Artifact * const artifact = createArtifact(groupContext.resolvedProduct, sa, logger); - foreach (const FileTag &ft, artifact->fileTags) - artifactsPerFileTag[ft] += artifact; + groupContext.resolvedProduct->registerAddedArtifact(artifact); } - RulesEvaluationContextPtr &evalContext - = groupContext.resolvedProduct->topLevelProject()->buildData->evaluationContext; - evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger)); - RulesApplicator(groupContext.resolvedProduct, artifactsPerFileTag, logger).applyAllRules(); - addTargetArtifacts(groupContext.resolvedProduct, artifactsPerFileTag, logger); - evalContext.clear(); } doSanityChecks(internalProject, logger); groupContext.currentGroup.d->filePaths << filesContext.absoluteFilePaths; @@ -521,14 +513,10 @@ void ProjectPrivate::removeFilesFromBuildGraph(const ResolvedProductConstPtr &pr QBS_CHECK(internalProject->buildData); foreach (const SourceArtifactPtr &sa, files) { Artifact * const artifact = lookupArtifact(product, sa->absoluteFilePath); - QBS_CHECK(artifact); - internalProject->buildData->removeArtifactAndExclusiveDependents(artifact, logger); + if (artifact) // Can be null if the executor has not yet applied the respective rule. + internalProject->buildData->removeArtifactAndExclusiveDependents(artifact, logger); + delete artifact; } - RulesEvaluationContextPtr &evalContext - = internalProject->buildData->evaluationContext; - evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger)); - internalProject->buildData->updateNodesThatMustGetNewTransformer(logger); - evalContext.clear(); } static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation &changeLocation, @@ -611,7 +599,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, product.d->groups << createGroupDataFromGroup(resolvedGroup); if (resolvedProduct->enabled) { QBS_CHECK(resolvedProduct->buildData); - foreach (const Artifact * const a, resolvedProduct->buildData->targetArtifacts) { + foreach (const Artifact * const a, resolvedProduct->buildData->targetArtifacts()) { TargetArtifact ta; ta.d->filePath = a->filePath(); ta.d->fileTags = a->fileTags.toStringList(); @@ -864,7 +852,8 @@ QList<InstallableFile> Project::installableFilesForProduct(const ProductData &pr } if (internalProduct->enabled) { QBS_CHECK(internalProduct->buildData); - foreach (const Artifact * const artifact, internalProduct->buildData->artifacts) { + foreach (const Artifact * const artifact, + ArtifactSet::fromNodeSet(internalProduct->buildData->nodes)) { if (artifact->artifactType == Artifact::SourceFile) continue; InstallableFile f; diff --git a/src/lib/corelib/buildgraph/artifact.cpp b/src/lib/corelib/buildgraph/artifact.cpp index c5203e5ea..d9f442494 100644 --- a/src/lib/corelib/buildgraph/artifact.cpp +++ b/src/lib/corelib/buildgraph/artifact.cpp @@ -30,7 +30,7 @@ #include "artifact.h" #include "transformer.h" - +#include "buildgraphvisitor.h" #include <language/propertymapinternal.h> #include <tools/fileinfo.h> #include <tools/persistence.h> @@ -62,6 +62,19 @@ Artifact::Artifact() Artifact::~Artifact() { + foreach (Artifact *p, parentArtifacts()) + p->childrenAddedByScanner.remove(this); +} + +void Artifact::accept(BuildGraphVisitor *visitor) +{ + if (visitor->visit(this)) + acceptChildren(visitor); +} + +QString Artifact::toString() const +{ + return QLatin1String("ARTIFACT ") + filePath(); } void Artifact::initialize() @@ -71,15 +84,35 @@ void Artifact::initialize() inputsScanned = false; timestampRetrieved = false; alwaysUpdated = true; + oldDataPossiblyPresent = true; +} + +ArtifactSet Artifact::parentArtifacts() const +{ + return ArtifactSet::fromNodeSet(parents); +} + +ArtifactSet Artifact::childArtifacts() const +{ + return ArtifactSet::fromNodeSet(children); +} + +void Artifact::onChildDisconnected(BuildGraphNode *child) +{ + Artifact *childArtifact = dynamic_cast<Artifact *>(child); + if (!childArtifact) + return; + childrenAddedByScanner.remove(childArtifact); } void Artifact::load(PersistentPool &pool) { FileResourceBase::load(pool); - pool.loadContainer(children); + BuildGraphNode::load(pool); + children.load(pool); // restore parents of the loaded children - for (ArtifactSet::const_iterator it = children.constBegin(); it != children.constEnd(); ++it) + for (NodeSet::const_iterator it = children.constBegin(); it != children.constEnd(); ++it) (*it)->parents.insert(this); pool.loadContainer(childrenAddedByScanner); @@ -90,16 +123,18 @@ void Artifact::load(PersistentPool &pool) pool.stream() >> fileTags >> artifactType - >> autoMocTimestamp >> c; alwaysUpdated = c; + pool.stream() >> c; + oldDataPossiblyPresent = c; } void Artifact::store(PersistentPool &pool) const { FileResourceBase::store(pool); + BuildGraphNode::store(pool); // Do not store parents to avoid recursion. - pool.storeContainer(children); + children.store(pool); pool.storeContainer(childrenAddedByScanner); pool.storeContainer(fileDependencies); pool.store(properties); @@ -107,8 +142,8 @@ void Artifact::store(PersistentPool &pool) const pool.stream() << fileTags << artifactType - << autoMocTimestamp - << static_cast<unsigned char>(alwaysUpdated); + << static_cast<unsigned char>(alwaysUpdated) + << static_cast<unsigned char>(oldDataPossiblyPresent); } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/artifact.h b/src/lib/corelib/buildgraph/artifact.h index 93cfeec52..ca62daea7 100644 --- a/src/lib/corelib/buildgraph/artifact.h +++ b/src/lib/corelib/buildgraph/artifact.h @@ -32,12 +32,10 @@ #include "artifactset.h" #include "filedependency.h" +#include "buildgraphnode.h" #include "forward_decls.h" #include <language/filetags.h> -#include <language/forward_decls.h> #include <tools/filetime.h> -#include <tools/persistentobject.h> -#include <tools/weakpointer.h> #include <QSet> #include <QString> @@ -54,18 +52,19 @@ class Logger; * * */ -class Artifact : public FileResourceBase +class Artifact : public FileResourceBase, public BuildGraphNode { public: Artifact(); ~Artifact(); - ArtifactSet parents; - ArtifactSet children; + Type type() const { return ArtifactNodeType; } + void accept(BuildGraphVisitor *visitor); + QString toString() const; + ArtifactSet childrenAddedByScanner; QSet<FileDependency *> fileDependencies; FileTags fileTags; - WeakPointer<ResolvedProduct> product; TransformerPtr transformer; PropertyMapPtr properties; @@ -76,22 +75,16 @@ public: Generated = 4 }; - enum BuildState - { - Untouched = 0, - Buildable, - Building, - Built - }; - ArtifactType artifactType; - FileTime autoMocTimestamp; - BuildState buildState; // Do not serialize. Will be refreshed for every build. bool inputsScanned : 1; // Do not serialize. Will be refreshed for every build. bool timestampRetrieved : 1; // Do not serialize. Will be refreshed for every build. bool alwaysUpdated : 1; + bool oldDataPossiblyPresent : 1; void initialize(); + ArtifactSet parentArtifacts() const; + ArtifactSet childArtifacts() const; + void onChildDisconnected(BuildGraphNode *child); private: void load(PersistentPool &pool); @@ -113,16 +106,16 @@ inline QString toString(Artifact::ArtifactType t) } // debugging helper -inline QString toString(Artifact::BuildState s) +inline QString toString(BuildGraphNode::BuildState s) { switch (s) { - case Artifact::Untouched: + case BuildGraphNode::Untouched: return QLatin1String("Untouched"); - case Artifact::Buildable: + case BuildGraphNode::Buildable: return QLatin1String("Buildable"); - case Artifact::Building: + case BuildGraphNode::Building: return QLatin1String("Building"); - case Artifact::Built: + case BuildGraphNode::Built: return QLatin1String("Built"); default: return QLatin1String("Unknown"); diff --git a/src/lib/corelib/buildgraph/artifactcleaner.cpp b/src/lib/corelib/buildgraph/artifactcleaner.cpp index a1e91cdaf..ad93c1d4a 100644 --- a/src/lib/corelib/buildgraph/artifactcleaner.cpp +++ b/src/lib/corelib/buildgraph/artifactcleaner.cpp @@ -113,7 +113,7 @@ private: if (m_options.cleanType() == CleanOptions::CleanupTemporaries) { QBS_CHECK(artifact->transformer); foreach (Artifact * const sibling, artifact->transformer->outputs) { - if (artifact->product->buildData->targetArtifacts.contains(sibling)) + if (artifact->product->buildData->targetArtifacts().contains(sibling)) return; } } diff --git a/src/lib/corelib/buildgraph/artifactset.cpp b/src/lib/corelib/buildgraph/artifactset.cpp index 921de6ee0..c44f5e4b9 100644 --- a/src/lib/corelib/buildgraph/artifactset.cpp +++ b/src/lib/corelib/buildgraph/artifactset.cpp @@ -28,30 +28,44 @@ ****************************************************************************/ #include "artifactset.h" +#include "artifact.h" namespace qbs { namespace Internal { ArtifactSet::ArtifactSet() -{} +{ +} ArtifactSet::ArtifactSet(const ArtifactSet &other) - : m_data(other.m_data) -{} + : QSet<Artifact *>(other) +{ +} + +ArtifactSet::ArtifactSet(const QSet<Artifact *> &other) + : QSet<Artifact *>(other) +{ +} -ArtifactSet &ArtifactSet::unite(const ArtifactSet &other) +ArtifactSet ArtifactSet::fromNodeSet(const NodeSet &nodes) { - std::set<Artifact *>::const_iterator it = other.m_data.begin(); - for (; it != other.m_data.end(); ++it) - m_data.insert(*it); - return *this; + ArtifactSet result; + result.reserve(nodes.count()); + foreach (BuildGraphNode *node, nodes) { + Artifact *artifact = dynamic_cast<Artifact *>(node); + if (artifact) + result += artifact; + } + return result; } -void ArtifactSet::remove(Artifact *artifact) +ArtifactSet ArtifactSet::fromNodeList(const QList<Artifact *> &lst) { - iterator it = m_data.find(artifact); - if (it != m_data.end()) - m_data.erase(it); + ArtifactSet result; + result.reserve(lst.count()); + for (QList<Artifact *>::const_iterator it = lst.constBegin(); it != lst.end(); ++it) + result.insert(*it); + return result; } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/artifactset.h b/src/lib/corelib/buildgraph/artifactset.h index fccff34d9..092e9e8a2 100644 --- a/src/lib/corelib/buildgraph/artifactset.h +++ b/src/lib/corelib/buildgraph/artifactset.h @@ -30,80 +30,22 @@ #ifndef QBS_ARTIFACTSET_H #define QBS_ARTIFACTSET_H -#include <set> -#include <cstddef> +#include <QSet> namespace qbs { namespace Internal { class Artifact; +class NodeSet; -/** - * Set that holds a bunch of build graph artifacts. - * This is faster than QSet when iterating over the container. - */ -class ArtifactSet +class ArtifactSet : public QSet<Artifact *> { public: ArtifactSet(); ArtifactSet(const ArtifactSet &other); - - ArtifactSet &unite(const ArtifactSet &other); - - typedef std::set<Artifact *>::const_iterator const_iterator; - typedef std::set<Artifact *>::iterator iterator; - typedef Artifact * value_type; - - iterator begin() { return m_data.begin(); } - iterator end() { return m_data.end(); } - const_iterator begin() const { return m_data.begin(); } - const_iterator end() const { return m_data.end(); } - const_iterator constBegin() const { return m_data.begin(); } - const_iterator constEnd() const { return m_data.end(); } - - void insert(Artifact *artifact) - { - m_data.insert(artifact); - } - - void operator +=(Artifact *artifact) - { - insert(artifact); - } - - void remove(Artifact *artifact); - - bool contains(Artifact *artifact) const - { - return m_data.find(artifact) != m_data.end(); - } - - void clear() - { - m_data.clear(); - } - - bool isEmpty() const - { - return m_data.empty(); - } - - int count() const - { - return (int)m_data.size(); - } - - void reserve(int) - { - // no-op - } - - bool operator==(const ArtifactSet &other) const { return m_data == other.m_data; } - bool operator!=(const ArtifactSet &other) const { return !(*this == other); } - - -private: - std::set<Artifact *> m_data; + ArtifactSet(const QSet<Artifact *> &other); + static ArtifactSet fromNodeSet(const NodeSet &nodes); + static ArtifactSet fromNodeList(const QList<Artifact *> &lst); }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/artifactvisitor.cpp b/src/lib/corelib/buildgraph/artifactvisitor.cpp index 3b5203e26..e8f438c9b 100644 --- a/src/lib/corelib/buildgraph/artifactvisitor.cpp +++ b/src/lib/corelib/buildgraph/artifactvisitor.cpp @@ -40,27 +40,26 @@ ArtifactVisitor::ArtifactVisitor(int artifactType) : m_artifactType(artifactType { } -void ArtifactVisitor::visitArtifact(Artifact *artifact) -{ - QBS_CHECK(artifact); - if (m_artifactType & artifact->artifactType) - doVisit(artifact); -} - void ArtifactVisitor::visitProduct(const ResolvedProductConstPtr &product) { if (!product->buildData) return; - foreach (Artifact * const artifact, product->buildData->artifacts) - visitArtifact(artifact); + foreach (BuildGraphNode *node, product->buildData->nodes) + node->accept(this); } void ArtifactVisitor::visitProject(const ResolvedProjectConstPtr &project) { - foreach (const ResolvedProductConstPtr &product, project->products) + foreach (const ResolvedProductConstPtr &product, project->allProducts()) visitProduct(product); - foreach (const ResolvedProjectConstPtr &subProject, project->subProjects) - visitProject(subProject); +} + +bool ArtifactVisitor::visit(Artifact *artifact) +{ + QBS_CHECK(artifact); + if (m_artifactType & artifact->artifactType) + doVisit(artifact); + return false; } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/artifactvisitor.h b/src/lib/corelib/buildgraph/artifactvisitor.h index 0c113ea1b..2cd67a8f8 100644 --- a/src/lib/corelib/buildgraph/artifactvisitor.h +++ b/src/lib/corelib/buildgraph/artifactvisitor.h @@ -31,6 +31,7 @@ #include "forward_decls.h" +#include "buildgraphvisitor.h" #include <language/forward_decls.h> #include <QList> @@ -39,14 +40,14 @@ namespace qbs { namespace Internal { -class ArtifactVisitor +class ArtifactVisitor : public BuildGraphVisitor { public: ArtifactVisitor(int artifactType); - virtual void visitArtifact(Artifact *artifact); - virtual void visitProduct(const ResolvedProductConstPtr &product); - virtual void visitProject(const ResolvedProjectConstPtr &project); + void visitProduct(const ResolvedProductConstPtr &product); + void visitProject(const ResolvedProjectConstPtr &project); + bool visit(Artifact *artifact); private: virtual void doVisit(Artifact *artifact) = 0; diff --git a/src/lib/corelib/buildgraph/automoc.cpp b/src/lib/corelib/buildgraph/automoc.cpp deleted file mode 100644 index 13c24674a..000000000 --- a/src/lib/corelib/buildgraph/automoc.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/**************************************************************************** -** -** 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 "automoc.h" -#include "productbuilddata.h" -#include "projectbuilddata.h" -#include "buildgraph.h" -#include "rulesapplicator.h" -#include "scanresultcache.h" -#include <buildgraph/artifact.h> -#include <buildgraph/transformer.h> -#include <language/language.h> -#include <logging/translator.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/scannerpluginmanager.h> - -namespace qbs { -namespace Internal { - -AutoMoc::AutoMoc(const Logger &logger, QObject *parent) - : QObject(parent) - , m_scanResultCache(0) - , m_logger(logger) -{ -} - -void AutoMoc::setScanResultCache(ScanResultCache *scanResultCache) -{ - m_scanResultCache = scanResultCache; -} - -void AutoMoc::apply(const ResolvedProductPtr &product) -{ - if (cppScanners().isEmpty() || hppScanners().isEmpty()) - throw ErrorInfo(Tr::tr("C++ scanner cannot be loaded.")); - - Artifact *pluginMetaDataFile = 0; - Artifact *pchFile = 0; - QList<QPair<Artifact *, FileType> > artifactsToMoc; - QSet<QString> includedMocCppFiles; - const FileTime currentTime = FileTime::currentTime(); - ArtifactSet::const_iterator it = product->buildData->artifacts.begin(); - for (; it != product->buildData->artifacts.end(); ++it) { - Artifact *artifact = *it; - if (!pchFile || !pluginMetaDataFile) { - foreach (const FileTag &fileTag, artifact->fileTags) { - if (fileTag == "cpp_pch") - pchFile = artifact; - else if (fileTag == "qt_plugin_metadata") - pluginMetaDataFile = artifact; - } - } - - if (!pluginMetaDataFile && artifact->fileTags.contains("qt_plugin_metadata")) { - if (m_logger.debugEnabled()) { - m_logger.qbsDebug() << "[AUTOMOC] found Qt plugin metadata file " - << artifact->filePath(); - } - pluginMetaDataFile = artifact; - } - if (artifact->artifactType != Artifact::SourceFile) - continue; - if (artifact->timestamp() < artifact->autoMocTimestamp) - continue; - artifact->autoMocTimestamp = currentTime; - const FileType fileType = AutoMoc::fileType(artifact); - if (fileType == UnknownFileType) - continue; - FileTag mocFileTag; - bool alreadyMocced = isVictimOfMoc(artifact, fileType, mocFileTag); - bool hasQObjectMacro; - scan(artifact, fileType, hasQObjectMacro, includedMocCppFiles); - if (hasQObjectMacro && !alreadyMocced) - artifactsToMoc += qMakePair(artifact, fileType); - else if (!hasQObjectMacro && alreadyMocced) - unmoc(artifact, mocFileTag); - } - - Artifact *pluginHeaderFile = 0; - ArtifactsPerFileTagMap artifactsPerFileTag; - for (int i = artifactsToMoc.count(); --i >= 0;) { - const QPair<Artifact *, FileType> &p = artifactsToMoc.at(i); - Artifact * const artifact = p.first; - FileType fileType = p.second; - foreach (const FileTag &fileTag, artifact->fileTags) { - if (fileTag == "moc_hpp") { - const QString mocFileName = generateMocFileName(artifact, fileType); - if (includedMocCppFiles.contains(mocFileName)) { - FileTag newFileTag = "moc_hpp_inc"; - artifact->fileTags -= fileTag; - artifact->fileTags += newFileTag; - artifactsPerFileTag[newFileTag].insert(artifact); - continue; - } - } else if (fileTag == "moc_plugin_hpp") { - if (m_logger.debugEnabled()) { - m_logger.qbsDebug() << "[AUTOMOC] found Qt plugin header file " - << artifact->filePath(); - } - FileTag newFileTag = "moc_hpp"; - artifact->fileTags -= fileTag; - artifact->fileTags += newFileTag; - artifactsPerFileTag[newFileTag].insert(artifact); - pluginHeaderFile = artifact; - } - artifactsPerFileTag[fileTag].insert(artifact); - } - } - - if (pchFile) - artifactsPerFileTag["cpp_pch"] += pchFile; - if (!artifactsPerFileTag.isEmpty()) { - emit reportCommandDescription(QLatin1String("automoc"), - Tr::tr("Applying moc rules for '%1'.") - .arg(product->name)); - RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules(); - } - if (pluginHeaderFile && pluginMetaDataFile) { - // Make every artifact that is dependent of the header file also - // dependent of the plugin metadata file. - foreach (Artifact *outputOfHeader, pluginHeaderFile->parents) - loggedConnect(outputOfHeader, pluginMetaDataFile, m_logger); - } - - product->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger); -} - -QString AutoMoc::generateMocFileName(Artifact *artifact, FileType fileType) -{ - QString mocFileName; - switch (fileType) { - case UnknownFileType: - break; - case HppFileType: - mocFileName = QLatin1String("moc_") + FileInfo::baseName(artifact->filePath()) + - QLatin1String(".cpp"); - break; - case CppFileType: - mocFileName = FileInfo::baseName(artifact->filePath()) + QLatin1String(".moc"); - break; - } - return mocFileName; -} - -AutoMoc::FileType AutoMoc::fileType(Artifact *artifact) -{ - foreach (const FileTag &fileTag, artifact->fileTags) - if (fileTag == "hpp") - return HppFileType; - else if (fileTag == "cpp") - return CppFileType; - return UnknownFileType; -} - -void AutoMoc::scan(Artifact *artifact, FileType fileType, bool &hasQObjectMacro, - QSet<QString> &includedMocCppFiles) -{ - if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[AUTOMOC] checks " << relativeArtifactFileName(artifact); - - hasQObjectMacro = false; - - foreach (ScannerPlugin *scanner, fileType == HppFileType ? hppScanners() : cppScanners()) { - ScanResultCache::Result scanResult = m_scanResultCache->value(artifact->filePath()); - if (!scanResult.valid) { - scanResult.valid = true; - void *opaq = scanner->open(artifact->filePath().utf16(), - ScanForDependenciesFlag | ScanForFileTagsFlag); - if (!opaq || !scanner->additionalFileTags) - continue; - - int length = 0; - const char **szFileTagsFromScanner = scanner->additionalFileTags(opaq, &length); - if (szFileTagsFromScanner && length > 0) { - for (int i = length; --i >= 0;) - scanResult.additionalFileTags += szFileTagsFromScanner[i]; - } - - forever { - int flags = 0; - const char *szOutFilePath = scanner->next(opaq, &length, &flags); - if (szOutFilePath == 0) - break; - QString includedFilePath = QString::fromLocal8Bit(szOutFilePath, length); - if (includedFilePath.isEmpty()) - continue; - bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG); - scanResult.deps += ScanResultCache::Dependency(includedFilePath, isLocalInclude); - } - - scanner->close(opaq); - m_scanResultCache->insert(artifact->filePath(), scanResult); - } - - foreach (const FileTag &tag, scanResult.additionalFileTags) { - artifact->fileTags.insert(tag); - if (tag.name().startsWith("moc")) { - hasQObjectMacro = true; - if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[AUTOMOC] finds Q_OBJECT macro"; - } - } - - foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) { - const QString &includedFilePath = dependency.filePath(); - if (includedFilePath.startsWith(QLatin1String("moc_")) && - includedFilePath.endsWith(QLatin1String(".cpp"))) { - if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[AUTOMOC] finds included file: " << includedFilePath; - includedMocCppFiles += includedFilePath; - } - } - } -} - -static FileTags provideMocHeaderFileTags() -{ - FileTags fileTags; - fileTags << "moc_hpp" << "moc_hpp_inc" << "moc_plugin_hpp"; - return fileTags; -} - -bool AutoMoc::isVictimOfMoc(Artifact *artifact, FileType fileType, FileTag &foundMocFileTag) -{ - static const FileTags mocHeaderFileTags = provideMocHeaderFileTags(); - static const FileTag mocCppFileTag = "moc_cpp"; - foundMocFileTag.clear(); - switch (fileType) { - case UnknownFileType: - break; - case HppFileType: - foreach (const FileTag &fileTag, artifact->fileTags) { - if (mocHeaderFileTags.contains(fileTag)) { - foundMocFileTag = fileTag; - break; - } - } - break; - case CppFileType: - if (artifact->fileTags.contains(mocCppFileTag)) - foundMocFileTag = mocCppFileTag; - break; - } - return foundMocFileTag.isValid(); -} - -void AutoMoc::unmoc(Artifact *artifact, const FileTag &mocFileTag) -{ - if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[AUTOMOC] unmoc'ing " << relativeArtifactFileName(artifact); - - artifact->fileTags.remove(mocFileTag); - - Artifact *generatedMocArtifact = 0; - foreach (Artifact *parent, artifact->parents) { - foreach (const FileTag &fileTag, parent->fileTags) { - if (fileTag == "hpp" || fileTag == "cpp") { - generatedMocArtifact = parent; - break; - } - } - } - - if (!generatedMocArtifact) { - m_logger.qbsTrace() << "[AUTOMOC] generated moc artifact could not be found"; - return; - } - - TopLevelProject * const project = artifact->product->topLevelProject(); - if (mocFileTag == "moc_hpp") { - Artifact *mocObjArtifact = 0; - foreach (Artifact *parent, generatedMocArtifact->parents) { - foreach (const FileTag &fileTag, parent->fileTags) { - if (fileTag == "obj" || fileTag == "fpicobj") { - mocObjArtifact = parent; - break; - } - } - } - - if (!mocObjArtifact) { - m_logger.qbsTrace() << "[AUTOMOC] generated moc obj artifact could not be found"; - } else { - if (m_logger.traceEnabled()) { - m_logger.qbsTrace() << "[AUTOMOC] removing moc obj artifact " - << relativeArtifactFileName(mocObjArtifact); - } - project->buildData->removeArtifact(mocObjArtifact, m_logger); - delete mocObjArtifact; - } - } - - if (m_logger.traceEnabled()) { - m_logger.qbsTrace() << "[AUTOMOC] removing generated artifact " - << relativeArtifactFileName(generatedMocArtifact); - } - project->buildData->removeArtifact(generatedMocArtifact, m_logger); - delete generatedMocArtifact; -} - -const QList<ScannerPlugin *> &AutoMoc::cppScanners() const -{ - if (m_cppScanners.isEmpty()) - m_cppScanners = ScannerPluginManager::scannersForFileTag("cpp"); - - return m_cppScanners; -} - -const QList<ScannerPlugin *> &AutoMoc::hppScanners() const -{ - if (m_hppScanners.isEmpty()) - m_hppScanners = ScannerPluginManager::scannersForFileTag("hpp"); - - return m_hppScanners; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp index 252f636cf..dd36ee15c 100644 --- a/src/lib/corelib/buildgraph/buildgraph.cpp +++ b/src/lib/corelib/buildgraph/buildgraph.cpp @@ -216,14 +216,14 @@ void setupScriptEngineForProduct(ScriptEngine *engine, const ResolvedProductCons rule->module->name.isEmpty() ? QScriptValue() : rule->module->name); } -bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path) +bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &path) { if (u == v) { path.append(v); return true; } - for (ArtifactSet::const_iterator it = u->children.begin(); it != u->children.end(); ++it) { + for (NodeSet::const_iterator it = u->children.begin(); it != u->children.end(); ++it) { if (findPath(*it, v, path)) { path.prepend(u); return true; @@ -242,28 +242,34 @@ bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path) * also: children means i depend on or i am produced by * parent means "produced by me" or "depends on me" */ -void connect(Artifact *p, Artifact *c) +void connect(BuildGraphNode *p, BuildGraphNode *c) { QBS_CHECK(p != c); - foreach (const Artifact * const child, p->children) - if (child != c && child->filePath() == c->filePath()) - throw ErrorInfo(QString::fromLocal8Bit("Artifact %1 already has a child artifact %2 as different object.").arg(p->filePath(), c->filePath()), CodeLocation(), true); + if (Artifact *ac = dynamic_cast<Artifact *>(c)) { + foreach (const Artifact * const child, ArtifactSet::fromNodeSet(p->children)) + if (child != ac && child->filePath() == ac->filePath()) { + throw ErrorInfo(QString::fromLocal8Bit("%1 already has a child artifact %2 as " + "different object.").arg(p->toString(), + ac->filePath()), + CodeLocation(), true); + } + } p->children.insert(c); c->parents.insert(p); p->product->topLevelProject()->buildData->isDirty = true; } -void loggedConnect(Artifact *u, Artifact *v, const Logger &logger) +void loggedConnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger) { QBS_CHECK(u != v); if (logger.traceEnabled()) { logger.qbsTrace() << QString::fromLocal8Bit("[BG] connect '%1' -> '%2'") - .arg(relativeArtifactFileName(u), relativeArtifactFileName(v)); + .arg(u->toString(), v->toString()); } connect(u, v); } -static bool existsPath_impl(Artifact *u, Artifact *v, QSet<Artifact *> *seen) +static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, QSet<BuildGraphNode *> *seen) { if (u == v) return true; @@ -272,19 +278,27 @@ static bool existsPath_impl(Artifact *u, Artifact *v, QSet<Artifact *> *seen) return false; seen->insert(u); - for (ArtifactSet::const_iterator it = u->children.begin(); it != u->children.end(); ++it) + for (NodeSet::const_iterator it = u->children.begin(); it != u->children.end(); ++it) if (existsPath_impl(*it, v, seen)) return true; return false; } -static bool existsPath(Artifact *u, Artifact *v) +static bool existsPath(BuildGraphNode *u, BuildGraphNode *v) { - QSet<Artifact *> seen; + QSet<BuildGraphNode *> seen; return existsPath_impl(u, v, &seen); } +static QStringList toStringList(const QList<BuildGraphNode *> &path) +{ + QStringList lst; + foreach (BuildGraphNode *node, path) + lst << node->toString(); + return lst; +} + bool safeConnect(Artifact *u, Artifact *v, const Logger &logger) { QBS_CHECK(u != v); @@ -294,7 +308,7 @@ bool safeConnect(Artifact *u, Artifact *v, const Logger &logger) } if (existsPath(v, u)) { - QList<Artifact *> circle; + QList<BuildGraphNode *> circle; findPath(v, u, circle); logger.qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle); return false; @@ -304,31 +318,32 @@ bool safeConnect(Artifact *u, Artifact *v, const Logger &logger) return true; } -void disconnect(Artifact *u, Artifact *v, const Logger &logger) +void disconnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger) { if (logger.traceEnabled()) { logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnect: '%1' '%2'") - .arg(relativeArtifactFileName(u), relativeArtifactFileName(v)); + .arg(u->toString(), v->toString()); } u->children.remove(v); - u->childrenAddedByScanner.remove(v); v->parents.remove(u); + u->onChildDisconnected(v); } void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger) { if (artifact->artifactType != Artifact::Generated) return; + removeGeneratedArtifactFromDisk(artifact->filePath(), logger); +} - QFile file(artifact->filePath()); +void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger) +{ + QFile file(filePath); if (!file.exists()) return; - - logger.qbsDebug() << "removing " << artifact->fileName(); - if (!file.remove()) { - logger.qbsWarning() << QString::fromLocal8Bit("Cannot remove '%1'.") - .arg(artifact->filePath()); - } + logger.qbsDebug() << "removing " << filePath; + if (!file.remove()) + logger.qbsWarning() << QString::fromLocal8Bit("Cannot remove '%1'.").arg(filePath); } QString relativeArtifactFileName(const Artifact *artifact) @@ -404,7 +419,7 @@ void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const { QBS_CHECK(!artifact->product); QBS_CHECK(!artifact->filePath().isEmpty()); - QBS_CHECK(!product->buildData->artifacts.contains(artifact)); + QBS_CHECK(!product->buildData->nodes.contains(artifact)); #ifdef QT_DEBUG foreach (const ResolvedProductConstPtr &otherProduct, product->project->products) { if (lookupArtifact(otherProduct, artifact->filePath())) { @@ -421,7 +436,7 @@ void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const } } #endif - product->buildData->artifacts.insert(artifact); + product->buildData->nodes.insert(artifact); artifact->product = product; product->topLevelProject()->buildData->insertIntoLookupTable(artifact); product->topLevelProject()->buildData->isDirty = true; @@ -443,25 +458,31 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, con QBS_CHECK(!!product->enabled == !!buildData); if (!product->enabled) return; - foreach (Artifact * const ta, buildData->targetArtifacts) { + foreach (BuildGraphNode * const node, buildData->roots) { if (logger.traceEnabled()) - logger.qbsTrace() << "Checking target artifact '" << ta->fileName() << "'."; - QBS_CHECK(buildData->artifacts.contains(ta)); + logger.qbsTrace() << "Checking root node '" << node->toString() << "'."; + QBS_CHECK(buildData->nodes.contains(node)); } QSet<QString> filePaths; - foreach (Artifact * const artifact, buildData->artifacts) { - logger.qbsDebug() << "Sanity checking artifact '" << artifact->fileName() << "'"; + foreach (BuildGraphNode * const node, buildData->nodes) { + logger.qbsDebug() << "Sanity checking node '" << node->toString() << "'"; + QBS_CHECK(node->product == product); + foreach (const BuildGraphNode * const parent, node->parents) + QBS_CHECK(parent->children.contains(node)); + foreach (BuildGraphNode * const child, node->children) { + QBS_CHECK(child->parents.contains(node)); + QBS_CHECK(child->product); + QBS_CHECK(!child->product->buildData.isNull()); + QBS_CHECK(child->product->buildData->nodes.contains(child)); + } + + Artifact * const artifact = dynamic_cast<Artifact *>(node); + if (!artifact) + continue; + QBS_CHECK(!filePaths.contains(artifact->filePath())); filePaths << artifact->filePath(); - QBS_CHECK(artifact->product == product); - foreach (const Artifact * const parent, artifact->parents) - QBS_CHECK(parent->children.contains(artifact)); - foreach (Artifact * const child, artifact->children) { - QBS_CHECK(child->parents.contains(artifact)); - QBS_CHECK(!child->product.isNull()); - QBS_CHECK(child->product->buildData); - QBS_CHECK(child->product->buildData->artifacts.contains(child)); - } + foreach (Artifact * const child, artifact->childrenAddedByScanner) QBS_CHECK(artifact->children.contains(child)); const TransformerConstPtr transformer = artifact->transformer; @@ -475,9 +496,9 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, con ArtifactSet transformerOutputChildren; foreach (const Artifact * const output, transformer->outputs) { QBS_CHECK(output->transformer == transformer); - transformerOutputChildren.unite(output->children); + transformerOutputChildren.unite(ArtifactSet::fromNodeSet(output->children)); QSet<QString> childFilePaths; - foreach (const Artifact * const a, output->children) { + foreach (const Artifact * const a, ArtifactSet::fromNodeSet(output->children)) { if (childFilePaths.contains(a->filePath())) { throw ErrorInfo(QString::fromLocal8Bit("There is more than one artifact for " "file '%1' in the child list for output '%2'.") diff --git a/src/lib/corelib/buildgraph/buildgraph.h b/src/lib/corelib/buildgraph/buildgraph.h index 5244894cb..d0da37fa7 100644 --- a/src/lib/corelib/buildgraph/buildgraph.h +++ b/src/lib/corelib/buildgraph/buildgraph.h @@ -30,8 +30,6 @@ #define QBS_BUILDGRAPH_H #include "forward_decls.h" -#include "rulesapplicator.h" - #include <language/forward_decls.h> #include <QScriptValue> @@ -39,6 +37,7 @@ namespace qbs { namespace Internal { +class BuildGraphNode; class Logger; class ScriptEngine; class PrepareScriptObserver; @@ -59,17 +58,16 @@ Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact Artifact *createArtifact(const ResolvedProductPtr &product, const SourceArtifactConstPtr &sourceArtifact, const Logger &logger); void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const Logger &logger); -void addTargetArtifacts(const ResolvedProductPtr &product, - ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger); void dumpProductBuildData(const ResolvedProductConstPtr &product); -bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path); -void connect(Artifact *p, Artifact *c); -void loggedConnect(Artifact *u, Artifact *v, const Logger &logger); +bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode*> &path); +void connect(BuildGraphNode *p, BuildGraphNode *c); +void loggedConnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger); bool safeConnect(Artifact *u, Artifact *v, const Logger &logger); void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger); -void disconnect(Artifact *u, Artifact *v, const Logger &logger); +void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger); +void disconnect(BuildGraphNode *u, BuildGraphNode *v, const Logger &logger); void setupScriptEngineForFile(ScriptEngine *engine, const ResolvedFileContextConstPtr &fileContext, QScriptValue targetObject); @@ -80,15 +78,6 @@ QString relativeArtifactFileName(const Artifact *artifact); // Debugging helpers void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger); -template <typename T> -QStringList toStringList(const T &artifactContainer) -{ - QStringList l; - foreach (Artifact *n, artifactContainer) - l.append(relativeArtifactFileName(n)); - return l; -} - } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraph.pri b/src/lib/corelib/buildgraph/buildgraph.pri index 4142d94bd..0674f2ca7 100644 --- a/src/lib/corelib/buildgraph/buildgraph.pri +++ b/src/lib/corelib/buildgraph/buildgraph.pri @@ -4,9 +4,9 @@ SOURCES += \ $$PWD/artifactcleaner.cpp \ $$PWD/artifactset.cpp \ $$PWD/artifactvisitor.cpp \ - $$PWD/automoc.cpp \ $$PWD/buildgraph.cpp \ $$PWD/buildgraphloader.cpp \ + $$PWD/buildgraphnode.cpp \ $$PWD/command.cpp \ $$PWD/cycledetector.cpp \ $$PWD/executor.cpp \ @@ -14,11 +14,15 @@ SOURCES += \ $$PWD/filedependency.cpp \ $$PWD/inputartifactscanner.cpp \ $$PWD/jscommandexecutor.cpp \ + $$PWD/nodeset.cpp \ $$PWD/processcommandexecutor.cpp \ $$PWD/productbuilddata.cpp \ $$PWD/productinstaller.cpp \ $$PWD/projectbuilddata.cpp \ + $$PWD/qtmocscanner.cpp \ + $$PWD/rescuableartifactdata.cpp \ $$PWD/rulegraph.cpp \ + $$PWD/rulenode.cpp \ $$PWD/rulesapplicator.cpp \ $$PWD/rulesevaluationcontext.cpp \ $$PWD/scanresultcache.cpp \ @@ -31,9 +35,10 @@ HEADERS += \ $$PWD/artifactcleaner.h \ $$PWD/artifactset.h \ $$PWD/artifactvisitor.h \ - $$PWD/automoc.h \ $$PWD/buildgraph.h \ $$PWD/buildgraphloader.h \ + $$PWD/buildgraphnode.h \ + $$PWD/buildgraphvisitor.h \ $$PWD/command.h \ $$PWD/cycledetector.h \ $$PWD/executor.h \ @@ -42,11 +47,15 @@ HEADERS += \ $$PWD/forward_decls.h \ $$PWD/inputartifactscanner.h \ $$PWD/jscommandexecutor.h \ + $$PWD/nodeset.h \ $$PWD/processcommandexecutor.h \ $$PWD/productbuilddata.h \ $$PWD/productinstaller.h \ $$PWD/projectbuilddata.h \ + $$PWD/qtmocscanner.h \ + $$PWD/rescuableartifactdata.h \ $$PWD/rulegraph.h \ + $$PWD/rulenode.h \ $$PWD/rulesapplicator.h \ $$PWD/rulesevaluationcontext.h \ $$PWD/scanresultcache.h \ diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp index 1a7d0a7f1..21cdc9547 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.cpp +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -85,8 +85,10 @@ static void restoreBackPointers(const ResolvedProjectPtr &project) product->project = project; if (!product->buildData) continue; - foreach (Artifact * const a, product->buildData->artifacts) - project->topLevelProject()->buildData->insertIntoLookupTable(a); + foreach (BuildGraphNode * const n, product->buildData->nodes) { + if (Artifact *a = dynamic_cast<Artifact *>(n)) + project->topLevelProject()->buildData->insertIntoLookupTable(a); + } } foreach (const ResolvedProjectPtr &subProject, project->subProjects) { @@ -217,8 +219,11 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met // If the product gets temporarily removed, its artifacts will get disconnected // and this structural information will no longer be directly available from them. - foreach (const Artifact * const a, product->buildData->artifacts) - childLists.insert(a, a->children); + foreach (const Artifact * const a, + ArtifactSet::fromNodeSet(product->buildData->nodes)) { + childLists.insert(a, ChildrenInfo(ArtifactSet::fromNodeSet(a->children), + a->childrenAddedByScanner)); + } } } @@ -248,7 +253,8 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met } else { if (restoredProduct->enabled) { QBS_CHECK(restoredProduct->buildData); - foreach (Artifact * const a, newlyResolvedProduct->buildData->artifacts) { + foreach (Artifact * const a, + ArtifactSet::fromNodeSet(newlyResolvedProduct->buildData->nodes)) { const bool removeFromDisk = a->artifactType == Artifact::Generated; newlyResolvedProduct->topLevelProject()->buildData->removeArtifact(a, m_logger, removeFromDisk, true); @@ -258,8 +264,8 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met productsWithChangedFiles.removeOne(restoredProduct); } if (newlyResolvedProduct->buildData) { - foreach (Artifact * const a, newlyResolvedProduct->buildData->artifacts) - a->product = newlyResolvedProduct; + foreach (BuildGraphNode *node, newlyResolvedProduct->buildData->nodes) + node->product = newlyResolvedProduct; } // Keep in list if build data still needs to be resolved. @@ -495,7 +501,7 @@ bool BuildGraphLoader::checkTransformersForPropertyChanges(const ResolvedProduct { bool transformerChanges = false; QSet<TransformerConstPtr> seenTransformers; - foreach (Artifact * const artifact, restoredProduct->buildData->artifacts) { + foreach (Artifact *artifact, ArtifactSet::fromNodeSet(restoredProduct->buildData->nodes)) { const TransformerPtr transformer = artifact->transformer; if (!transformer || seenTransformers.contains(transformer)) continue; @@ -517,7 +523,7 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, product->project->products.removeOne(product); if (product->buildData) { - foreach (Artifact *artifact, product->buildData->artifacts) + foreach (Artifact *artifact, ArtifactSet::fromNodeSet(product->buildData->nodes)) projectBuildData->removeArtifact(artifact, m_logger, removeArtifactsFromDisk, false); } } @@ -529,7 +535,6 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor QBS_CHECK(newlyResolvedProduct->enabled); - ArtifactsPerFileTagMap artifactsPerFileTag; QList<Artifact *> addedArtifacts; ArtifactSet artifactsToRemove; QHash<QString, SourceArtifactConstPtr> oldArtifacts, newArtifacts; @@ -601,20 +606,13 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor // handle added filetags foreach (const FileTag &addedFileTag, changedArtifact->fileTags - a->fileTags) { artifact->fileTags += addedFileTag; - artifactsPerFileTag[addedFileTag] += artifact; + newlyResolvedProduct->registerAddedFileTag(addedFileTag, artifact); } // handle removed filetags foreach (const FileTag &removedFileTag, a->fileTags - changedArtifact->fileTags) { artifact->fileTags -= removedFileTag; - foreach (Artifact *parent, artifact->parents) { - if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) { - // this parent has been created because of the removed filetag - newlyResolvedProduct->topLevelProject()->buildData - ->removeArtifactAndExclusiveDependents(parent, m_logger, true, - &artifactsToRemove); - } - } + newlyResolvedProduct->registerRemovedFileTag(removedFileTag, artifact); } } @@ -631,23 +629,11 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor } } - // apply rules for new artifacts - foreach (Artifact *artifact, addedArtifacts) - foreach (const FileTag &ft, artifact->fileTags) - artifactsPerFileTag[ft] += artifact; - RulesApplicator(newlyResolvedProduct, artifactsPerFileTag, m_logger).applyAllRules(); - - addTargetArtifacts(newlyResolvedProduct, artifactsPerFileTag, m_logger); - - // parents of removed artifacts must update their transformers - foreach (Artifact *removedArtifact, artifactsToRemove) - foreach (Artifact *parent, removedArtifact->parents) - newlyResolvedProduct->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent; - newlyResolvedProduct->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger); - // defer destruction of removed artifacts foreach (Artifact *artifact, artifactsToRemove) m_objectsToDelete << artifact; + foreach (Artifact * const artifact, addedArtifacts) + newlyResolvedProduct->registerAddedArtifact(artifact); } static SourceArtifactConstPtr findSourceArtifact(const ResolvedProductConstPtr &product, @@ -673,7 +659,7 @@ bool BuildGraphLoader::checkForPropertyChanges(const TransformerPtr &restoredTra const QVariantMap properties = property.kind == Property::PropertyInProject ? freshProduct->project->projectProperties() : freshProduct->properties->value(); if (checkForPropertyChange(property, properties)) { - JavaScriptCommand * const pseudoCommand = new JavaScriptCommand; + const JavaScriptCommandPtr &pseudoCommand = JavaScriptCommand::create(); pseudoCommand->setSourceCode(QLatin1String("random stuff that will cause " "commandsEqual() to fail")); restoredTrafo->commands << pseudoCommand; @@ -746,7 +732,7 @@ void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPt foreach (const ResolvedProductPtr &product, fileDepProduct->topLevelProject()->allProducts()) { if (!product->buildData) continue; - foreach (Artifact *artifactInProduct, product->buildData->artifacts) { + foreach (Artifact *artifactInProduct, ArtifactSet::fromNodeSet(product->buildData->nodes)) { if (artifactInProduct->fileDependencies.contains(filedep)) { artifactInProduct->fileDependencies.remove(filedep); loggedConnect(artifactInProduct, artifact, m_logger); @@ -758,24 +744,9 @@ void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPt m_objectsToDelete << filedep; } -static bool commandsEqual(const TransformerConstPtr &t1, const TransformerConstPtr &t2) -{ - if (t1->commands.count() != t2->commands.count()) - return false; - for (int i = 0; i < t1->commands.count(); ++i) - if (!t1->commands.at(i)->equals(t2->commands.at(i))) - return false; - return true; -} - -/** - * Rescues the following data from the restoredProduct to newlyResolvedProduct: - * - dependencies between artifacts, - * - time stamps of artifacts, if their commands have not changed. - */ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, - const ResolvedProductPtr &newlyResolvedProduct, - const ProjectBuildData *oldBuildData, const ChildListHash &childLists) + const ResolvedProductPtr &newlyResolvedProduct, ProjectBuildData *oldBuildData, + const ChildListHash &childLists) { if (!restoredProduct->enabled || !newlyResolvedProduct->enabled) return; @@ -785,22 +756,31 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore "product '%1'").arg(restoredProduct->name); } - foreach (Artifact *artifact, newlyResolvedProduct->buildData->artifacts) { + // This is needed for artifacts created by manually added transformers, which are + // already present in the build graph. + // FIXME: This complete block could go away if Transformers were also to create their + // output artifacts at build time. + QList<Artifact *> oldArtifactsCreatedByTransformers; + foreach (Artifact *artifact, + ArtifactSet::fromNodeSet(newlyResolvedProduct->buildData->nodes)) { + if (!artifact->transformer) + continue; if (m_logger.traceEnabled()) { m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] artifact '%1'") .arg(artifact->fileName()); } Artifact * const oldArtifact = lookupArtifact(restoredProduct, oldBuildData, - artifact->dirPath(), artifact->fileName(), true); + artifact->dirPath(), artifact->fileName(), + true); if (!oldArtifact || !oldArtifact->transformer) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] no transformer data"); continue; } - - if (artifact->transformer - && !commandsEqual(artifact->transformer, oldArtifact->transformer)) { + oldArtifactsCreatedByTransformers << oldArtifact; + if (!commandListsAreEqual(artifact->transformer->commands, + oldArtifact->transformer->commands)) { if (m_logger.traceEnabled()) m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] artifact invalidated"); removeGeneratedArtifactFromDisk(oldArtifact, m_logger); @@ -808,30 +788,40 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore } artifact->setTimestamp(oldArtifact->timestamp()); - foreach (Artifact * const oldChild, childLists.value(oldArtifact)) { + const ChildrenInfo &childrenInfo = childLists.value(oldArtifact); + foreach (Artifact * const oldChild, childrenInfo.children) { Artifact * const newChild = lookupArtifact(oldChild->product, newlyResolvedProduct->topLevelProject()->buildData.data(), oldChild->filePath(), true); if (!newChild || artifact->children.contains(newChild)) continue; safeConnect(artifact, newChild, m_logger); + if (childrenInfo.childrenAddedByScanner.contains(oldChild)) + artifact->childrenAddedByScanner << newChild; } } -} -void addTargetArtifacts(const ResolvedProductPtr &product, - ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger) -{ - foreach (const FileTag &fileTag, product->fileTags) { - foreach (Artifact * const artifact, artifactsPerFileTag.value(fileTag)) { - if (artifact->artifactType == Artifact::Generated) - product->buildData->targetArtifacts += artifact; + // This is needed for artifacts created by rules, which happens later in the executor. + foreach (Artifact * const oldArtifact, + ArtifactSet::fromNodeSet(restoredProduct->buildData->nodes)) { + if (!oldArtifact->transformer) + continue; + if (oldArtifactsCreatedByTransformers.contains(oldArtifact)) + continue; + Artifact * const newArtifact = lookupArtifact(newlyResolvedProduct, oldArtifact, false); + if (!newArtifact) { + RescuableArtifactData rad; + rad.timeStamp = oldArtifact->timestamp(); + rad.commands = oldArtifact->transformer->commands; + const ChildrenInfo &childrenInfo = childLists.value(oldArtifact); + foreach (Artifact * const child, childrenInfo.children) { + rad.children << RescuableArtifactData::ChildData(child->product->name, + child->filePath(), childrenInfo.childrenAddedByScanner.contains(child)); + } + newlyResolvedProduct->buildData->rescuableArtifactData.insert( + oldArtifact->filePath(), rad); } } - if (product->buildData->targetArtifacts.isEmpty()) { - const QString msg = QString::fromLocal8Bit("No artifacts generated for product '%1'."); - logger.qbsDebug() << msg.arg(product->name); - } } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h index 0c0b7cb8b..a6b705d1f 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.h +++ b/src/lib/corelib/buildgraph/buildgraphloader.h @@ -45,6 +45,7 @@ namespace Internal { class FileDependency; class FileResourceBase; class FileTime; +class NodeSet; class Property; class BuildGraphLoadResult @@ -101,10 +102,17 @@ private: void replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct, FileDependency *filedep, Artifact *artifact); - typedef QHash<const Artifact *, ArtifactSet> ChildListHash; + struct ChildrenInfo { + ChildrenInfo() {} + ChildrenInfo(const ArtifactSet &c1, const ArtifactSet &c2) + : children(c1), childrenAddedByScanner(c2) {} + ArtifactSet children; + ArtifactSet childrenAddedByScanner; + }; + typedef QHash<const Artifact *, ChildrenInfo> ChildListHash; void rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, const ResolvedProductPtr &newlyResolvedProduct, - const ProjectBuildData *oldBuildData, + ProjectBuildData *oldBuildData, const ChildListHash &childLists); RulesEvaluationContextPtr m_evalContext; diff --git a/src/lib/corelib/buildgraph/buildgraphnode.cpp b/src/lib/corelib/buildgraph/buildgraphnode.cpp new file mode 100644 index 000000000..d7eaac079 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphnode.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 "buildgraphnode.h" + +#include "buildgraphvisitor.h" +#include "projectbuilddata.h" +#include <language/language.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +//#include <qglobal.h> + +namespace qbs { +namespace Internal { + +BuildGraphNode::BuildGraphNode() +{ +} + +BuildGraphNode::~BuildGraphNode() +{ + foreach (BuildGraphNode *p, parents) + p->children.remove(this); + foreach (BuildGraphNode *c, children) + c->parents.remove(this); +} + +void BuildGraphNode::onChildDisconnected(BuildGraphNode *child) +{ + Q_UNUSED(child); +} + +void BuildGraphNode::acceptChildren(BuildGraphVisitor *visitor) +{ + foreach (BuildGraphNode *child, children) + child->accept(visitor); +} + +void BuildGraphNode::load(PersistentPool &pool) +{ + children.load(pool); + // Parents must be updated after loading all nodes. +} + +void BuildGraphNode::store(PersistentPool &pool) const +{ + children.store(pool); + // Do not store parents to avoid recursion. +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraphnode.h b/src/lib/corelib/buildgraph/buildgraphnode.h new file mode 100644 index 000000000..dbb046547 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphnode.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_BUILDGRAPHNODE_H +#define QBS_BUILDGRAPHNODE_H + +#include "nodeset.h" +#include <language/forward_decls.h> +#include <tools/persistentobject.h> +#include <tools/weakpointer.h> + +namespace qbs { +namespace Internal { + +class BuildGraphVisitor; +class TopLevelProject; + +class BuildGraphNode : public virtual PersistentObject +{ + friend class NodeSet; +public: + virtual ~BuildGraphNode(); + + NodeSet parents; + NodeSet children; + WeakPointer<ResolvedProduct> product; + + enum BuildState + { + Untouched = 0, + Buildable, + Building, + Built + }; + + BuildState buildState; // Do not serialize. Will be refreshed for every build. + + enum Type + { + ArtifactNodeType, + RuleNodeType + }; + + virtual Type type() const = 0; + virtual void accept(BuildGraphVisitor *visitor) = 0; + virtual QString toString() const = 0; + virtual void onChildDisconnected(BuildGraphNode *child); + +protected: + explicit BuildGraphNode(); + void acceptChildren(BuildGraphVisitor *visitor); + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHNODE_H diff --git a/src/lib/corelib/buildgraph/buildgraphvisitor.h b/src/lib/corelib/buildgraph/buildgraphvisitor.h new file mode 100644 index 000000000..0a574e910 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphvisitor.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_BUILDGRAPHVISITOR_H +#define QBS_BUILDGRAPHVISITOR_H + +namespace qbs { +namespace Internal { + +class Artifact; +class RuleNode; + +/*! + * \brief The BuildGraphVisitor class + * + * The return value of a visit method indicates whether the children of the current node + * are to be visited next. + */ +class BuildGraphVisitor +{ +public: + virtual bool visit(Artifact *) { return true; } + virtual bool visit(RuleNode *) { return true; } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHVISITOR_H diff --git a/src/lib/corelib/buildgraph/command.cpp b/src/lib/corelib/buildgraph/command.cpp index 60ec93907..ea47191c5 100644 --- a/src/lib/corelib/buildgraph/command.cpp +++ b/src/lib/corelib/buildgraph/command.cpp @@ -50,16 +50,16 @@ AbstractCommand::~AbstractCommand() { } -AbstractCommand *AbstractCommand::createByType(AbstractCommand::CommandType commandType) +AbstractCommandPtr AbstractCommand::createByType(AbstractCommand::CommandType commandType) { switch (commandType) { case AbstractCommand::ProcessCommandType: - return new ProcessCommand; + return ProcessCommand::create(); case AbstractCommand::JavaScriptCommandType: - return new JavaScriptCommand; + return JavaScriptCommand::create(); } qFatal("%s: Invalid command type %d", Q_FUNC_INFO, commandType); - return 0; + return AbstractCommandPtr(); } bool AbstractCommand::equals(const AbstractCommand *other) const @@ -104,33 +104,33 @@ static QScriptValue js_Command(QScriptContext *context, QScriptEngine *engine) if (Q_UNLIKELY(!context->isCalledAsConstructor())) return context->throwError(Tr::tr("Command constructor called without new.")); - static ProcessCommand commandPrototype; + static ProcessCommandPtr commandPrototype = ProcessCommand::create(); QScriptValue program = context->argument(0); if (program.isUndefined()) - program = engine->toScriptValue(commandPrototype.program()); + program = engine->toScriptValue(commandPrototype->program()); QScriptValue arguments = context->argument(1); if (arguments.isUndefined()) - arguments = engine->toScriptValue(commandPrototype.arguments()); + arguments = engine->toScriptValue(commandPrototype->arguments()); QScriptValue cmd = js_CommandBase(context, engine); cmd.setProperty(QLatin1String("className"), engine->toScriptValue(QString::fromLatin1("Command"))); cmd.setProperty(QLatin1String("program"), program); cmd.setProperty(QLatin1String("arguments"), arguments); cmd.setProperty(QLatin1String("workingDir"), - engine->toScriptValue(commandPrototype.workingDir())); + engine->toScriptValue(commandPrototype->workingDir())); cmd.setProperty(QLatin1String("maxExitCode"), - engine->toScriptValue(commandPrototype.maxExitCode())); + engine->toScriptValue(commandPrototype->maxExitCode())); cmd.setProperty(QLatin1String("stdoutFilterFunction"), - engine->toScriptValue(commandPrototype.stdoutFilterFunction())); + engine->toScriptValue(commandPrototype->stdoutFilterFunction())); cmd.setProperty(QLatin1String("stderrFilterFunction"), - engine->toScriptValue(commandPrototype.stderrFilterFunction())); + engine->toScriptValue(commandPrototype->stderrFilterFunction())); cmd.setProperty(QLatin1String("responseFileThreshold"), - engine->toScriptValue(commandPrototype.responseFileThreshold())); + engine->toScriptValue(commandPrototype->responseFileThreshold())); cmd.setProperty(QLatin1String("responseFileUsagePrefix"), - engine->toScriptValue(commandPrototype.responseFileUsagePrefix())); + engine->toScriptValue(commandPrototype->responseFileUsagePrefix())); cmd.setProperty(QLatin1String("environment"), - engine->toScriptValue(commandPrototype.environment().toStringList())); + engine->toScriptValue(commandPrototype->environment().toStringList())); return cmd; } @@ -236,12 +236,12 @@ static QScriptValue js_JavaScriptCommand(QScriptContext *context, QScriptEngine QLatin1String("JavaScriptCommand c'tor doesn't take arguments.")); } - static JavaScriptCommand commandPrototype; + static JavaScriptCommandPtr commandPrototype = JavaScriptCommand::create(); QScriptValue cmd = js_CommandBase(context, engine); cmd.setProperty(QLatin1String("className"), engine->toScriptValue(QString::fromLatin1("JavaScriptCommand"))); cmd.setProperty(QLatin1String("sourceCode"), - engine->toScriptValue(commandPrototype.sourceCode())); + engine->toScriptValue(commandPrototype->sourceCode())); return cmd; } @@ -301,5 +301,41 @@ void JavaScriptCommand::store(QDataStream &s) << m_properties; } +QList<AbstractCommandPtr> loadCommandList(QDataStream &s) +{ + QList<AbstractCommandPtr> commands; + int count; + s >> count; + commands.reserve(count); + while (--count >= 0) { + int cmdType; + s >> cmdType; + const AbstractCommandPtr cmd + = AbstractCommand::createByType(static_cast<AbstractCommand::CommandType>(cmdType)); + cmd->load(s); + commands += cmd; + } + return commands; +} + +void storeCommandList(const QList<AbstractCommandPtr> &commands, QDataStream &s) +{ + s << commands.count(); + foreach (const AbstractCommandPtr &cmd, commands) { + s << int(cmd->type()); + cmd->store(s); + } +} + +bool commandListsAreEqual(const QList<AbstractCommandPtr> &l1, const QList<AbstractCommandPtr> &l2) +{ + if (l1.count() != l2.count()) + return false; + for (int i = 0; i < l1.count(); ++i) + if (!l1.at(i)->equals(l2.at(i).data())) + return false; + return true; +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/command.h b/src/lib/corelib/buildgraph/command.h index 69fa32299..0b9b10edb 100644 --- a/src/lib/corelib/buildgraph/command.h +++ b/src/lib/corelib/buildgraph/command.h @@ -30,6 +30,8 @@ #ifndef QBS_COMMAND_H #define QBS_COMMAND_H +#include "forward_decls.h" + #include <tools/codelocation.h> #include <QProcessEnvironment> @@ -50,7 +52,7 @@ public: JavaScriptCommandType }; - static AbstractCommand *createByType(CommandType commandType); + static AbstractCommandPtr createByType(CommandType commandType); static QString defaultDescription() { return QString(); } static QString defaultHighLight() { return QString(); } static bool defaultIsSilent() { return false; } @@ -79,10 +81,9 @@ private: class ProcessCommand : public AbstractCommand { public: + static ProcessCommandPtr create() { return ProcessCommandPtr(new ProcessCommand); } static void setupForJavaScript(QScriptValue targetObject); - ProcessCommand(); - CommandType type() const { return ProcessCommandType; } bool equals(const AbstractCommand *otherAbstractCommand) const; void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); @@ -100,6 +101,8 @@ public: QProcessEnvironment environment() const { return m_environment; } private: + ProcessCommand(); + void getEnvironmentFromList(const QStringList &envList); QString m_program; @@ -116,10 +119,9 @@ private: class JavaScriptCommand : public AbstractCommand { public: + static JavaScriptCommandPtr create() { return JavaScriptCommandPtr(new JavaScriptCommand); } static void setupForJavaScript(QScriptValue targetObject); - JavaScriptCommand(); - virtual CommandType type() const { return JavaScriptCommandType; } bool equals(const AbstractCommand *otherAbstractCommand) const; void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); @@ -132,10 +134,17 @@ public: const QVariantMap &properties() const { return m_properties; } private: + JavaScriptCommand(); + QString m_sourceCode; QVariantMap m_properties; }; +QList<AbstractCommandPtr> loadCommandList(QDataStream &s); +void storeCommandList(const QList<AbstractCommandPtr> &commands, QDataStream &s); + +bool commandListsAreEqual(const QList<AbstractCommandPtr> &l1, const QList<AbstractCommandPtr> &l2); + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/cycledetector.cpp b/src/lib/corelib/buildgraph/cycledetector.cpp index 1ab6b4bb8..767fd2b12 100644 --- a/src/lib/corelib/buildgraph/cycledetector.cpp +++ b/src/lib/corelib/buildgraph/cycledetector.cpp @@ -30,7 +30,8 @@ #include "artifact.h" #include "buildgraph.h" -#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulenode.h" #include <language/language.h> #include <logging/translator.h> @@ -40,51 +41,58 @@ namespace qbs { namespace Internal { CycleDetector::CycleDetector(const Logger &logger) - : ArtifactVisitor(0), m_parent(0), m_logger(logger) + : m_parent(0), m_logger(logger) { } -void CycleDetector::visitProject(const ResolvedProjectConstPtr &project) +void CycleDetector::visitProject(const TopLevelProjectConstPtr &project) { const QString description = QString::fromLocal8Bit("Cycle detection for project '%1'") .arg(project->name); TimedActivityLogger timeLogger(m_logger, description, QLatin1String("[BG] "), LoggerTrace); - ArtifactVisitor::visitProject(project); + + project->accept(this); } void CycleDetector::visitProduct(const ResolvedProductConstPtr &product) { - if (!product->buildData) - return; - foreach (Artifact * const artifact, product->buildData->targetArtifacts) - visitArtifact(artifact); + product->accept(this); +} + +bool CycleDetector::visit(Artifact *artifact) +{ + return visitNode(artifact); } -void CycleDetector::visitArtifact(Artifact *artifact) +bool CycleDetector::visit(RuleNode *ruleNode) { - if (Q_UNLIKELY(m_artifactsInCurrentPath.contains(artifact))) { + return visitNode(ruleNode); +} + +bool CycleDetector::visitNode(BuildGraphNode *node) +{ + if (Q_UNLIKELY(m_nodesInCurrentPath.contains(node))) { ErrorInfo error(Tr::tr("Cycle in build graph detected.")); - foreach (const Artifact * const a, cycle(artifact)) - error.append(a->filePath()); + foreach (const BuildGraphNode * const node, cycle(node)) + error.append(node->toString()); throw error; } - if (m_allArtifacts.contains(artifact)) - return; + if (m_allNodes.contains(node)) + return false; - m_artifactsInCurrentPath += artifact; - m_parent = artifact; - foreach (Artifact * const child, artifact->children) - visitArtifact(child); - m_artifactsInCurrentPath -= artifact; - m_allArtifacts += artifact; + m_nodesInCurrentPath += node; + m_parent = node; + foreach (BuildGraphNode * const child, node->children) + child->accept(this); + m_nodesInCurrentPath -= node; + m_allNodes += node; + return false; } -void CycleDetector::doVisit(Artifact *) { } - -QList<Artifact *> CycleDetector::cycle(Artifact *doubleEntry) +QList<BuildGraphNode *> CycleDetector::cycle(BuildGraphNode *doubleEntry) { - QList<Artifact *> path; + QList<BuildGraphNode *> path; findPath(doubleEntry, m_parent, path); return path << doubleEntry; } diff --git a/src/lib/corelib/buildgraph/cycledetector.h b/src/lib/corelib/buildgraph/cycledetector.h index 6e55efac3..690b3509e 100644 --- a/src/lib/corelib/buildgraph/cycledetector.h +++ b/src/lib/corelib/buildgraph/cycledetector.h @@ -29,7 +29,8 @@ #ifndef QBS_CYCLEDETECTOR_H #define QBS_CYCLEDETECTOR_H -#include "artifactvisitor.h" +#include "buildgraphvisitor.h" +#include <language/forward_decls.h> #include <logging/logger.h> #include <QSet> @@ -37,23 +38,27 @@ namespace qbs { namespace Internal { -class CycleDetector : public ArtifactVisitor +class BuildGraphNode; + +class CycleDetector : private BuildGraphVisitor { public: CycleDetector(const Logger &logger); - void visitProject(const ResolvedProjectConstPtr &project); + void visitProject(const TopLevelProjectConstPtr &project); void visitProduct(const ResolvedProductConstPtr &product); - void visitArtifact(Artifact *artifact); private: - void doVisit(Artifact *artifact); + bool visit(Artifact *artifact); + bool visit(RuleNode *ruleNode); + + bool visitNode(BuildGraphNode *node); - QList<Artifact *> cycle(Artifact *doubleEntry); + QList<BuildGraphNode *> cycle(BuildGraphNode *doubleEntry); - QSet<Artifact *> m_allArtifacts; - QSet<Artifact *> m_artifactsInCurrentPath; - Artifact *m_parent; + QSet<BuildGraphNode *> m_allNodes; + QSet<BuildGraphNode *> m_nodesInCurrentPath; + BuildGraphNode *m_parent; Logger m_logger; }; diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp index 070b37d97..02e0d332a 100644 --- a/src/lib/corelib/buildgraph/executor.cpp +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -28,14 +28,15 @@ ****************************************************************************/ #include "executor.h" -#include "artifactvisitor.h" -#include "automoc.h" #include "buildgraph.h" +#include "command.h" #include "productbuilddata.h" #include "projectbuilddata.h" #include "cycledetector.h" #include "executorjob.h" #include "inputartifactscanner.h" +#include "rescuableartifactdata.h" +#include "rulenode.h" #include "rulesevaluationcontext.h" #include <buildgraph/transformer.h> @@ -57,41 +58,7 @@ namespace qbs { namespace Internal { -class MocEffortCalculator : public ArtifactVisitor -{ -public: - MocEffortCalculator() : ArtifactVisitor(Artifact::SourceFile), m_effort(0) {} - - int effort() const { return m_effort; } - -private: - void doVisit(Artifact *) { m_effort += 10; } - - int m_effort; -}; - -class BuildEffortCalculator : public ArtifactVisitor -{ -public: - BuildEffortCalculator() : ArtifactVisitor(Artifact::Generated), m_effort(0) {} - - int effort() const { return m_effort; } - - static int multiplier(const Artifact *artifact) { - return artifact->transformer->rule->multiplex ? 200 : 20; - } - -private: - void doVisit(Artifact *artifact) - { - m_effort += multiplier(artifact); - } - - int m_effort; -}; - - -bool Executor::ComparePriority::operator() (const Artifact *x, const Artifact *y) const +bool Executor::ComparePriority::operator() (const BuildGraphNode *x, const BuildGraphNode *y) const { return x->product->buildData->buildPriority < y->product->buildData->buildPriority; } @@ -106,10 +73,6 @@ Executor::Executor(const Logger &logger, QObject *parent) , m_doDebug(logger.debugEnabled()) { m_inputArtifactScanContext = new InputArtifactScannerContext(&m_scanResultCache); - m_autoMoc = new AutoMoc(logger); - connect(m_autoMoc, SIGNAL(reportCommandDescription(QString,QString)), - this, SIGNAL(reportCommandDescription(QString,QString))); - m_autoMoc->setScanResultCache(&m_scanResultCache); } Executor::~Executor() @@ -119,7 +82,6 @@ Executor::~Executor() delete job; foreach (ExecutorJob *job, m_processingJobs.keys()) delete job; - delete m_autoMoc; // delete before shared scan result cache delete m_inputArtifactScanContext; } @@ -225,6 +187,7 @@ void Executor::doBuild() } QBS_CHECK(m_state == ExecutorIdle); m_leaves = Leaves(); + m_changedSourceArtifacts.clear(); m_error.clear(); m_explicitlyCanceled = false; m_activeFileTags = FileTags::fromStringList(m_buildOptions.activeFileTags()); @@ -246,14 +209,11 @@ void Executor::doBuild() addExecutorJobs(); - bool sourceFilesChanged = false; - prepareAllArtifacts(&sourceFilesChanged); + prepareAllNodes(); prepareProducts(); setupRootNodes(); - prepareReachableArtifacts(); - setupProgressObserver(sourceFilesChanged); - if (sourceFilesChanged) - runAutoMoc(); + prepareReachableNodes(); + setupProgressObserver(); initLeaves(); if (!scheduleJobs()) { m_logger.qbsTrace() << "Nothing to do at all, finishing."; @@ -266,12 +226,12 @@ void Executor::setBuildOptions(const BuildOptions &buildOptions) m_buildOptions = buildOptions; } -static void initArtifactsBottomUp(Artifact *artifact) +static void initArtifactsBottomUp(BuildGraphNode *node) { - if (artifact->buildState == Artifact::Untouched) + if (node->buildState == BuildGraphNode::Untouched) return; - artifact->buildState = Artifact::Buildable; - foreach (Artifact *parent, artifact->parents) + node->buildState = BuildGraphNode::Buildable; + foreach (BuildGraphNode *parent, node->parents) initArtifactsBottomUp(parent); } @@ -295,36 +255,37 @@ void Executor::initLeaves() changedArtifacts.end()); if (changedArtifacts.isEmpty()) { - QSet<Artifact *> seenArtifacts; - foreach (Artifact *root, m_roots) - initLeavesTopDown(root, seenArtifacts); + QSet<BuildGraphNode *> seenNodes; + foreach (BuildGraphNode *root, m_roots) + initLeavesTopDown(root, seenNodes); } else { - foreach (Artifact *artifact, changedArtifacts) { + foreach (BuildGraphNode *artifact, changedArtifacts) { m_leaves.push(artifact); initArtifactsBottomUp(artifact); } } } -void Executor::initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts) +void Executor::initLeavesTopDown(BuildGraphNode *node, QSet<BuildGraphNode *> &seenNodes) { - if (seenArtifacts.contains(artifact)) + if (seenNodes.contains(node)) return; - seenArtifacts += artifact; + seenNodes += node; // Artifacts that appear in the build graph after // prepareBuildGraph() has been called, must be initialized. - if (artifact->buildState == Artifact::Untouched) { - artifact->buildState = Artifact::Buildable; - if (artifact->artifactType == Artifact::SourceFile) + if (node->buildState == BuildGraphNode::Untouched) { + node->buildState = BuildGraphNode::Buildable; + Artifact *artifact = dynamic_cast<Artifact *>(node); + if (artifact && artifact->artifactType == Artifact::SourceFile) retrieveSourceFileTimestamp(artifact); } - if (artifact->children.isEmpty()) { - m_leaves.push(artifact); + if (node->children.isEmpty()) { + m_leaves.push(node); } else { - foreach (Artifact *child, artifact->children) - initLeavesTopDown(child, seenArtifacts); + foreach (BuildGraphNode *child, node->children) + initLeavesTopDown(child, seenNodes); } } @@ -333,9 +294,30 @@ bool Executor::scheduleJobs() { QBS_CHECK(m_state == ExecutorRunning); while (!m_leaves.empty() && !m_availableJobs.isEmpty()) { - Artifact * const artifact = m_leaves.top(); + BuildGraphNode * const nodeToBuild = m_leaves.top(); m_leaves.pop(); - buildArtifact(artifact); + + switch (nodeToBuild->buildState) { + case BuildGraphNode::Untouched: + QBS_ASSERT(!"untouched node in leaves list", /* ignore */); + break; + case BuildGraphNode::Buildable: + // This is the only state in which we want to build a node. + nodeToBuild->accept(this); + break; + case BuildGraphNode::Building: + if (m_doDebug) { + m_logger.qbsDebug() << "[EXEC] " << nodeToBuild->toString(); + m_logger.qbsDebug() << "[EXEC] node is currently being built. Skipping."; + } + break; + case BuildGraphNode::Built: + if (m_doDebug) { + m_logger.qbsDebug() << "[EXEC] " << nodeToBuild->toString(); + m_logger.qbsDebug() << "[EXEC] node already built. Skipping."; + } + break; + } } return !m_leaves.empty() || !m_processingJobs.isEmpty(); } @@ -363,11 +345,12 @@ bool Executor::isUpToDate(Artifact *artifact) const return false; } - foreach (Artifact *child, artifact->children) { - QBS_CHECK(child->timestamp().isValid()); + foreach (Artifact *childArtifact, ArtifactSet::fromNodeSet(artifact->children)) { + QBS_CHECK(childArtifact->timestamp().isValid()); if (m_doDebug) - m_logger.qbsDebug() << "[UTD] child timestamp " << child->timestamp().toString(); - if (artifact->timestamp() < child->timestamp()) + m_logger.qbsDebug() << "[UTD] child timestamp " + << childArtifact->timestamp().toString(); + if (artifact->timestamp() < childArtifact->timestamp()) return false; } @@ -416,13 +399,6 @@ void Executor::buildArtifact(Artifact *artifact) if (m_doDebug) m_logger.qbsDebug() << "[EXEC] " << relativeArtifactFileName(artifact); - // Skip artifacts that are already built. - if (artifact->buildState == Artifact::Built) { - if (m_doDebug) - m_logger.qbsDebug() << "[EXEC] artifact already built. Skipping."; - return; - } - // skip artifacts without transformer if (artifact->artifactType != Artifact::Generated) { // For source artifacts, that were not reachable when initializing the build, we must @@ -441,6 +417,11 @@ void Executor::buildArtifact(Artifact *artifact) // Every generated artifact must have a transformer. QBS_CHECK(artifact->transformer); + bool childrenAddedDueToRescue; + rescueOldBuildData(artifact, &childrenAddedDueToRescue); + if (childrenAddedDueToRescue && checkForUnbuiltDependencies(artifact)) + return; + // Skip if outputs of this transformer are already built. // That means we already ran the transformation. foreach (Artifact *sideBySideArtifact, artifact->transformer->outputs) { @@ -448,18 +429,18 @@ void Executor::buildArtifact(Artifact *artifact) continue; switch (sideBySideArtifact->buildState) { - case Artifact::Untouched: - case Artifact::Buildable: + case BuildGraphNode::Untouched: + case BuildGraphNode::Buildable: break; - case Artifact::Built: + case BuildGraphNode::Built: if (m_doDebug) m_logger.qbsDebug() << "[EXEC] Side by side artifact already finished. Skipping."; finishArtifact(artifact); return; - case Artifact::Building: + case BuildGraphNode::Building: if (m_doDebug) m_logger.qbsDebug() << "[EXEC] Side by side artifact processing. Skipping."; - artifact->buildState = Artifact::Building; + artifact->buildState = BuildGraphNode::Building; return; } } @@ -496,36 +477,11 @@ void Executor::buildArtifact(Artifact *artifact) scanner.scan(); // postpone the build of this artifact, if new dependencies found - if (scanner.newDependencyAdded()) { - bool buildingDependenciesFound = false; - QVector<Artifact *> unbuiltDependencies; - foreach (Artifact *dependency, artifact->children) { - switch (dependency->buildState) { - case Artifact::Untouched: - case Artifact::Buildable: - unbuiltDependencies += dependency; - break; - case Artifact::Building: - buildingDependenciesFound = true; - break; - case Artifact::Built: - // do nothing - break; - } - } - if (!unbuiltDependencies.isEmpty()) { - artifact->inputsScanned = false; - insertLeavesAfterAddingDependencies(unbuiltDependencies); - return; - } - if (buildingDependenciesFound) { - artifact->inputsScanned = false; - return; - } - } + if (scanner.newDependencyAdded() && checkForUnbuiltDependencies(artifact)) + return; ExecutorJob *job = m_availableJobs.takeFirst(); - artifact->buildState = Artifact::Building; + artifact->buildState = BuildGraphNode::Building; m_processingJobs.insert(job, artifact); Q_ASSERT_X(artifact->product, Q_FUNC_INFO, @@ -535,6 +491,69 @@ void Executor::buildArtifact(Artifact *artifact) job->run(artifact->transformer.data(), artifact->product); } +void Executor::executeRuleNode(RuleNode *ruleNode) +{ + ArtifactSet changedInputArtifacts; + if (ruleNode->rule()->isDynamic()) { + foreach (Artifact *artifact, m_changedSourceArtifacts) { + if (artifact->product != ruleNode->product) + continue; + if (ruleNode->rule()->acceptsAsInput(artifact)) + changedInputArtifacts += artifact; + } + foreach (Artifact *artifact, + ArtifactSet::fromNodeSet(ruleNode->product->buildData->nodes)) { + if (artifact->artifactType == Artifact::SourceFile) + continue; + if (artifact->timestampRetrieved && !isUpToDate(artifact) + && ruleNode->rule()->acceptsAsInput(artifact)) { + changedInputArtifacts += artifact; + } + } + } + + RuleNode::ApplicationResult result; + ruleNode->apply(m_logger, changedInputArtifacts, &result); + + if (result.upToDate) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] " << ruleNode->toString() + << " is up to date. Skipping."; + } else { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] " << ruleNode->toString(); + const QVector<BuildGraphNode *> &createdNodes = result.createdNodes; + const WeakPointer<ResolvedProduct> &product = ruleNode->product; + QSet<RuleNode *> parentRules; + if (!createdNodes.isEmpty()) { + foreach (BuildGraphNode *parent, ruleNode->parents) { + if (RuleNode *parentRule = dynamic_cast<RuleNode *>(parent)) + parentRules += parentRule; + } + } + foreach (BuildGraphNode *node, createdNodes) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] rule created " << node->toString(); + loggedConnect(node, ruleNode, m_logger); + Artifact *outputArtifact = dynamic_cast<Artifact *>(node); + if (!outputArtifact) + continue; + if (outputArtifact->fileTags.matches(product->fileTags)) + product->buildData->roots += outputArtifact; + + foreach (Artifact *inputArtifact, outputArtifact->transformer->inputs) + loggedConnect(ruleNode, inputArtifact, m_logger); + + foreach (RuleNode *parentRule, parentRules) + loggedConnect(parentRule, outputArtifact, m_logger); + } + insertLeavesAfterAddingDependencies(createdNodes); + } + finishNode(ruleNode); + if (m_progressObserver) + m_progressObserver->incrementProgressValue(); +} + void Executor::finishJob(ExecutorJob *job, bool success) { QBS_CHECK(job); @@ -570,27 +589,21 @@ void Executor::finishJob(ExecutorJob *job, bool success) } } -static bool allChildrenBuilt(Artifact *artifact) +static bool allChildrenBuilt(BuildGraphNode *node) { - foreach (Artifact *child, artifact->children) - if (child->buildState != Artifact::Built) + foreach (BuildGraphNode *child, node->children) + if (child->buildState != BuildGraphNode::Built) return false; return true; } -void Executor::finishArtifact(Artifact *leaf) +void Executor::finishNode(BuildGraphNode *leaf) { - QBS_CHECK(leaf); - - if (m_doTrace) - m_logger.qbsTrace() << "[EXEC] finishArtifact " << relativeArtifactFileName(leaf); - - leaf->buildState = Artifact::Built; - m_scanResultCache.remove(leaf->filePath()); - foreach (Artifact *parent, leaf->parents) { - if (parent->buildState != Artifact::Buildable) { + leaf->buildState = BuildGraphNode::Built; + foreach (BuildGraphNode *parent, leaf->parents) { + if (parent->buildState != BuildGraphNode::Buildable) { if (m_doTrace) { - m_logger.qbsTrace() << "[EXEC] parent " << relativeArtifactFileName(parent) + m_logger.qbsTrace() << "[EXEC] parent " << parent->toString() << " build state: " << toString(parent->buildState); } continue; @@ -599,48 +612,58 @@ void Executor::finishArtifact(Artifact *leaf) if (allChildrenBuilt(parent)) { m_leaves.push(parent); if (m_doTrace) { - m_logger.qbsTrace() << "[EXEC] finishArtifact adds leaf " - << relativeArtifactFileName(parent) << " " << toString(parent->buildState); + m_logger.qbsTrace() << "[EXEC] finishNode adds leaf " + << parent->toString() << " " << toString(parent->buildState); } } else { if (m_doTrace) { - m_logger.qbsTrace() << "[EXEC] parent " << relativeArtifactFileName(parent) + m_logger.qbsTrace() << "[EXEC] parent " << parent->toString() << " build state: " << toString(parent->buildState); } } } +} + +void Executor::finishArtifact(Artifact *leaf) +{ + QBS_CHECK(leaf); + if (m_doTrace) + m_logger.qbsTrace() << "[EXEC] finishArtifact " << relativeArtifactFileName(leaf); - if (leaf->transformer) + finishNode(leaf); + m_scanResultCache.remove(leaf->filePath()); + + if (leaf->transformer) { foreach (Artifact *sideBySideArtifact, leaf->transformer->outputs) - if (leaf != sideBySideArtifact && sideBySideArtifact->buildState == Artifact::Building) + if (leaf != sideBySideArtifact + && sideBySideArtifact->buildState == BuildGraphNode::Building) { finishArtifact(sideBySideArtifact); - - if (m_progressObserver && leaf->artifactType == Artifact::Generated) - m_progressObserver->incrementProgressValue(BuildEffortCalculator::multiplier(leaf)); + } + } } -void Executor::insertLeavesAfterAddingDependencies_recurse(Artifact *const artifact, - QSet<Artifact *> *seenArtifacts, Leaves *leaves) const +void Executor::insertLeavesAfterAddingDependencies_recurse(BuildGraphNode *const node, + QSet<BuildGraphNode *> *seenNodes, Leaves *leaves) const { - if (seenArtifacts->contains(artifact)) + if (seenNodes->contains(node)) return; - seenArtifacts->insert(artifact); + seenNodes->insert(node); - if (artifact->buildState == Artifact::Untouched) - artifact->buildState = Artifact::Buildable; + if (node->buildState == BuildGraphNode::Untouched) + node->buildState = BuildGraphNode::Buildable; bool isLeaf = true; - foreach (Artifact *child, artifact->children) { - if (child->buildState != Artifact::Built) { + foreach (BuildGraphNode *child, node->children) { + if (child->buildState != BuildGraphNode::Built) { isLeaf = false; - insertLeavesAfterAddingDependencies_recurse(child, seenArtifacts, leaves); + insertLeavesAfterAddingDependencies_recurse(child, seenNodes, leaves); } } if (isLeaf) { if (m_doDebug) - m_logger.qbsDebug() << "[EXEC] adding leaf " << relativeArtifactFileName(artifact); - leaves->push(artifact); + m_logger.qbsDebug() << "[EXEC] adding leaf " << node->toString(); + leaves->push(node); } } @@ -649,11 +672,11 @@ QString Executor::configString() const return tr(" for configuration %1").arg(m_project->id()); } -void Executor::insertLeavesAfterAddingDependencies(QVector<Artifact *> dependencies) +void Executor::insertLeavesAfterAddingDependencies(QVector<BuildGraphNode *> dependencies) { - QSet<Artifact *> seenArtifacts; - foreach (Artifact *dependency, dependencies) - insertLeavesAfterAddingDependencies_recurse(dependency, &seenArtifacts, &m_leaves); + QSet<BuildGraphNode *> seenNodes; + foreach (BuildGraphNode *dependency, dependencies) + insertLeavesAfterAddingDependencies_recurse(dependency, &seenNodes, &m_leaves); } void Executor::cancelJobs() @@ -665,20 +688,19 @@ void Executor::cancelJobs() job->cancel(); } -void Executor::setupProgressObserver(bool mocWillRun) +void Executor::setupProgressObserver() { if (!m_progressObserver) return; - MocEffortCalculator mocEffortCalculator; - BuildEffortCalculator buildEffortCalculator; - foreach (const ResolvedProductConstPtr &product, m_productsToBuild) - buildEffortCalculator.visitProduct(product); - if (mocWillRun) { - foreach (const ResolvedProductConstPtr &product, m_productsToBuild) - mocEffortCalculator.visitProduct(product); - } - m_mocEffort = mocEffortCalculator.effort(); - const int totalEffort = m_mocEffort + buildEffortCalculator.effort(); + int totalEffort = 1; // For the effort after the last rule application; + foreach (const ResolvedProductConstPtr &product, m_productsToBuild) { + QBS_CHECK(product->buildData); + foreach (const BuildGraphNode * const node, product->buildData->nodes) { + const RuleNode * const ruleNode = dynamic_cast<const RuleNode *>(node); + if (ruleNode) + ++totalEffort; + } + } m_progressObserver->initialize(tr("Building%1").arg(configString()), totalEffort); } @@ -721,27 +743,89 @@ void Executor::addExecutorJobs() } } -void Executor::runAutoMoc() +void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0) { - bool autoMocApplied = false; - foreach (const ResolvedProductPtr &product, m_productsToBuild) { - if (m_progressObserver && m_progressObserver->canceled()) - throw ErrorInfo(Tr::tr("Build canceled%1.").arg(configString())); - // HACK call the automoc thingy here only if we have use Qt/core module - foreach (const ResolvedModuleConstPtr &m, product->modules) { - if (m->name == QLatin1String("Qt/core")) { - autoMocApplied = true; - m_autoMoc->apply(product); - break; + if (childrenAdded) + *childrenAdded = false; + if (!artifact->oldDataPossiblyPresent) + return; + artifact->oldDataPossiblyPresent = false; + if (artifact->artifactType != Artifact::Generated) + return; + + ResolvedProduct * const product = artifact->product.data(); + AllRescuableArtifactData::Iterator it + = product->buildData->rescuableArtifactData.find(artifact->filePath()); + if (it == product->buildData->rescuableArtifactData.end()) + return; + + const RescuableArtifactData &rad = it.value(); + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] Attempting to rescue data of " + "artifact '%1'").arg(artifact->fileName()); + } + if (commandListsAreEqual(artifact->transformer->commands, rad.commands)) { + artifact->setTimestamp(rad.timeStamp); + ResolvedProductPtr pseudoProduct = ResolvedProduct::create(); + foreach (const RescuableArtifactData::ChildData &cd, rad.children) { + pseudoProduct->name = cd.productName; + Artifact * const child = lookupArtifact(pseudoProduct, m_project->buildData.data(), + cd.childFilePath, true); + if (!child || artifact->children.contains(child)) + continue; + if (childrenAdded) + *childrenAdded = true; + safeConnect(artifact, child, m_logger); + if (cd.addedByScanner) + artifact->childrenAddedByScanner << child; + } + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "Data was rescued."; + } else { + removeGeneratedArtifactFromDisk(artifact->filePath(), m_logger); + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "Transformer commands changed, data not rescued."; + } + product->buildData->rescuableArtifactData.erase(it); +} + +bool Executor::checkForUnbuiltDependencies(Artifact *artifact) +{ + bool buildingDependenciesFound = false; + QVector<BuildGraphNode *> unbuiltDependencies; + foreach (BuildGraphNode *dependency, artifact->children) { + switch (dependency->buildState) { + case BuildGraphNode::Untouched: + case BuildGraphNode::Buildable: + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[EXEC] unbuilt dependency: " + << dependency->toString(); + } + unbuiltDependencies += dependency; + break; + case BuildGraphNode::Building: { + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[EXEC] dependency in state 'Building': " + << dependency->toString(); } + buildingDependenciesFound = true; + break; + } + case BuildGraphNode::Built: + // do nothing + break; } } - if (autoMocApplied) { - foreach (const ResolvedProductConstPtr &product, m_productsToBuild) - CycleDetector(m_logger).visitProduct(product); + if (!unbuiltDependencies.isEmpty()) { + artifact->inputsScanned = false; + insertLeavesAfterAddingDependencies(unbuiltDependencies); + return true; } - if (m_progressObserver) - m_progressObserver->incrementProgressValue(m_mocEffort); + if (buildingDependenciesFound) { + artifact->inputsScanned = false; + return true; + } + return false; } void Executor::onProcessError(const qbs::ErrorInfo &err) @@ -790,12 +874,23 @@ void Executor::finish() QStringList unbuiltProductNames; foreach (const ResolvedProductPtr &product, m_productsToBuild) { - foreach (Artifact *artifact, product->buildData->targetArtifacts) { - if (artifact->buildState != Artifact::Built) { + foreach (BuildGraphNode *rootNode, product->buildData->roots) { + if (rootNode->buildState != BuildGraphNode::Built) { unbuiltProductNames += product->name; break; } } + if (!unbuiltProductNames.contains(product->name)) { + // Any element still left after a successful build has not been re-created + // by any rule and therefore does not exist anymore as an artifact. + foreach (const QString &filePath, product->buildData->rescuableArtifactData.keys()) + removeGeneratedArtifactFromDisk(filePath, m_logger); + product->buildData->rescuableArtifactData.clear(); + + // Similar logic applies for the artifacts scheduled for potential rule application. + product->buildData->addedArtifactsByFileTag.clear(); + product->buildData->removedArtifactsByFileTag.clear(); + } } if (unbuiltProductNames.isEmpty()) { @@ -813,58 +908,76 @@ void Executor::finish() emit finished(); } +bool Executor::visit(Artifact *artifact) +{ + buildArtifact(artifact); + return false; +} + +bool Executor::visit(RuleNode *ruleNode) +{ + executeRuleNode(ruleNode); + return false; +} + /** * Sets the state of all artifacts in the graph to "untouched". * This must be done before doing a build. * * Retrieves the timestamps of source artifacts. * - * This function sets *sourceFilesChanged to true, if the timestamp of a reachable source artifact - * changed. + * This function also fills the list of changed source files. */ -void Executor::prepareAllArtifacts(bool *sourceFilesChanged) +void Executor::prepareAllNodes() { foreach (const ResolvedProductPtr &product, m_productsToBuild) { - foreach (Artifact *artifact, product->buildData->artifacts) { - artifact->buildState = Artifact::Untouched; - artifact->inputsScanned = false; - artifact->timestampRetrieved = false; - - if (artifact->artifactType == Artifact::SourceFile) { - const FileTime oldTimestamp = artifact->timestamp(); - retrieveSourceFileTimestamp(artifact); - if (oldTimestamp != artifact->timestamp()) - *sourceFilesChanged = true; - } - - // Timestamps of file dependencies must be invalid for every build. - foreach (FileDependency *fileDependency, artifact->fileDependencies) - fileDependency->clearTimestamp(); + foreach (BuildGraphNode *node, product->buildData->nodes) { + node->buildState = BuildGraphNode::Untouched; + Artifact *artifact = dynamic_cast<Artifact *>(node); + if (artifact) + prepareArtifact(artifact); } } } +void Executor::prepareArtifact(Artifact *artifact) +{ + artifact->inputsScanned = false; + artifact->timestampRetrieved = false; + + if (artifact->artifactType == Artifact::SourceFile) { + const FileTime oldTimestamp = artifact->timestamp(); + retrieveSourceFileTimestamp(artifact); + if (oldTimestamp != artifact->timestamp()) + m_changedSourceArtifacts.append(artifact); + } + + // Timestamps of file dependencies must be invalid for every build. + foreach (FileDependency *fileDependency, artifact->fileDependencies) + fileDependency->clearTimestamp(); +} + /** * Walk the build graph top-down from the roots and for each reachable node N * - mark N as buildable. */ -void Executor::prepareReachableArtifacts() +void Executor::prepareReachableNodes() { - const Artifact::BuildState initialBuildState = m_buildOptions.changedFiles().isEmpty() - ? Artifact::Buildable : Artifact::Built; - foreach (Artifact *root, m_roots) - prepareReachableArtifacts_impl(root, initialBuildState); + const BuildGraphNode::BuildState initialBuildState = m_buildOptions.changedFiles().isEmpty() + ? BuildGraphNode::Buildable : BuildGraphNode::Built; + foreach (BuildGraphNode *root, m_roots) + prepareReachableNodes_impl(root, initialBuildState); } -void Executor::prepareReachableArtifacts_impl(Artifact *artifact, - const Artifact::BuildState buildState) +void Executor::prepareReachableNodes_impl(BuildGraphNode *node, + const BuildGraphNode::BuildState buildState) { - if (artifact->buildState != Artifact::Untouched) + if (node->buildState != BuildGraphNode::Untouched) return; - artifact->buildState = buildState; - foreach (Artifact *child, artifact->children) - prepareReachableArtifacts_impl(child, buildState); + node->buildState = buildState; + foreach (BuildGraphNode *child, node->children) + prepareReachableNodes_impl(child, buildState); } void Executor::prepareProducts() @@ -879,28 +992,29 @@ void Executor::setupRootNodes() { m_roots.clear(); foreach (const ResolvedProductPtr &product, m_productsToBuild) { - foreach (Artifact *targetArtifact, product->buildData->targetArtifacts) - m_roots += targetArtifact; + foreach (BuildGraphNode *root, product->buildData->roots) + m_roots += root; } } void Executor::updateBuildGraph(Artifact::BuildState buildState) { - QSet<Artifact *> seenArtifacts; - foreach (Artifact *root, m_roots) + QSet<BuildGraphNode *> seenArtifacts; + foreach (BuildGraphNode *root, m_roots) updateBuildGraph_impl(root, buildState, seenArtifacts); } -void Executor::updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts) +void Executor::updateBuildGraph_impl(BuildGraphNode *node, Artifact::BuildState buildState, + QSet<BuildGraphNode *> &seenNodes) { - if (seenArtifacts.contains(artifact)) + if (seenNodes.contains(node)) return; - seenArtifacts += artifact; - artifact->buildState = buildState; + seenNodes += node; + node->buildState = buildState; - foreach (Artifact *child, artifact->children) - updateBuildGraph_impl(child, buildState, seenArtifacts); + foreach (BuildGraphNode *child, node->children) + updateBuildGraph_impl(child, buildState, seenNodes); } void Executor::setState(ExecutorState s) diff --git a/src/lib/corelib/buildgraph/executor.h b/src/lib/corelib/buildgraph/executor.h index 446ed8c81..e402e303b 100644 --- a/src/lib/corelib/buildgraph/executor.h +++ b/src/lib/corelib/buildgraph/executor.h @@ -31,6 +31,7 @@ #define QBS_BUILDGRAPHEXECUTOR_H #include "forward_decls.h" +#include "buildgraphvisitor.h" #include <buildgraph/artifact.h> #include <buildgraph/scanresultcache.h> #include <language/forward_decls.h> @@ -46,13 +47,13 @@ namespace qbs { class ProcessResult; namespace Internal { -class AutoMoc; class ExecutorJob; class FileTime; class InputArtifactScannerContext; class ProgressObserver; +class RuleNode; -class Executor : public QObject +class Executor : public QObject, private BuildGraphVisitor { Q_OBJECT @@ -82,44 +83,52 @@ private slots: void finish(); private: + // BuildGraphVisitor implementation + bool visit(Artifact *artifact); + bool visit(RuleNode *ruleNode); + enum ExecutorState { ExecutorIdle, ExecutorRunning, ExecutorCanceling }; struct ComparePriority { - bool operator() (const Artifact *x, const Artifact *y) const; + bool operator() (const BuildGraphNode *x, const BuildGraphNode *y) const; }; - typedef std::priority_queue<Artifact *, std::vector<Artifact *>, ComparePriority> Leaves; + typedef std::priority_queue<Artifact *, std::vector<BuildGraphNode *>, ComparePriority> Leaves; void doBuild(); - void prepareAllArtifacts(bool *sourceFilesChanged); - void prepareReachableArtifacts(); - void prepareReachableArtifacts_impl(Artifact *artifact, const Artifact::BuildState buildState); + void prepareAllNodes(); + void prepareArtifact(Artifact *artifact); + void prepareReachableNodes(); + void prepareReachableNodes_impl(BuildGraphNode *node, const Artifact::BuildState buildState); void prepareProducts(); void setupRootNodes(); void updateBuildGraph(Artifact::BuildState buildState); - void updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts); + void updateBuildGraph_impl(BuildGraphNode *node, Artifact::BuildState buildState, QSet<BuildGraphNode *> &seenNodes); void initLeaves(); - void initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts); + void initLeavesTopDown(BuildGraphNode *artifact, QSet<BuildGraphNode *> &seenArtifacts); bool scheduleJobs(); void buildArtifact(Artifact *artifact); + void executeRuleNode(RuleNode *ruleNode); void finishJob(ExecutorJob *job, bool success); + void finishNode(BuildGraphNode *leaf); void finishArtifact(Artifact *artifact); void setState(ExecutorState); void addExecutorJobs(); - void runAutoMoc(); - void insertLeavesAfterAddingDependencies(QVector<Artifact *> dependencies); + void insertLeavesAfterAddingDependencies(QVector<BuildGraphNode *> dependencies); void cancelJobs(); - void setupProgressObserver(bool mocWillRun); + void setupProgressObserver(); void doSanityChecks(); void handleError(const ErrorInfo &error); + void rescueOldBuildData(Artifact *artifact, bool *childrenAdded); + bool checkForUnbuiltDependencies(Artifact *artifact); bool mustExecuteTransformer(const TransformerPtr &transformer) const; bool isUpToDate(Artifact *artifact) const; void retrieveSourceFileTimestamp(Artifact *artifact) const; FileTime recursiveFileTime(const QString &filePath) const; - void insertLeavesAfterAddingDependencies_recurse(Artifact *const artifact, - QSet<Artifact *> *seenArtifacts, Leaves *leaves) const; + void insertLeavesAfterAddingDependencies_recurse(BuildGraphNode * const node, + QSet<BuildGraphNode *> *seenNodes, Leaves *leaves) const; QString configString() const; RulesEvaluationContextPtr m_evalContext; @@ -131,12 +140,11 @@ private: ExecutorState m_state; TopLevelProjectPtr m_project; QList<ResolvedProductPtr> m_productsToBuild; - QList<Artifact *> m_roots; + QList<BuildGraphNode *> m_roots; Leaves m_leaves; + QList<Artifact *> m_changedSourceArtifacts; ScanResultCache m_scanResultCache; InputArtifactScannerContext *m_inputArtifactScanContext; - AutoMoc *m_autoMoc; - int m_mocEffort; ErrorInfo m_error; bool m_explicitlyCanceled; FileTags m_activeFileTags; diff --git a/src/lib/corelib/buildgraph/executorjob.cpp b/src/lib/corelib/buildgraph/executorjob.cpp index 3ad3df6d5..281d82e36 100644 --- a/src/lib/corelib/buildgraph/executorjob.cpp +++ b/src/lib/corelib/buildgraph/executorjob.cpp @@ -117,7 +117,7 @@ void ExecutorJob::runNextCommand() return; } - const AbstractCommand * const command = m_transformer->commands.at(m_currentCommandIdx); + const AbstractCommandPtr &command = m_transformer->commands.at(m_currentCommandIdx); switch (command->type()) { case AbstractCommand::ProcessCommandType: m_currentCommandExecutor = m_processCommandExecutor; @@ -129,7 +129,7 @@ void ExecutorJob::runNextCommand() qFatal("Missing implementation for command type %d", command->type()); } - m_currentCommandExecutor->start(m_transformer, command); + m_currentCommandExecutor->start(m_transformer, command.data()); } void ExecutorJob::onCommandError(const ErrorInfo &err) diff --git a/src/lib/corelib/buildgraph/forward_decls.h b/src/lib/corelib/buildgraph/forward_decls.h index 7f3986a9b..2baa74d6d 100644 --- a/src/lib/corelib/buildgraph/forward_decls.h +++ b/src/lib/corelib/buildgraph/forward_decls.h @@ -45,6 +45,15 @@ typedef QSharedPointer<const Transformer> TransformerConstPtr; class RulesEvaluationContext; typedef QSharedPointer<RulesEvaluationContext> RulesEvaluationContextPtr; +class AbstractCommand; +typedef QSharedPointer<AbstractCommand> AbstractCommandPtr; + +class ProcessCommand; +typedef QSharedPointer<ProcessCommand> ProcessCommandPtr; + +class JavaScriptCommand; +typedef QSharedPointer<JavaScriptCommand> JavaScriptCommandPtr; + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp index d1bae81dd..36defdcb0 100644 --- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp +++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp @@ -301,7 +301,7 @@ resolved: // file or conflict with the writing process. if (filePathsToScan) { Artifact *artifactDependency = dynamic_cast<Artifact *>(resolvedDependency->file); - if (!artifactDependency || artifactDependency->buildState != Artifact::Building) + if (!artifactDependency || artifactDependency->buildState != BuildGraphNode::Building) filePathsToScan->append(resolvedDependency->filePath); } handleDependency(*resolvedDependency); @@ -360,7 +360,7 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency) } else { if (m_artifact->children.contains(artifactDependency)) return; - if (insertIntoProduct && !product->buildData->artifacts.contains(artifactDependency)) + if (insertIntoProduct && !product->buildData->nodes.contains(artifactDependency)) insertArtifact(product, artifactDependency, m_logger); safeConnect(m_artifact, artifactDependency, m_logger); m_artifact->childrenAddedByScanner += artifactDependency; diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp index b51dd6475..2c1973d90 100644 --- a/src/lib/corelib/buildgraph/jscommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp @@ -84,7 +84,7 @@ public slots: setupScriptEngineForFile(scriptEngine, transformer->rule->prepareScript->fileContext, scope); setupScriptEngineForProduct(scriptEngine, transformer->product(), transformer->rule, scope, &observer); - transformer->setupInputs(scriptEngine, scope); + transformer->setupInputs(scope); transformer->setupOutputs(scriptEngine, scope); for (QVariantMap::const_iterator it = cmd->properties().constBegin(); diff --git a/src/lib/corelib/buildgraph/nodeset.cpp b/src/lib/corelib/buildgraph/nodeset.cpp new file mode 100644 index 000000000..176cab53c --- /dev/null +++ b/src/lib/corelib/buildgraph/nodeset.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "nodeset.h" + +#include "artifact.h" +#include "rulenode.h" +#include <language/language.h> // because of RulePtr +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +NodeSet::NodeSet() +{ +} + +NodeSet::NodeSet(const NodeSet &other) + : m_data(other.m_data) +{ +} + +NodeSet &NodeSet::unite(const NodeSet &other) +{ + m_data.insert(other.begin(), other.end()); + return *this; +} + +void NodeSet::remove(BuildGraphNode *node) +{ + m_data.erase(node); +} + +void NodeSet::load(PersistentPool &pool) +{ + clear(); + int i; + pool.stream() >> i; + for (; --i >= 0;) { + int t; + pool.stream() >> t; + BuildGraphNode *node = 0; + switch (static_cast<BuildGraphNode::Type>(t)) { + case BuildGraphNode::ArtifactNodeType: + node = pool.idLoad<Artifact>(); + break; + case BuildGraphNode::RuleNodeType: + node = pool.idLoad<RuleNode>(); + break; + } + QBS_CHECK(node); + insert(node); + } +} + +void NodeSet::store(PersistentPool &pool) const +{ + pool.stream() << count(); + for (NodeSet::const_iterator it = constBegin(); it != constEnd(); ++it) { + pool.stream() << int((*it)->type()); + pool.store(*it); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/nodeset.h b/src/lib/corelib/buildgraph/nodeset.h new file mode 100644 index 000000000..9bcf780ce --- /dev/null +++ b/src/lib/corelib/buildgraph/nodeset.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_NODESET_H +#define QBS_NODESET_H + +#include <set> +#include <cstddef> + +namespace qbs { +namespace Internal { + +class BuildGraphNode; +class PersistentPool; + +/** + * Set of build graph nodes. + * This is faster than QSet when iterating over the container. + */ +class NodeSet +{ +public: + NodeSet(); + NodeSet(const NodeSet &other); + + NodeSet &unite(const NodeSet &other); + + typedef std::set<BuildGraphNode *>::const_iterator const_iterator; + typedef std::set<BuildGraphNode *>::iterator iterator; + typedef BuildGraphNode * value_type; + + iterator begin() { return m_data.begin(); } + iterator end() { return m_data.end(); } + const_iterator begin() const { return m_data.begin(); } + const_iterator end() const { return m_data.end(); } + const_iterator constBegin() const { return m_data.begin(); } + const_iterator constEnd() const { return m_data.end(); } + + void insert(BuildGraphNode *node) + { + m_data.insert(node); + } + + void operator+=(BuildGraphNode *node) + { + insert(node); + } + + NodeSet &operator<<(BuildGraphNode *node) + { + insert(node); + return *this; + } + + void remove(BuildGraphNode *node); + + bool contains(BuildGraphNode *node) const + { + return m_data.find(node) != m_data.end(); + } + + void clear() + { + m_data.clear(); + } + + bool isEmpty() const + { + return m_data.empty(); + } + + int count() const + { + return (int)m_data.size(); + } + + void reserve(int) + { + // no-op + } + + bool operator==(const NodeSet &other) const { return m_data == other.m_data; } + bool operator!=(const NodeSet &other) const { return !(*this == other); } + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + + +private: + std::set<BuildGraphNode *> m_data; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_NODESET_H diff --git a/src/lib/corelib/buildgraph/productbuilddata.cpp b/src/lib/corelib/buildgraph/productbuilddata.cpp index 0b9f36ab1..cdd3874e7 100644 --- a/src/lib/corelib/buildgraph/productbuilddata.cpp +++ b/src/lib/corelib/buildgraph/productbuilddata.cpp @@ -29,6 +29,7 @@ #include "productbuilddata.h" #include "artifact.h" +#include "command.h" #include "projectbuilddata.h" #include <language/language.h> #include <logging/logger.h> @@ -41,19 +42,74 @@ namespace Internal { ProductBuildData::~ProductBuildData() { - qDeleteAll(artifacts); + qDeleteAll(nodes); +} + +ArtifactSet ProductBuildData::targetArtifacts() const +{ + return ArtifactSet::fromNodeSet(roots); +} + +static void loadArtifactSetByFileTag(PersistentPool &pool, + ProductBuildData::ArtifactSetByFileTag &s) +{ + int elemCount; + pool.stream() >> elemCount; + for (int i = 0; i < elemCount; ++i) { + QVariant fileTag; + pool.stream() >> fileTag; + ArtifactSet artifacts; + pool.loadContainer(artifacts); + s.insert(FileTag::fromSetting(fileTag), artifacts); + } } void ProductBuildData::load(PersistentPool &pool) { - pool.loadContainer(artifacts); - pool.loadContainer(targetArtifacts); + nodes.load(pool); + roots.load(pool); + int rescuableArtifactCount; + pool.stream() >> rescuableArtifactCount; + rescuableArtifactData.reserve(rescuableArtifactCount); + for (int i = 0; i < rescuableArtifactCount; ++i) { + const QString filePath = pool.idLoadString(); + RescuableArtifactData elem; + elem.load(pool); + rescuableArtifactData.insert(filePath, elem); + } + loadArtifactSetByFileTag(pool, addedArtifactsByFileTag); + loadArtifactSetByFileTag(pool, removedArtifactsByFileTag); +} + +static void storeArtifactSetByFileTag(PersistentPool &pool, + const ProductBuildData::ArtifactSetByFileTag &s) +{ + pool.stream() << s.count(); + ProductBuildData::ArtifactSetByFileTag::ConstIterator it; + for (it = s.constBegin(); it != s.constEnd(); ++it) { + pool.stream() << it.key().toSetting(); + pool.storeContainer(it.value()); + } } void ProductBuildData::store(PersistentPool &pool) const { - pool.storeContainer(artifacts); - pool.storeContainer(targetArtifacts); + nodes.store(pool); + roots.store(pool); + pool.stream() << rescuableArtifactData.count(); + for (AllRescuableArtifactData::ConstIterator it = rescuableArtifactData.constBegin(); + it != rescuableArtifactData.constEnd(); ++it) { + pool.storeString(it.key()); + it.value().store(pool); + } + storeArtifactSetByFileTag(pool, addedArtifactsByFileTag); + storeArtifactSetByFileTag(pool, removedArtifactsByFileTag); +} + +void addArtifactToSet(Artifact *artifact, ProductBuildData::ArtifactSetByFileTag &container) +{ + foreach (const FileTag &tag, artifact->fileTags) + container[tag] += artifact; } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/productbuilddata.h b/src/lib/corelib/buildgraph/productbuilddata.h index 1e4b4402e..10b4655be 100644 --- a/src/lib/corelib/buildgraph/productbuilddata.h +++ b/src/lib/corelib/buildgraph/productbuilddata.h @@ -30,8 +30,10 @@ #define QBS_PRODUCTBUILDDATA_H #include "artifactset.h" +#include "nodeset.h" +#include "rescuableartifactdata.h" +#include <language/filetags.h> #include <language/forward_decls.h> - #include <tools/persistentobject.h> #include <QList> @@ -39,6 +41,7 @@ namespace qbs { namespace Internal { + class Logger; class ProductBuildData : public PersistentObject @@ -46,17 +49,33 @@ class ProductBuildData : public PersistentObject public: ~ProductBuildData(); - QSet<Artifact *> targetArtifacts; - ArtifactSet artifacts; - QList<RuleConstPtr> topSortedRules; + ArtifactSet targetArtifacts() const; + NodeSet nodes; + NodeSet roots; + + // After change tracking, this is the relevant data of artifacts that were in the build data + // of the restored product, and will potentially be re-created by our rules. + // If and when that happens, the relevant data will be copied over to the newly created + // artifact. + AllRescuableArtifactData rescuableArtifactData; // Do not store, initialized in executor. Higher prioritized artifacts are built first. unsigned int buildPriority; + typedef QHash<FileTag, ArtifactSet> ArtifactSetByFileTag; + ArtifactSetByFileTag addedArtifactsByFileTag; + ArtifactSetByFileTag removedArtifactsByFileTag; + + // TODO: Serialize. + typedef QHash<RuleConstPtr, ArtifactSet> ArtifactSetByRule; + ArtifactSetByRule artifactsWithChangedInputsPerRule; + void load(PersistentPool &pool); void store(PersistentPool &pool) const; }; +void addArtifactToSet(Artifact *artifact, ProductBuildData::ArtifactSetByFileTag &container); + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/productinstaller.cpp b/src/lib/corelib/buildgraph/productinstaller.cpp index 867ad1370..c7df23d24 100644 --- a/src/lib/corelib/buildgraph/productinstaller.cpp +++ b/src/lib/corelib/buildgraph/productinstaller.cpp @@ -82,7 +82,7 @@ void ProductInstaller::install() QList<const Artifact *> artifactsToInstall; foreach (const ResolvedProductConstPtr &product, m_products) { QBS_CHECK(product->buildData); - foreach (const Artifact *artifact, product->buildData->artifacts) { + foreach (const Artifact *artifact, ArtifactSet::fromNodeSet(product->buildData->nodes)) { if (artifact->properties->qbsPropertyValue(QLatin1String("install")).toBool()) artifactsToInstall += artifact; } diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp index 8e3bfd3e0..96d0e925c 100644 --- a/src/lib/corelib/buildgraph/projectbuilddata.cpp +++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp @@ -30,8 +30,11 @@ #include "artifact.h" #include "buildgraph.h" +#include "buildgraphvisitor.h" #include "productbuilddata.h" #include "command.h" +#include "rulegraph.h" +#include "rulenode.h" #include "rulesapplicator.h" #include "rulesevaluationcontext.h" #include "transformer.h" @@ -111,36 +114,36 @@ static void disconnectArtifactChildren(Artifact *artifact, const Logger &logger) logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnectChildren: '%1'") .arg(relativeArtifactFileName(artifact)); } - foreach (Artifact * const child, artifact->children) + foreach (BuildGraphNode * const child, artifact->children) child->parents.remove(artifact); artifact->children.clear(); artifact->childrenAddedByScanner.clear(); } -static void disconnectArtifactParents(Artifact *artifact, ProjectBuildData *projectBuildData, - const Logger &logger) +static void disconnectArtifactParents(Artifact *artifact, const Logger &logger) { if (logger.traceEnabled()) { logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnectParents: '%1'") .arg(relativeArtifactFileName(artifact)); } - foreach (Artifact * const parent, artifact->parents) { + foreach (BuildGraphNode * const parent, artifact->parents) { parent->children.remove(artifact); - parent->childrenAddedByScanner.remove(artifact); - if (parent->transformer) { - parent->transformer->inputs.remove(artifact); - projectBuildData->artifactsThatMustGetNewTransformers += parent; + Artifact *parentArtifact = dynamic_cast<Artifact *>(parent); + if (parentArtifact) { + QBS_CHECK(parentArtifact->transformer); + parentArtifact->childrenAddedByScanner.remove(artifact); + parentArtifact->transformer->inputs.remove(artifact); + parentArtifact->product->registerArtifactWithChangedInputs(parentArtifact); } } artifact->parents.clear(); } -static void disconnectArtifact(Artifact *artifact, ProjectBuildData *projectBuildData, - const Logger &logger) +static void disconnectArtifact(Artifact *artifact, const Logger &logger) { disconnectArtifactChildren(artifact, logger); - disconnectArtifactParents(artifact, projectBuildData, logger); + disconnectArtifactParents(artifact, logger); } /*! @@ -154,13 +157,14 @@ void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact, { if (removedArtifacts) removedArtifacts->insert(artifact); - foreach (Artifact *parent, artifact->parents) { + + foreach (Artifact *parent, ArtifactSet::fromNodeSet(artifact->parents)) { bool removeParent = false; disconnect(parent, artifact, logger); if (parent->children.isEmpty()) { removeParent = true; } else if (parent->transformer) { - artifactsThatMustGetNewTransformers += parent; + parent->product->registerArtifactWithChangedInputs(parent); parent->transformer->inputs.remove(artifact); removeParent = parent->transformer->inputs.isEmpty(); } @@ -171,6 +175,7 @@ void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact, } const bool removeFromDisk = artifact->artifactType == Artifact::Generated; removeArtifact(artifact, logger, removeFromDisk, removeFromProduct); + } void ProjectBuildData::removeArtifact(Artifact *artifact, @@ -183,49 +188,18 @@ void ProjectBuildData::removeArtifact(Artifact *artifact, removeGeneratedArtifactFromDisk(artifact, logger); removeFromLookupTable(artifact); if (removeFromProduct) { - artifact->product->buildData->artifacts.remove(artifact); - artifact->product->buildData->targetArtifacts.remove(artifact); + artifact->product->buildData->nodes.remove(artifact); + artifact->product->buildData->roots.remove(artifact); } - disconnectArtifact(artifact, this, logger); - artifactsThatMustGetNewTransformers -= artifact; - isDirty = true; -} -void ProjectBuildData::updateNodesThatMustGetNewTransformer(const Logger &logger) -{ - RulesEvaluationContext::Scope s(evaluationContext.data()); - foreach (Artifact *artifact, artifactsThatMustGetNewTransformers) - updateNodeThatMustGetNewTransformer(artifact, logger); - artifactsThatMustGetNewTransformers.clear(); -} - -void ProjectBuildData::updateNodeThatMustGetNewTransformer(Artifact *artifact, const Logger &logger) -{ - QBS_CHECK(artifact->transformer); + // If removal is requested and the executor has not run since the time the artifact was last + // added, we must undo the "register" operation. + artifact->product->unregisterAddedArtifact(artifact); - if (logger.debugEnabled()) { - logger.qbsDebug() << "[BG] updating transformer for " - << relativeArtifactFileName(artifact); - } - - removeGeneratedArtifactFromDisk(artifact, logger); - artifact->autoMocTimestamp.clear(); - artifact->clearTimestamp(); - - const RuleConstPtr rule = artifact->transformer->rule; + disconnectArtifact(artifact, logger); + if (artifact->transformer) + artifact->product->unregisterArtifactWithChangedInputs(artifact); isDirty = true; - - QBS_CHECK(artifact->transformer); - foreach (Artifact * const sibling, artifact->transformer->outputs) - sibling->transformer.clear(); - - ArtifactsPerFileTagMap artifactsPerFileTag; - foreach (Artifact *input, artifact->children) { - foreach (const FileTag &fileTag, input->fileTags) - artifactsPerFileTag[fileTag] += input; - } - RulesApplicator rulesApplier(artifact->product, artifactsPerFileTag, logger); - rulesApplier.applyRule(rule); } void ProjectBuildData::load(PersistentPool &pool) @@ -275,10 +249,172 @@ void BuildDataResolver::resolveProductBuildDataForExistingProject(const TopLevel m_project = project; foreach (const ResolvedProductPtr &product, freshProducts) { if (product->enabled) - resolveProductBuildData(product); + resolveProductBuildDataForExistingProject(product); + } +} + +static QSet<ResolvedProductPtr> findDependentProducts(const ResolvedProductPtr &product) +{ + QSet<ResolvedProductPtr> result; + foreach (const ResolvedProductPtr &parent, product->topLevelProject()->allProducts()) { + if (parent->dependencies.contains(product)) + result += parent; + } + return result; +} + +class FindLeafRules : public BuildGraphVisitor +{ +public: + FindLeafRules() + { + } + + const QSet<RuleNode *> &apply(const ResolvedProductPtr &product) + { + m_result.clear(); + m_product = product; + foreach (BuildGraphNode *n, product->buildData->nodes) + n->accept(this); + return m_result; + } + +private: + virtual bool visit(Artifact *) + { + return false; + } + + virtual bool visit(RuleNode *node) + { + if (!hasChildRuleInThisProduct(node)) + m_result << node; + return false; + } + + bool hasChildRuleInThisProduct(const RuleNode *node) const + { + foreach (BuildGraphNode *c, node->children) { + if (c->product == m_product && c->type() == BuildGraphNode::RuleNodeType) + return true; + } + return false; + } + + ResolvedProductPtr m_product; + QSet<RuleNode *> m_result; +}; + +class FindRootRules : public BuildGraphVisitor +{ +public: + FindRootRules() + { + } + + const QList<RuleNode *> &apply(const ResolvedProductPtr &product) + { + m_result.clear(); + foreach (BuildGraphNode *n, product->buildData->roots) + n->accept(this); + return m_result; + } + +private: + virtual bool visit(Artifact *) + { + return true; + } + + virtual bool visit(RuleNode *node) + { + m_result << node; + return true; + } + + QList<RuleNode *> m_result; +}; + + +void BuildDataResolver::resolveProductBuildDataForExistingProject(const ResolvedProductPtr &product) +{ + resolveProductBuildData(product); + + // Connect the leaf rules of all dependent products to the root rules of this product. + const QList<RuleNode *> rootRules = FindRootRules().apply(product); + QSet<ResolvedProductPtr> dependents = findDependentProducts(product); + foreach (const ResolvedProductPtr &dependentProduct, dependents) { + foreach (RuleNode *leaf, FindLeafRules().apply(dependentProduct)) { + foreach (RuleNode *root, rootRules) { + loggedConnect(leaf, root, m_logger); + } + } } } +class CreateRuleNodes : public RuleGraphVisitor +{ +public: + CreateRuleNodes(const ResolvedProductPtr &product, const Logger &logger) + : m_product(product), m_logger(logger) + { + } + + const QSet<RuleNode *> &leaves() const + { + return m_leaves; + } + +private: + const ResolvedProductPtr &m_product; + const Logger &m_logger; + QHash<RuleConstPtr, RuleNode *> m_nodePerRule; + QSet<const Rule *> m_rulesOnPath; + QList<const Rule *> m_rulePath; + QSet<RuleNode *> m_leaves; + + void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) + { + if (m_rulesOnPath.contains(rule.data())) { + QString pathstr; + foreach (const Rule *r, m_rulePath) { + pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t') + + r->prepareScript->location.toString(); + } + throw ErrorInfo(Tr::tr("Cycle detected in rule dependencies: %1").arg(pathstr)); + } + m_rulesOnPath.insert(rule.data()); + m_rulePath.append(rule.data()); + RuleNode *node = m_nodePerRule.value(rule); + if (!node) { + node = new RuleNode; + m_leaves.insert(node); + m_nodePerRule.insert(rule, node); + node->product = m_product; + node->setRule(rule); + m_product->buildData->nodes += node; + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[BG] create " << node->toString() + << " for product " << m_product->name; + } + } + if (parentRule) { + RuleNode *parent = m_nodePerRule.value(parentRule); + QBS_CHECK(parent); + loggedConnect(parent, node, m_logger); + m_leaves.remove(parent); + } else { + m_product->buildData->roots += node; + } + } + + void endVisit(const RuleConstPtr &rule) + { + m_rulesOnPath.remove(rule.data()); + m_rulePath.removeLast(); + } +}; + void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &product) { if (product->buildData) @@ -308,6 +444,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc } qbsFileArtifact->fileTags.insert("qbs"); artifactsPerFileTag["qbs"].insert(qbsFileArtifact); + product->registerAddedArtifact(qbsFileArtifact); // read sources foreach (const SourceArtifactConstPtr &sourceArtifact, product->allEnabledFiles()) { @@ -316,6 +453,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc continue; // ignore duplicate artifacts Artifact *artifact = createArtifact(product, sourceArtifact, m_logger); + product->registerAddedArtifact(artifact); foreach (const FileTag &fileTag, artifact->fileTags) artifactsPerFileTag[fileTag].insert(artifact); } @@ -345,11 +483,12 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc outputArtifact->artifactType = Artifact::Generated; outputArtifact->transformer = transformer; transformer->outputs += outputArtifact; - product->buildData->targetArtifacts += outputArtifact; + product->buildData->roots += outputArtifact; foreach (Artifact *inputArtifact, inputArtifacts) safeConnect(outputArtifact, inputArtifact, m_logger); foreach (const FileTag &fileTag, outputArtifact->fileTags) artifactsPerFileTag[fileTag].insert(outputArtifact); + product->registerAddedArtifact(outputArtifact); RuleArtifactPtr ruleArtifact = RuleArtifact::create(); ruleArtifact->fileName = outputArtifact->filePath(); @@ -364,7 +503,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc PrepareScriptObserver observer(engine()); setupScriptEngineForProduct(engine(), product, transformer->rule, prepareScriptContext, &observer); - transformer->setupInputs(engine(), prepareScriptContext); + transformer->setupInputs(prepareScriptContext); transformer->setupOutputs(engine(), prepareScriptContext); transformer->createCommands(rtrafo->transform, evalContext(), ScriptEngine::argumentList(transformer->rule->prepareScript->argumentNames, @@ -387,8 +526,23 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc } } - RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules(); - addTargetArtifacts(product, artifactsPerFileTag, m_logger); + RuleGraph ruleGraph; + ruleGraph.build(product->rules, product->fileTags); + CreateRuleNodes crn(product, m_logger); + ruleGraph.accept(&crn); + + // Connect the leaf rules of this product to the root rules of all product dependencies. + foreach (const ResolvedProductConstPtr &dep, product->dependencies) { + if (!dep->buildData) + continue; + foreach (BuildGraphNode *depRoot, dep->buildData->roots) { + RuleNode *depRootRule = dynamic_cast<RuleNode *>(depRoot); + if (!depRootRule) + continue; + foreach (RuleNode *leafRule, crn.leaves()) + loggedConnect(leafRule, depRootRule, m_logger); + } + } } RulesEvaluationContextPtr BuildDataResolver::evalContext() const diff --git a/src/lib/corelib/buildgraph/projectbuilddata.h b/src/lib/corelib/buildgraph/projectbuilddata.h index efc756428..afbd363d5 100644 --- a/src/lib/corelib/buildgraph/projectbuilddata.h +++ b/src/lib/corelib/buildgraph/projectbuilddata.h @@ -29,7 +29,6 @@ #ifndef QBS_PROJECTBUILDDATA_H #define QBS_PROJECTBUILDDATA_H -#include "artifactset.h" #include "forward_decls.h" #include <language/forward_decls.h> #include <logging/logger.h> @@ -43,6 +42,8 @@ namespace qbs { namespace Internal { +class ArtifactSet; +class BuildGraphNode; class FileDependency; class FileResourceBase; class ScriptEngine; @@ -62,21 +63,21 @@ public: QList<FileResourceBase *> lookupFiles(const QString &dirPath, const QString &fileName) const; QList<FileResourceBase *> lookupFiles(const Artifact *artifact) const; void insertFileDependency(FileDependency *dependency); - void updateNodesThatMustGetNewTransformer(const Logger &logger); void removeArtifactAndExclusiveDependents(Artifact *artifact, const Logger &logger, bool removeFromProduct = true, ArtifactSet *removedArtifacts = 0); void removeArtifact(Artifact *artifact, const Logger &logger, bool removeFromDisk = true, bool removeFromProduct = true); + QSet<FileDependency *> fileDependencies; + + // do not serialize: RulesEvaluationContextPtr evaluationContext; - QSet<Artifact *> artifactsThatMustGetNewTransformers; bool isDirty; private: void load(PersistentPool &pool); void store(PersistentPool &pool) const; - void updateNodeThatMustGetNewTransformer(Artifact *artifact, const Logger &logger); typedef QHash<QString, QList<FileResourceBase *> > ResultsPerDirectory; typedef QHash<QString, ResultsPerDirectory> ArtifactLookupTable; @@ -95,6 +96,7 @@ public: const QList<ResolvedProductPtr> &freshProducts); private: + void resolveProductBuildDataForExistingProject(const ResolvedProductPtr &product); void resolveProductBuildData(const ResolvedProductPtr &product); RulesEvaluationContextPtr evalContext() const; ScriptEngine *engine() const; diff --git a/src/lib/corelib/buildgraph/qtmocscanner.cpp b/src/lib/corelib/buildgraph/qtmocscanner.cpp new file mode 100644 index 000000000..995250441 --- /dev/null +++ b/src/lib/corelib/buildgraph/qtmocscanner.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** 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 "qtmocscanner.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include "scanresultcache.h" +#include <tools/qbsassert.h> +#include <tools/scannerpluginmanager.h> +#include <tools/scripttools.h> + +#include <QScriptContext> +#include <QScriptEngine> +#include <QDebug> + +namespace qbs { +namespace Internal { + +QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue, + const Logger &logger) + : m_product(product) + , m_targetScriptValue(targetScriptValue) + , m_logger(logger) + , m_scanResultCache(new ScanResultCache) + , m_cppScanner(0) + , m_hppScanner(0) +{ + QScriptEngine *engine = targetScriptValue.engine(); + QScriptValue scannerObj = engine->newObject(); + targetScriptValue.setProperty(QLatin1String("QtMocScanner"), scannerObj); + QScriptValue applyFunction = engine->newFunction(&js_apply, this); + scannerObj.setProperty(QLatin1String("apply"), applyFunction); +} + +QtMocScanner::~QtMocScanner() +{ + m_targetScriptValue.setProperty(QLatin1String("QtMocScanner"), QScriptValue()); + delete m_scanResultCache; +} + +static ScanResultCache::Result runScanner(ScannerPlugin *scanner, const Artifact *artifact, + ScanResultCache *scanResultCache) +{ + ScanResultCache::Result scanResult = scanResultCache->value(artifact->filePath()); + if (!scanResult.valid) { + scanResult.valid = true; + void *opaq = scanner->open(artifact->filePath().utf16(), + ScanForDependenciesFlag | ScanForFileTagsFlag); + if (!opaq || !scanner->additionalFileTags) + return scanResult; + + int length = 0; + const char **szFileTagsFromScanner = scanner->additionalFileTags(opaq, &length); + if (szFileTagsFromScanner) { + for (int i = length; --i >= 0;) + scanResult.additionalFileTags += szFileTagsFromScanner[i]; + } + + forever { + int flags = 0; + const char *szOutFilePath = scanner->next(opaq, &length, &flags); + if (szOutFilePath == 0) + break; + QString includedFilePath = QString::fromLocal8Bit(szOutFilePath, length); + if (includedFilePath.isEmpty()) + continue; + bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG); + scanResult.deps += ScanResultCache::Dependency(includedFilePath, isLocalInclude); + } + + scanner->close(opaq); + scanResultCache->insert(artifact->filePath(), scanResult); + } + return scanResult; +} + +void QtMocScanner::findIncludedMocCppFiles() +{ + if (!m_includedMocCppFiles.isEmpty()) + return; + + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[QtMocScanner] looking for included moc_XXX.cpp files"; + + foreach (Artifact *artifact, m_product->lookupArtifactsByFileTag("cpp")) { + const ScanResultCache::Result scanResult + = runScanner(m_cppScanner, artifact, m_scanResultCache); + foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) { + QString includedFilePath = dependency.filePath(); + if (includedFilePath.startsWith("moc_") && includedFilePath.endsWith(".cpp")) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[QtMocScanner] " << artifact->fileName() + << " includes " << includedFilePath; + includedFilePath.remove(0, 4); + includedFilePath.chop(4); + m_includedMocCppFiles.insert(includedFilePath, artifact->fileName()); + } + } + } +} + +QScriptValue QtMocScanner::js_apply(QScriptContext *ctx, QScriptEngine *engine, void *data) +{ + QtMocScanner *that = reinterpret_cast<QtMocScanner *>(data); + QScriptValue input = ctx->argument(0); + return that->apply(engine, attachedPointer<Artifact>(input)); +} + +QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact) +{ + if (!m_cppScanner) { + QList<ScannerPlugin *> scanners = ScannerPluginManager::scannersForFileTag("cpp"); + QBS_CHECK(scanners.count() == 1); + m_cppScanner = scanners.first(); + scanners = ScannerPluginManager::scannersForFileTag("hpp"); + QBS_CHECK(scanners.count() == 1); + m_hppScanner = scanners.first(); + } + + findIncludedMocCppFiles(); + + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[QtMocScanner] scanning " << artifact->toString(); + + bool hasQObjectMacro = false; + bool mustCompile = false; + bool hasPluginMetaDataMacro = false; + const bool isHeaderFile = artifact->fileTags.contains("hpp"); + + ScannerPlugin * const scanner = isHeaderFile ? m_hppScanner : m_cppScanner; + const ScanResultCache::Result scanResult = runScanner(scanner, artifact, m_scanResultCache); + if (!scanResult.additionalFileTags.isEmpty()) { + if (isHeaderFile) { + if (scanResult.additionalFileTags.contains("moc_hpp")) + hasQObjectMacro = true; + if (scanResult.additionalFileTags.contains("moc_hpp_plugin")) { + hasQObjectMacro = true; + hasPluginMetaDataMacro = true; + } + if (!m_includedMocCppFiles.contains(FileInfo::completeBaseName(artifact->fileName()))) + mustCompile = true; + } else { + if (scanResult.additionalFileTags.contains("moc_cpp")) + hasQObjectMacro = true; + } + } + + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << "[QtMocScanner] hasQObjectMacro: " << hasQObjectMacro + << " mustCompile: " << mustCompile + << " hasPluginMetaDataMacro: " << hasPluginMetaDataMacro; + } + + QScriptValue obj = engine->newObject(); + obj.setProperty(QLatin1String("hasQObjectMacro"), hasQObjectMacro); + obj.setProperty(QLatin1String("mustCompile"), mustCompile); + obj.setProperty(QLatin1String("hasPluginMetaDataMacro"), hasPluginMetaDataMacro); + return obj; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/automoc.h b/src/lib/corelib/buildgraph/qtmocscanner.h index d57fc292e..60983fe06 100644 --- a/src/lib/corelib/buildgraph/automoc.h +++ b/src/lib/corelib/buildgraph/qtmocscanner.h @@ -27,69 +27,50 @@ ** ****************************************************************************/ -#ifndef QBS_AUTOMOC_H -#define QBS_AUTOMOC_H +#ifndef QBS_QTMOCSCANNER_H +#define QBS_QTMOCSCANNER_H -#include "forward_decls.h" - -#include <language/forward_decls.h> +#include <language/language.h> #include <logging/logger.h> -#include <QObject> +#include <QHash> +#include <QScriptValue> +#include <QString> + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE -struct ScannerPlugin; +class ScannerPlugin; namespace qbs { namespace Internal { -class FileTag; + +class Artifact; class ScanResultCache; -/** - * Scans cpp and hpp files for the Q_OBJECT / Q_GADGET macro and - * applies the corresponding rule then. - * Also scans the files for moc_XXX.cpp files to find out if we must - * compile and link a moc_XXX.cpp file or not. - * - * This whole thing is an ugly hack, I know. - */ -class AutoMoc : public QObject +class QtMocScanner { - Q_OBJECT - public: - AutoMoc(const Logger &logger, QObject *parent = 0); - - void setScanResultCache(ScanResultCache *scanResultCache); - void apply(const ResolvedProductPtr &product); - -signals: - void reportCommandDescription(const QString &highlight, const QString &message); - -private: - enum FileType - { - UnknownFileType, - HppFileType, - CppFileType - }; + explicit QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue, + const Logger &logger); + ~QtMocScanner(); private: - static QString generateMocFileName(Artifact *artifact, FileType fileType); - static FileType fileType(Artifact *artifact); - void scan(Artifact *artifact, FileType fileType, bool &hasQObjectMacro, - QSet<QString> &includedMocCppFiles); - bool isVictimOfMoc(Artifact *artifact, FileType fileType, FileTag &foundMocFileTag); - void unmoc(Artifact *artifact, const FileTag &mocFileTag); - const QList<ScannerPlugin *> &cppScanners() const; - const QList<ScannerPlugin *> &hppScanners() const; + void findIncludedMocCppFiles(); + static QScriptValue js_apply(QScriptContext *ctx, QScriptEngine *engine, void *data); + QScriptValue apply(QScriptEngine *engine, const Artifact *artifact); - mutable QList<ScannerPlugin *> m_cppScanners; - mutable QList<ScannerPlugin *> m_hppScanners; + const ResolvedProductPtr &m_product; + QScriptValue m_targetScriptValue; + const Logger &m_logger; ScanResultCache *m_scanResultCache; - Logger m_logger; + QHash<QString, QString> m_includedMocCppFiles; + ScannerPlugin *m_cppScanner; + ScannerPlugin *m_hppScanner; }; } // namespace Internal } // namespace qbs -#endif // QBS_AUTOMOC_H +#endif // QBS_QTMOCSCANNER_H diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.cpp b/src/lib/corelib/buildgraph/rescuableartifactdata.cpp new file mode 100644 index 000000000..314b84008 --- /dev/null +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 "rescuableartifactdata.h" + +#include "command.h" + +#include <tools/persistence.h> + +namespace qbs { +namespace Internal { + +RescuableArtifactData::~RescuableArtifactData() +{ +} + +void RescuableArtifactData::load(PersistentPool &pool) +{ + pool.stream() >> timeStamp; + + int c; + pool.stream() >> c; + for (int i = 0; i < c; ++i) { + ChildData cd; + pool.stream() >> cd.productName >> cd.childFilePath >> cd.addedByScanner; + children << cd; + } + + commands = loadCommandList(pool.stream()); +} + +void RescuableArtifactData::store(PersistentPool &pool) const +{ + pool.stream() << timeStamp; + + pool.stream() << children.count(); + foreach (const ChildData &cd, children) + pool.stream() << cd.productName << cd.childFilePath << cd.addedByScanner; + + storeCommandList(commands, pool.stream()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.h b/src/lib/corelib/buildgraph/rescuableartifactdata.h new file mode 100644 index 000000000..9c93b1cce --- /dev/null +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_RESCUABLEARTIFACTDATA_H +#define QBS_RESCUABLEARTIFACTDATA_H + +#include "forward_decls.h" + +#include <tools/filetime.h> +#include <tools/persistence.h> + +#include <QHash> +#include <QList> + +namespace qbs { +namespace Internal { + +class RescuableArtifactData : public PersistentObject +{ +public: + ~RescuableArtifactData(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + + struct ChildData + { + ChildData(const QString &p = QString(), const QString &c = QString(), + bool byScanner = false) + : productName(p), childFilePath(c), addedByScanner(byScanner) + {} + QString productName; + QString childFilePath; + bool addedByScanner; + }; + + FileTime timeStamp; + QList<ChildData> children; + QList<AbstractCommandPtr> commands; +}; +typedef QHash<QString, RescuableArtifactData> AllRescuableArtifactData; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp index 7a2e114c4..7098004ef 100644 --- a/src/lib/corelib/buildgraph/rulegraph.cpp +++ b/src/lib/corelib/buildgraph/rulegraph.cpp @@ -44,7 +44,7 @@ void RuleGraph::build(const QSet<RulePtr> &rules, const FileTags &productFileTag QMap<FileTag, QList<const Rule *> > inputFileTagToRule; m_artifacts.reserve(rules.count()); foreach (const RulePtr &rule, rules) { - foreach (const FileTag &fileTag, rule->staticOutputFileTags()) + foreach (const FileTag &fileTag, rule->collectedOutputFileTags()) m_outputFileTagToRule[fileTag].append(rule.data()); insert(rule); } @@ -58,8 +58,11 @@ void RuleGraph::build(const QSet<RulePtr> &rules, const FileTags &productFileTag inFileTags += rule->explicitlyDependsOn; foreach (const FileTag &fileTag, inFileTags) { inputFileTagToRule[fileTag].append(rule.data()); - foreach (const Rule * const consumingRule, m_outputFileTagToRule.value(fileTag)) { - connect(rule.data(), consumingRule); + foreach (const Rule * const producingRule, m_outputFileTagToRule.value(fileTag)) { + if (!producingRule->collectedOutputFileTags().matches( + rule->excludedAuxiliaryInputs)) { + connect(rule.data(), producingRule); + } } } } @@ -74,30 +77,11 @@ void RuleGraph::build(const QSet<RulePtr> &rules, const FileTags &productFileTag m_rootRules += r->ruleGraphId; } -QList<RuleConstPtr> RuleGraph::topSorted() +void RuleGraph::accept(RuleGraphVisitor *visitor) const { - QSet<int> rootRules = m_rootRules; - QList<RuleConstPtr> result; - foreach (int rootIndex, rootRules) { - RuleConstPtr rule = m_artifacts.at(rootIndex); - QSet<const Rule *> seenRules; - QList<const Rule *> rulePath; - result.append(topSort(rule, &seenRules, &rulePath)); - } - - // remove duplicates from the result of our post-order traversal - QSet<const Rule*> seenRules; - seenRules.reserve(result.count()); - for (int i = 0; i < result.count();) { - const Rule * const rule = result.at(i).data(); - if (seenRules.contains(rule)) - result.removeAt(i); - else - ++i; - seenRules.insert(rule); - } - - return result; + const RuleConstPtr nullParent; + foreach (int rootIndex, m_rootRules) + traverse(visitor, nullParent, m_artifacts.at(rootIndex)); } void RuleGraph::dump() const @@ -145,29 +129,13 @@ void RuleGraph::connect(const Rule *creatingRule, const Rule *consumingRule) m_children[creatingRule->ruleGraphId].append(consumingRule->ruleGraphId); } -QList<RuleConstPtr> RuleGraph::topSort(const RuleConstPtr &rule, QSet<const Rule *> *seenRules, - QList<const Rule *> *rulePath) +void RuleGraph::traverse(RuleGraphVisitor *visitor, const RuleConstPtr &parentRule, + const RuleConstPtr &rule) const { - if (seenRules->contains(rule.data())) { - QString pathstr; - foreach (const Rule *r, *rulePath) { - pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t') - + r->prepareScript->location.toString(); - } - throw ErrorInfo(Tr::tr("Cycle detected in rule dependencies: %1").arg(pathstr)); - } - - seenRules->insert(rule.data()); - rulePath->prepend(rule.data()); - - QList<RuleConstPtr> result; + visitor->visit(parentRule, rule); foreach (int childIndex, m_children.at(rule->ruleGraphId)) - result.append(topSort(m_artifacts.at(childIndex), seenRules, rulePath)); - - result.append(rule); - seenRules->remove(rule.data()); - rulePath->removeFirst(); - return result; + traverse(visitor, rule, m_artifacts.at(childIndex)); + visitor->endVisit(rule); } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/rulegraph.h b/src/lib/corelib/buildgraph/rulegraph.h index 2f9a42a33..375224449 100644 --- a/src/lib/corelib/buildgraph/rulegraph.h +++ b/src/lib/corelib/buildgraph/rulegraph.h @@ -42,13 +42,20 @@ namespace qbs { namespace Internal { +class RuleGraphVisitor +{ +public: + virtual void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) = 0; + virtual void endVisit(const RuleConstPtr &rule) { Q_UNUSED(rule); } +}; + class RuleGraph { public: RuleGraph(); void build(const QSet<RulePtr> &rules, const FileTags &productFileTag); - QList<RuleConstPtr> topSorted(); + void accept(RuleGraphVisitor *visitor) const; void dump() const; @@ -56,8 +63,8 @@ private: void dump_impl(QByteArray &indent, int rootIndex) const; int insert(const RulePtr &rule); void connect(const Rule *creatingRule, const Rule *consumingRule); - QList<RuleConstPtr> topSort(const RuleConstPtr &rule, QSet<const Rule *> *seenRules, - QList<const Rule *> *rulePath); + void traverse(RuleGraphVisitor *visitor, const RuleConstPtr &parentRule, + const RuleConstPtr &rule) const; private: QMap<FileTag, QList<const Rule*> > m_outputFileTagToRule; diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp new file mode 100644 index 000000000..3d79cdea3 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulenode.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** 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 "rulenode.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "buildgraphvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulesapplicator.h" +#include "transformer.h" +#include <language/language.h> +#include <logging/logger.h> + +namespace qbs { +namespace Internal { + +RuleNode::RuleNode() +{ +} + +RuleNode::~RuleNode() +{ +} + +void RuleNode::accept(BuildGraphVisitor *visitor) +{ + if (visitor->visit(this)) + acceptChildren(visitor); +} + +QString RuleNode::toString() const +{ + return QLatin1String("RULE ") + m_rule->toString(); +} + +void RuleNode::apply(const Logger &logger, const ArtifactSet &changedInputs, + ApplicationResult *result) +{ + bool hasAddedTags = false; + bool hasRemovedTags = false; + result->upToDate = changedInputs.isEmpty(); + + ProductBuildData::ArtifactSetByFileTag relevantArtifacts; + if (product->isMarkedForReapplication(m_rule)) { + QBS_CHECK(m_rule->multiplex); + result->upToDate = false; + product->unmarkForReapplication(m_rule); + if (logger.traceEnabled()) + logger.qbsTrace() << "[BG] rule is marked for reapplication " << m_rule->toString(); + + foreach (Artifact *artifact, ArtifactSet::fromNodeSet(product->buildData->nodes)) { + if (m_rule->acceptsAsInput(artifact)) + addArtifactToSet(artifact, relevantArtifacts); + } + } else { + foreach (const FileTag &tag, m_rule->inputs) { + if (product->addedArtifactsByFileTag(tag).count()) { + hasAddedTags = true; + result->upToDate = false; + } + if (product->removedArtifactsByFileTag(tag).count()) { + hasRemovedTags = true; + result->upToDate = false; + } + if (hasAddedTags && hasRemovedTags) + break; + } + + relevantArtifacts = product->buildData->addedArtifactsByFileTag; + if (!changedInputs.isEmpty()) { + foreach (Artifact *artifact, changedInputs) + addArtifactToSet(artifact, relevantArtifacts); + } + } + if (result->upToDate) + return; + if (hasRemovedTags) { + ArtifactSet outputArtifactsToRemove; + foreach (const FileTag &tag, m_rule->inputs) { + foreach (Artifact *artifact, product->removedArtifactsByFileTag(tag)) { + foreach (Artifact *parent, ArtifactSet::fromNodeSet(artifact->parents)) { + if (!parent->transformer || parent->transformer->rule != m_rule + || !parent->transformer->inputs.contains(artifact)) { + // parent was not created by our rule. + continue; + } + outputArtifactsToRemove += parent; + } + } + } + RulesApplicator::handleRemovedRuleOutputs(outputArtifactsToRemove, logger); + } + if (!relevantArtifacts.isEmpty()) { + RulesApplicator applicator(product, relevantArtifacts, logger); + result->createdNodes = applicator.applyRuleInEvaluationContext(m_rule); + foreach (BuildGraphNode *node, result->createdNodes) { + if (Artifact *artifact = dynamic_cast<Artifact *>(node)) + product->registerAddedArtifact(artifact); + } + } +} + +void RuleNode::load(PersistentPool &pool) +{ + BuildGraphNode::load(pool); + m_rule = pool.idLoadS<Rule>(); +} + +void RuleNode::store(PersistentPool &pool) const +{ + BuildGraphNode::store(pool); + pool.store(m_rule); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulenode.h b/src/lib/corelib/buildgraph/rulenode.h new file mode 100644 index 000000000..b4f17a405 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulenode.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_RULENODE_H +#define QBS_RULENODE_H + +#include "artifactset.h" +#include "buildgraphnode.h" +#include <language/forward_decls.h> + +#include <QVector> + +namespace qbs { +namespace Internal { + +class Logger; + +class RuleNode : public BuildGraphNode +{ +public: + RuleNode(); + ~RuleNode(); + + void setRule(const RuleConstPtr &rule) { m_rule = rule; } + const RuleConstPtr &rule() const { return m_rule; } + + Type type() const { return RuleNodeType; } + void accept(BuildGraphVisitor *visitor); + QString toString() const; + + struct ApplicationResult + { + bool upToDate; + QVector<BuildGraphNode *> createdNodes; + }; + + void apply(const Logger &logger, const ArtifactSet &changedInputs, ApplicationResult *result); + +protected: + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + +private: + RuleConstPtr m_rule; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULENODE_H diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp index 70dce38c1..43c4efb45 100644 --- a/src/lib/corelib/buildgraph/rulesapplicator.cpp +++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp @@ -32,6 +32,7 @@ #include "buildgraph.h" #include "productbuilddata.h" #include "projectbuilddata.h" +#include "qtmocscanner.h" #include "rulesevaluationcontext.h" #include "transformer.h" #include <jsextensions/moduleproperties.h> @@ -53,20 +54,31 @@ RulesApplicator::RulesApplicator(const ResolvedProductPtr &product, ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger) : m_product(product) , m_artifactsPerFileTag(artifactsPerFileTag) + , m_mocScanner(0) , m_logger(logger) { } -void RulesApplicator::applyAllRules() +RulesApplicator::~RulesApplicator() { + delete m_mocScanner; +} + +QVector<BuildGraphNode *> RulesApplicator::applyRuleInEvaluationContext(const RuleConstPtr &rule) +{ + m_createdArtifacts.clear(); RulesEvaluationContext::Scope s(m_product->topLevelProject()->buildData->evaluationContext.data()); - foreach (const RuleConstPtr &rule, m_product->topSortedRules()) - applyRule(rule); + applyRule(rule); + return m_createdArtifacts; } void RulesApplicator::applyRule(const RuleConstPtr &rule) { m_rule = rule; + if (rule->name == QLatin1String("QtCoreMocRule")) { + delete m_mocScanner; + m_mocScanner = new QtMocScanner(m_product, scope(), m_logger); + } QScriptValue prepareScriptContext = engine()->newObject(); PrepareScriptObserver observer(engine()); setupScriptEngineForFile(engine(), m_rule->prepareScript->fileContext, scope()); @@ -89,13 +101,44 @@ void RulesApplicator::applyRule(const RuleConstPtr &rule) } } +void RulesApplicator::handleRemovedRuleOutputs(ArtifactSet outputArtifactsToRemove, + const Logger &logger) +{ + ArtifactSet artifactsToRemove; + foreach (Artifact *removedArtifact, outputArtifactsToRemove) { + if (logger.traceEnabled()) { + logger.qbsTrace() << "[BG] dynamic rule removed output artifact " + << removedArtifact->toString(); + } + removedArtifact->product->topLevelProject() + ->buildData->removeArtifactAndExclusiveDependents(removedArtifact, logger, true, + &artifactsToRemove); + } + // parents of removed artifacts must update their transformers + foreach (Artifact *removedArtifact, artifactsToRemove) { + foreach (Artifact *parent, removedArtifact->parentArtifacts()) + parent->product->registerArtifactWithChangedInputs(parent); + } + qDeleteAll(artifactsToRemove); +} + static void copyProperty(const QString &name, const QScriptValue &src, QScriptValue dst) { dst.setProperty(name, src.property(name)); } -void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, - QScriptValue &prepareScriptContext) +static QStringList toStringList(const ArtifactSet &artifacts) +{ + QStringList lst; + foreach (const Artifact *artifact, artifacts) { + const QString str = artifact->filePath() + QLatin1String(" [") + + artifact->fileTags.toStringList().join(QLatin1String(", ")) + QLatin1Char(']'); + lst << str; + } + return lst; +} + +void RulesApplicator::doApply(ArtifactSet inputArtifacts, QScriptValue &prepareScriptContext) { evalContext()->checkForCancelation(); @@ -108,31 +151,49 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QList<QPair<const RuleArtifact *, Artifact *> > ruleArtifactArtifactMap; QList<Artifact *> outputArtifacts; - ArtifactSet usingArtifacts; if (!m_rule->usings.isEmpty()) { const FileTags usingsFileTags = m_rule->usings; foreach (const ResolvedProductPtr &dep, m_product->dependencies) { QBS_CHECK(dep->buildData); ArtifactSet artifactsToCheck; - foreach (Artifact *targetArtifact, dep->buildData->targetArtifacts) + foreach (Artifact *targetArtifact, dep->buildData->targetArtifacts()) artifactsToCheck.unite(targetArtifact->transformer->outputs); foreach (Artifact *artifact, artifactsToCheck) { if (artifact->fileTags.matches(usingsFileTags)) - usingArtifacts.insert(artifact); + inputArtifacts.insert(artifact); } } } m_transformer.clear(); // create the output artifacts from the set of input artifacts + Transformer::setupInputs(prepareScriptContext, inputArtifacts, m_rule->module->name); + copyProperty(QLatin1String("inputs"), prepareScriptContext, scope()); + if (m_rule->multiplex) { + // ### awful! Revisit how the "input" property is set up! + copyProperty(QLatin1String("input"), prepareScriptContext, scope()); + } copyProperty(QLatin1String("product"), prepareScriptContext, scope()); copyProperty(QLatin1String("project"), prepareScriptContext, scope()); - foreach (const RuleArtifactConstPtr &ruleArtifact, m_rule->artifacts) { - Artifact * const outputArtifact = createOutputArtifact(ruleArtifact, inputArtifacts); - outputArtifacts << outputArtifact; - ruleArtifactArtifactMap << qMakePair(ruleArtifact.data(), outputArtifact); + if (m_rule->isDynamic()) { + outputArtifacts = runOutputArtifactsScript(inputArtifacts, + ScriptEngine::argumentList(m_rule->outputArtifactsScript->argumentNames, + scope())); + ArtifactSet newOutputs = ArtifactSet::fromNodeList(outputArtifacts); + const ArtifactSet oldOutputs = collectOldOutputArtifacts(inputArtifacts); + handleRemovedRuleOutputs(oldOutputs - newOutputs, m_logger); + } else { + foreach (const RuleArtifactConstPtr &ruleArtifact, m_rule->artifacts) { + Artifact * const outputArtifact + = createOutputArtifactFromRuleArtifact(ruleArtifact, inputArtifacts); + outputArtifacts << outputArtifact; + ruleArtifactArtifactMap << qMakePair(ruleArtifact.data(), outputArtifact); + } } + if (outputArtifacts.isEmpty()) + return; + foreach (Artifact *outputArtifact, outputArtifacts) { // insert the output artifacts into the pool of artifacts foreach (const FileTag &fileTag, outputArtifact->fileTags) @@ -143,21 +204,12 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, foreach (Artifact *dependency, m_artifactsPerFileTag.value(fileTag)) loggedConnect(outputArtifact, dependency, m_logger); - // Transformer setup - for (ArtifactSet::const_iterator it = usingArtifacts.constBegin(); - it != usingArtifacts.constEnd(); ++it) - { - Artifact *dep = *it; - loggedConnect(outputArtifact, dep, m_logger); - m_transformer->inputs.insert(dep); - } m_transformer->outputs.insert(outputArtifact); - - m_product->topLevelProject()->buildData->artifactsThatMustGetNewTransformers - -= outputArtifact; + outputArtifact->product->unregisterArtifactWithChangedInputs(outputArtifact); } - m_transformer->setupInputs(engine(), prepareScriptContext); + if (inputArtifacts != m_transformer->inputs) + m_transformer->setupInputs(prepareScriptContext); // change the transformer outputs according to the bindings in Artifact QScriptValue scriptValue; @@ -227,21 +279,44 @@ void RulesApplicator::setupScriptEngineForArtifact(Artifact *artifact) scriptValue.setProperty(QLatin1String("baseName"), inBaseName); scriptValue.setProperty(QLatin1String("completeBaseName"), inCompleteBaseName); scriptValue.setProperty(QLatin1String("baseDir"), basedir); + scriptValue.setProperty(QLatin1String("fileTags"), + engine()->toScriptValue(artifact->fileTags.toStringList())); + attachPointerTo(scriptValue, artifact); scope().setProperty(QLatin1String("input"), scriptValue); Q_ASSERT_X(scriptValue.strictlyEquals(engine()->evaluate(QLatin1String("input"))), "BG", "The input object is not in current scope."); } -Artifact *RulesApplicator::createOutputArtifact(const RuleArtifactConstPtr &ruleArtifact, - const ArtifactSet &inputArtifacts) +ArtifactSet RulesApplicator::collectOldOutputArtifacts(const ArtifactSet &inputArtifacts) const +{ + ArtifactSet result; + foreach (Artifact *a, inputArtifacts) { + foreach (Artifact *p, a->parentArtifacts()) { + QBS_CHECK(p->transformer); + if (p->transformer->rule == m_rule && p->transformer->inputs.contains(a)) + result += p; + } + } + return result; +} + +Artifact *RulesApplicator::createOutputArtifactFromRuleArtifact( + const RuleArtifactConstPtr &ruleArtifact, const ArtifactSet &inputArtifacts) { QScriptValue scriptValue = engine()->evaluate(ruleArtifact->fileName); if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) throw ErrorInfo(Tr::tr("Error in Rule.Artifact fileName: ") + scriptValue.toString()); QString outputPath = scriptValue.toString(); + return createOutputArtifact(outputPath, ruleArtifact->fileTags, ruleArtifact->alwaysUpdated, + inputArtifacts); +} - // Don't let the output artifact "escape" its build dir +Artifact *RulesApplicator::createOutputArtifact(const QString &filePath, const FileTags &fileTags, + bool alwaysUpdated, const ArtifactSet &inputArtifacts) +{ + QString outputPath = filePath; + // don't let the output artifact "escape" its build dir outputPath.replace(QLatin1String(".."), QLatin1String("dotdot")); outputPath = resolveOutPath(outputPath); @@ -288,15 +363,17 @@ Artifact *RulesApplicator::createOutputArtifact(const RuleArtifactConstPtr &rule throw ErrorInfo(e); } } - outputArtifact->fileTags += ruleArtifact->fileTags; + outputArtifact->fileTags += fileTags; + outputArtifact->clearTimestamp(); } else { outputArtifact = new Artifact; outputArtifact->artifactType = Artifact::Generated; outputArtifact->setFilePath(outputPath); - outputArtifact->fileTags = ruleArtifact->fileTags; - outputArtifact->alwaysUpdated = ruleArtifact->alwaysUpdated; + outputArtifact->fileTags = fileTags; + outputArtifact->alwaysUpdated = alwaysUpdated; outputArtifact->properties = m_product->properties; insertArtifact(m_product, outputArtifact, m_logger); + m_createdArtifacts += outputArtifact; } if (outputArtifact->fileTags.isEmpty()) @@ -326,6 +403,47 @@ Artifact *RulesApplicator::createOutputArtifact(const RuleArtifactConstPtr &rule return outputArtifact; } +QList<Artifact *> RulesApplicator::runOutputArtifactsScript(const ArtifactSet &inputArtifacts, + const QScriptValueList &args) +{ + QList<Artifact *> lst; + QScriptValue fun = engine()->evaluate(m_rule->outputArtifactsScript->sourceCode); + if (!fun.isFunction()) + throw ErrorInfo(QLatin1String("Function expected."), + m_rule->outputArtifactsScript->location); + QScriptValue res = fun.call(QScriptValue(), args); + if (res.isError() || engine()->hasUncaughtException()) + throw ErrorInfo(Tr::tr("Error while calling Rule.outputArtifacts: %1").arg(res.toString()), + m_rule->outputArtifactsScript->location); + if (!res.isArray()) + throw ErrorInfo(Tr::tr("Rule.outputArtifacts must return an array of objects."), + m_rule->outputArtifactsScript->location); + const quint32 c = res.property(QLatin1String("length")).toUInt32(); + for (quint32 i = 0; i < c; ++i) + lst += createOutputArtifactFromScriptValue(res.property(i), inputArtifacts); + return lst; +} + +Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValue &obj, + const ArtifactSet &inputArtifacts) +{ + QBS_CHECK(obj.isObject()); + const QString filePath = obj.property(QLatin1String("filePath")).toVariant().toString(); + const FileTags fileTags = FileTags::fromStringList( + obj.property(QLatin1String("fileTags")).toVariant().toStringList()); + const QVariant alwaysUpdatedVar = obj.property(QLatin1String("alwaysUpdated")).toVariant(); + const bool alwaysUpdated = alwaysUpdatedVar.isValid() ? alwaysUpdatedVar.toBool() : true; + Artifact *output = createOutputArtifact(filePath, fileTags, alwaysUpdated, inputArtifacts); + const FileTags explicitlyDependsOn = FileTags::fromStringList( + obj.property(QLatin1String("explicitlyDependsOn")).toVariant().toStringList()); + foreach (const FileTag &tag, explicitlyDependsOn) { + foreach (Artifact *dependency, m_product->lookupArtifactsByFileTag(tag)) { + loggedConnect(output, dependency, m_logger); + } + } + return output; +} + QString RulesApplicator::resolveOutPath(const QString &path) const { QString buildDir = m_product->topLevelProject()->buildDirectory; diff --git a/src/lib/corelib/buildgraph/rulesapplicator.h b/src/lib/corelib/buildgraph/rulesapplicator.h index 9ea54dfe7..e5813834a 100644 --- a/src/lib/corelib/buildgraph/rulesapplicator.h +++ b/src/lib/corelib/buildgraph/rulesapplicator.h @@ -35,29 +35,41 @@ #include <language/forward_decls.h> #include <logging/logger.h> -#include <QMap> +#include <QHash> #include <QScriptValue> #include <QString> +#include <QVector> namespace qbs { namespace Internal { +class BuildGraphNode; +class QtMocScanner; class ScriptEngine; -typedef QMap<FileTag, ArtifactSet> ArtifactsPerFileTagMap; +typedef QHash<FileTag, ArtifactSet> ArtifactsPerFileTagMap; class RulesApplicator { public: RulesApplicator(const ResolvedProductPtr &product, ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger); - void applyAllRules(); + ~RulesApplicator(); + QVector<BuildGraphNode *> applyRuleInEvaluationContext(const RuleConstPtr &rule); void applyRule(const RuleConstPtr &rule); + static void handleRemovedRuleOutputs(ArtifactSet artifactsToRemove, const Logger &logger); private: - void doApply(const ArtifactSet &inputArtifacts, QScriptValue &prepareScriptContext); + void doApply(ArtifactSet inputArtifacts, QScriptValue &prepareScriptContext); void setupScriptEngineForArtifact(Artifact *artifact); - Artifact *createOutputArtifact(const RuleArtifactConstPtr &ruleArtifact, - const ArtifactSet &inputArtifacts); + ArtifactSet collectOldOutputArtifacts(const ArtifactSet &inputArtifacts) const; + Artifact *createOutputArtifactFromRuleArtifact(const RuleArtifactConstPtr &ruleArtifact, + const ArtifactSet &inputArtifacts); + Artifact *createOutputArtifact(const QString &filePath, const FileTags &fileTags, + bool alwaysUpdated, const ArtifactSet &inputArtifacts); + QList<Artifact *> runOutputArtifactsScript(const ArtifactSet &inputArtifacts, + const QScriptValueList &args); + Artifact *createOutputArtifactFromScriptValue(const QScriptValue &obj, + const ArtifactSet &inputArtifacts); QString resolveOutPath(const QString &path) const; RulesEvaluationContextPtr evalContext() const; ScriptEngine *engine() const; @@ -65,9 +77,11 @@ private: const ResolvedProductPtr m_product; ArtifactsPerFileTagMap &m_artifactsPerFileTag; + QVector<BuildGraphNode *> m_createdArtifacts; RuleConstPtr m_rule; TransformerPtr m_transformer; + QtMocScanner *m_mocScanner; Logger m_logger; }; diff --git a/src/lib/corelib/buildgraph/timestampsupdater.cpp b/src/lib/corelib/buildgraph/timestampsupdater.cpp index c22e9f96c..2ba6bc8ee 100644 --- a/src/lib/corelib/buildgraph/timestampsupdater.cpp +++ b/src/lib/corelib/buildgraph/timestampsupdater.cpp @@ -54,7 +54,7 @@ public: // For target artifacts, we have to update the on-disk timestamp, because // the executor will look at it. - foreach (Artifact * const targetArtifact, product->buildData->targetArtifacts) { + foreach (Artifact * const targetArtifact, product->buildData->targetArtifacts()) { if (FileInfo(targetArtifact->filePath()).exists()) QFile(targetArtifact->filePath()).open(QIODevice::WriteOnly | QIODevice::Append); } diff --git a/src/lib/corelib/buildgraph/transformer.cpp b/src/lib/corelib/buildgraph/transformer.cpp index becb110ba..011ff0fb0 100644 --- a/src/lib/corelib/buildgraph/transformer.cpp +++ b/src/lib/corelib/buildgraph/transformer.cpp @@ -48,7 +48,6 @@ Transformer::Transformer() Transformer::~Transformer() { - qDeleteAll(commands); } QScriptValue Transformer::translateFileConfig(QScriptEngine *scriptEngine, Artifact *artifact, const QString &defaultModuleName) @@ -92,9 +91,10 @@ ResolvedProductPtr Transformer::product() const return (*outputs.begin())->product; } -void Transformer::setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue) +void Transformer::setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, + const QString &defaultModuleName) { - const QString &defaultModuleName = rule->module->name; + QScriptEngine *const scriptEngine = targetScriptValue.engine(); QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName); targetScriptValue.setProperty(QLatin1String("inputs"), scriptValue); if (inputs.count() == 1) { @@ -109,6 +109,11 @@ void Transformer::setupInputs(QScriptEngine *scriptEngine, QScriptValue targetSc } } +void Transformer::setupInputs(QScriptValue targetScriptValue) +{ + setupInputs(targetScriptValue, inputs, rule->module->name); +} + void Transformer::setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue) { const QString &defaultModuleName = rule->module->name; @@ -126,17 +131,17 @@ void Transformer::setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetS } } -static AbstractCommand *createCommandFromScriptValue(const QScriptValue &scriptValue, - const CodeLocation &codeLocation) +static AbstractCommandPtr createCommandFromScriptValue(const QScriptValue &scriptValue, + const CodeLocation &codeLocation) { + AbstractCommandPtr cmdBase; if (scriptValue.isUndefined() || !scriptValue.isValid()) - return 0; - AbstractCommand *cmdBase = 0; + return cmdBase; QString className = scriptValue.property(QLatin1String("className")).toString(); if (className == QLatin1String("Command")) - cmdBase = new ProcessCommand; + cmdBase = ProcessCommand::create(); else if (className == QLatin1String("JavaScriptCommand")) - cmdBase = new JavaScriptCommand; + cmdBase = JavaScriptCommand::create(); if (cmdBase) cmdBase->fillFromScriptValue(&scriptValue, codeLocation); return cmdBase; @@ -162,20 +167,19 @@ void Transformer::createCommands(const ScriptFunctionConstPtr &script, CodeLocation(script->location.fileName(), script->location.line() + engine->uncaughtExceptionLineNumber() - 1)); - qDeleteAll(commands); commands.clear(); if (scriptValue.isArray()) { const int count = scriptValue.property(QLatin1String("length")).toInt32(); for (qint32 i = 0; i < count; ++i) { QScriptValue item = scriptValue.property(i); if (item.isValid() && !item.isUndefined()) { - AbstractCommand *cmd = createCommandFromScriptValue(item, script->location); + const AbstractCommandPtr cmd = createCommandFromScriptValue(item, script->location); if (cmd) commands += cmd; } } } else { - AbstractCommand *cmd = createCommandFromScriptValue(scriptValue, script->location); + const AbstractCommandPtr cmd = createCommandFromScriptValue(scriptValue, script->location); if (cmd) commands += cmd; } @@ -223,15 +227,7 @@ void Transformer::load(PersistentPool &pool) } propertiesRequestedFromArtifactInPrepareScript.insert(artifactName, list); } - int cmdType; - pool.stream() >> count; - commands.reserve(count); - while (--count >= 0) { - pool.stream() >> cmdType; - AbstractCommand *cmd = AbstractCommand::createByType(static_cast<AbstractCommand::CommandType>(cmdType)); - cmd->load(pool.stream()); - commands += cmd; - } + commands = loadCommandList(pool.stream()); } static void storePropertyList(PersistentPool &pool, const PropertyList &list) @@ -263,11 +259,7 @@ void Transformer::store(PersistentPool &pool) const pool.stream() << p.value; // kind is always PropertyInModule } } - pool.stream() << commands.count(); - foreach (AbstractCommand *cmd, commands) { - pool.stream() << int(cmd->type()); - cmd->store(pool.stream()); - } + storeCommandList(commands, pool.stream()); } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/transformer.h b/src/lib/corelib/buildgraph/transformer.h index 3ef30cc29..ee9ee4002 100644 --- a/src/lib/corelib/buildgraph/transformer.h +++ b/src/lib/corelib/buildgraph/transformer.h @@ -56,7 +56,7 @@ public: ArtifactSet inputs; // Subset of "children of all outputs". ArtifactSet outputs; RuleConstPtr rule; - QList<AbstractCommand *> commands; + QList<AbstractCommandPtr> commands; PropertyList propertiesRequestedInPrepareScript; PropertyList propertiesRequestedInCommands; QHash<QString, PropertyList> propertiesRequestedFromArtifactInPrepareScript; @@ -69,7 +69,9 @@ public: const QString &defaultModuleName); ResolvedProductPtr product() const; - void setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue); + static void setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, + const QString &defaultModuleName); + void setupInputs(QScriptValue targetScriptValue); void setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue); void createCommands(const ScriptFunctionConstPtr &script, const RulesEvaluationContextPtr &evalContext, const QScriptValueList &args); diff --git a/src/lib/corelib/buildgraph/tst_buildgraph.cpp b/src/lib/corelib/buildgraph/tst_buildgraph.cpp index a159c2938..4f2aa3ebf 100644 --- a/src/lib/corelib/buildgraph/tst_buildgraph.cpp +++ b/src/lib/corelib/buildgraph/tst_buildgraph.cpp @@ -73,7 +73,7 @@ ResolvedProductConstPtr TestBuildGraph::productWithDirectCycle() child->children.insert(root); const ResolvedProductPtr product = ResolvedProduct::create(); product->buildData.reset(new ProductBuildData); - product->buildData->targetArtifacts.insert(root); + product->buildData->roots.insert(root); return product; } @@ -88,7 +88,7 @@ ResolvedProductConstPtr TestBuildGraph::productWithLessDirectCycle() grandchild->children.insert(root); const ResolvedProductPtr product = ResolvedProduct::create(); product->buildData.reset(new ProductBuildData); - product->buildData->targetArtifacts << root; + product->buildData->roots << root; return product; } @@ -101,7 +101,7 @@ ResolvedProductConstPtr TestBuildGraph::productWithNoCycle() root2->children.insert(root); const ResolvedProductPtr product = ResolvedProduct::create(); product->buildData.reset(new ProductBuildData); - product->buildData->targetArtifacts << root << root2; + product->buildData->roots << root << root2; return product; } diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs index 6ebda9b2c..c2e84ca81 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -73,12 +73,13 @@ QbsLibrary { "artifactset.h", "artifactvisitor.cpp", "artifactvisitor.h", - "automoc.cpp", - "automoc.h", "buildgraph.cpp", "buildgraph.h", + "buildgraphnode.cpp", + "buildgraphnode.h", "buildgraphloader.cpp", "buildgraphloader.h", + "buildgraphvisitor.h", "command.cpp", "command.h", "cycledetector.cpp", @@ -93,6 +94,8 @@ QbsLibrary { "inputartifactscanner.h", "jscommandexecutor.cpp", "jscommandexecutor.h", + "nodeset.cpp", + "nodeset.h", "processcommandexecutor.cpp", "processcommandexecutor.h", "productbuilddata.cpp", @@ -101,8 +104,14 @@ QbsLibrary { "productinstaller.h", "projectbuilddata.cpp", "projectbuilddata.h", + "qtmocscanner.cpp", + "qtmocscanner.h", + "rescuableartifactdata.cpp", + "rescuableartifactdata.h", "rulegraph.cpp", "rulegraph.h", + "rulenode.cpp", + "rulenode.h", "rulesapplicator.cpp", "rulesapplicator.h", "rulesevaluationcontext.cpp", diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index 54d221d96..5b2b09bf8 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -364,10 +364,19 @@ void BuiltinDeclarations::addRuleItem() PropertyDeclaration decl(QLatin1String("multiplex"), PropertyDeclaration::Boolean); decl.initialValueSource = QLatin1String("false"); item << decl; + item << PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String); item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("outputFileTags"), PropertyDeclaration::StringList); + decl = PropertyDeclaration(QLatin1String("outputArtifacts"), PropertyDeclaration::Verbatim); + decl.functionArgumentNames + << QLatin1String("project") << QLatin1String("product") + << QLatin1String("inputs") << QLatin1String("input"); + item << decl; item << PropertyDeclaration(QLatin1String("usings"), PropertyDeclaration::StringList); item << PropertyDeclaration(QLatin1String("auxiliaryInputs"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("excludedAuxiliaryInputs"), + PropertyDeclaration::StringList); item << PropertyDeclaration(QLatin1String("explicitlyDependsOn"), PropertyDeclaration::StringList); item << prepareScriptProperty(); diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index 676551129..170d04863 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -35,6 +35,7 @@ #include <buildgraph/productbuilddata.h> #include <buildgraph/projectbuilddata.h> #include <buildgraph/rulegraph.h> // TODO: Move to language? +#include <buildgraph/transformer.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/hostosinfo.h> @@ -290,6 +291,11 @@ bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b) * This is mostly needed for diagnostics. */ +bool ScriptFunction::isValid() const +{ + return location.isValid(); +} + void ScriptFunction::load(PersistentPool &pool) { pool.stream() @@ -354,12 +360,18 @@ static bool modulesAreEqual(const ResolvedModuleConstPtr &m1, const ResolvedModu QString Rule::toString() const { - QStringList outputTagsSorted = staticOutputFileTags().toStringList(); + QStringList outputTagsSorted = collectedOutputFileTags().toStringList(); outputTagsSorted.sort(); QStringList inputTagsSorted = inputs.toStringList(); inputTagsSorted.sort(); - return QLatin1Char('[') + inputTagsSorted.join(QLatin1String(",")) + QLatin1String(" -> ") - + outputTagsSorted.join(QLatin1String(",")) + QLatin1Char(']'); + return QLatin1Char('[') + outputTagsSorted.join(QLatin1String(",")) + + QLatin1String("][") + + inputTagsSorted.join(QLatin1String(",")) + QLatin1Char(']'); +} + +bool Rule::acceptsAsInput(Artifact *artifact) const +{ + return artifact->fileTags.matches(inputs); } FileTags Rule::staticOutputFileTags() const @@ -370,13 +382,27 @@ FileTags Rule::staticOutputFileTags() const return result; } +FileTags Rule::collectedOutputFileTags() const +{ + return outputFileTags.isEmpty() ? staticOutputFileTags() : outputFileTags; +} + +bool Rule::isDynamic() const +{ + return outputArtifactsScript->isValid(); +} + void Rule::load(PersistentPool &pool) { + name = pool.idLoadString(); prepareScript = pool.idLoadS<ScriptFunction>(); + outputArtifactsScript = pool.idLoadS<ScriptFunction>(); module = pool.idLoadS<ResolvedModule>(); pool.stream() >> inputs + >> outputFileTags >> auxiliaryInputs + >> excludedAuxiliaryInputs >> usings >> explicitlyDependsOn >> multiplex; @@ -386,11 +412,15 @@ void Rule::load(PersistentPool &pool) void Rule::store(PersistentPool &pool) const { + pool.storeString(name); pool.store(prepareScript); + pool.store(outputArtifactsScript); pool.store(module); pool.stream() << inputs + << outputFileTags << auxiliaryInputs + << excludedAuxiliaryInputs << usings << explicitlyDependsOn << multiplex; @@ -407,6 +437,14 @@ ResolvedProduct::~ResolvedProduct() { } +void ResolvedProduct::accept(BuildGraphVisitor *visitor) const +{ + if (!buildData) + return; + foreach (BuildGraphNode * const node, buildData->roots) + node->accept(visitor); +} + /*! * \brief Returns all files of all groups as source artifacts. * This includes the expanded list of wildcards. @@ -661,19 +699,100 @@ void ResolvedProduct::setupRunEnvironment(ScriptEngine *engine, const QProcessEn topLevelProject(), env); } -const QList<RuleConstPtr> &ResolvedProduct::topSortedRules() const +void ResolvedProduct::registerAddedFileTag(const FileTag &fileTag, Artifact *artifact) +{ + QBS_CHECK(buildData); + QBS_CHECK(artifact->product == this); + if (buildData->removedArtifactsByFileTag.value(fileTag).contains(artifact)) { + buildData->removedArtifactsByFileTag[fileTag].remove(artifact); + return; + } + buildData->addedArtifactsByFileTag[fileTag].insert(artifact); +} + +void ResolvedProduct::registerAddedArtifact(Artifact *artifact) +{ + QBS_CHECK(buildData); + QBS_CHECK(artifact->product == this); + foreach (const FileTag &tag, artifact->fileTags) + registerAddedFileTag(tag, artifact); +} + +void ResolvedProduct::unregisterAddedArtifact(Artifact *artifact) +{ + ProductBuildData::ArtifactSetByFileTag::Iterator it + = buildData->addedArtifactsByFileTag.begin(); + while (it != buildData->addedArtifactsByFileTag.end()) { + ArtifactSet &artifacts = it.value(); + artifacts.remove(artifact); + if (artifacts.isEmpty()) + it = buildData->addedArtifactsByFileTag.erase(it); + else + ++it; + } +} + +void ResolvedProduct::registerRemovedFileTag(const FileTag &fileTag, Artifact *artifact) { QBS_CHECK(buildData); - if (buildData->topSortedRules.isEmpty()) { - RuleGraph ruleGraph; - ruleGraph.build(rules, fileTags); -// ruleGraph.dump(); - buildData->topSortedRules = ruleGraph.topSorted(); -// int i=0; -// foreach (RulePtr r, m_topSortedRules) -// qDebug() << ++i << r->toString() << (void*)r.data(); + QBS_CHECK(artifact->product == this); + if (buildData->addedArtifactsByFileTag.value(fileTag).contains(artifact)) { + buildData->addedArtifactsByFileTag[fileTag].remove(artifact); + return; + } + buildData->removedArtifactsByFileTag[fileTag].insert(artifact); +} + +void ResolvedProduct::registerArtifactWithChangedInputs(Artifact *artifact) +{ + QBS_CHECK(buildData); + QBS_CHECK(artifact->product == this); + QBS_CHECK(artifact->transformer); + if (artifact->transformer->rule->multiplex) { + // Reapplication of rules only makes sense for multiplex rules (e.g. linker). + buildData->artifactsWithChangedInputsPerRule[artifact->transformer->rule] += artifact; + } +} + +void ResolvedProduct::unregisterArtifactWithChangedInputs(Artifact *artifact) +{ + QBS_CHECK(buildData); + QBS_CHECK(artifact->product == this); + QBS_CHECK(artifact->transformer); + buildData->artifactsWithChangedInputsPerRule[artifact->transformer->rule] -= artifact; +} + +void ResolvedProduct::unmarkForReapplication(const RuleConstPtr &rule) +{ + QBS_CHECK(buildData); + buildData->artifactsWithChangedInputsPerRule.remove(rule); +} + +const ArtifactSet ResolvedProduct::addedArtifactsByFileTag(const FileTag &tag) const +{ + return buildData->addedArtifactsByFileTag.value(tag); +} + +const ArtifactSet ResolvedProduct::removedArtifactsByFileTag(const FileTag &tag) const +{ + return buildData->removedArtifactsByFileTag.value(tag); +} + +bool ResolvedProduct::isMarkedForReapplication(const RuleConstPtr &rule) const +{ + return !buildData->artifactsWithChangedInputsPerRule.value(rule).isEmpty(); +} + +ArtifactSet ResolvedProduct::lookupArtifactsByFileTag(const FileTag &tag) const +{ + QBS_CHECK(buildData); + // ### slow. improve. + ArtifactSet result; + foreach (Artifact * const a, ArtifactSet::fromNodeSet(buildData->nodes)) { + if (a->fileTags.contains(tag)) + result += a; } - return buildData->topSortedRules; + return result; } TopLevelProject *ResolvedProduct::topLevelProject() const @@ -684,13 +803,13 @@ TopLevelProject *ResolvedProduct::topLevelProject() const static QStringList findGeneratedFiles(const Artifact *base, const FileTags &tags) { QStringList result; - foreach (const Artifact *parent, base->parents) { + foreach (const Artifact *parent, base->parentArtifacts()) { if (tags.isEmpty() || parent->fileTags.matches(tags)) result << parent->filePath(); } if (result.isEmpty() || tags.isEmpty()) - foreach (const Artifact *parent, base->parents) + foreach (const Artifact *parent, base->parentArtifacts()) result << findGeneratedFiles(parent, tags); return result; @@ -702,7 +821,7 @@ QStringList ResolvedProduct::generatedFiles(const QString &baseFile, const FileT if (!data) return QStringList(); - foreach (const Artifact *art, data->artifacts) { + foreach (const Artifact *art, ArtifactSet::fromNodeSet(data->nodes)) { if (art->filePath() == baseFile) return findGeneratedFiles(art, tags); } @@ -713,6 +832,14 @@ ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(0) { } +void ResolvedProject::accept(BuildGraphVisitor *visitor) const +{ + foreach (const ResolvedProductPtr &product, products) + product->accept(visitor); + foreach (const ResolvedProjectPtr &subProject, subProjects) + subProject->accept(visitor); +} + TopLevelProject *ResolvedProject::topLevelProject() { if (m_topLevelProject) @@ -756,8 +883,13 @@ void ResolvedProject::load(PersistentPool &pool) for (; --count >= 0;) { ResolvedProductPtr rProduct = pool.idLoadS<ResolvedProduct>(); if (rProduct->buildData) { - foreach (Artifact * const a, rProduct->buildData->artifacts) - a->product = rProduct; + foreach (BuildGraphNode * const node, rProduct->buildData->nodes) { + node->product = rProduct; + + // restore parent links + foreach (BuildGraphNode *child, node->children) + child->parents.insert(node); + } } products.append(rProduct); } @@ -1097,8 +1229,11 @@ bool operator==(const Rule &r1, const Rule &r2) return r1.module->name == r2.module->name && r1.prepareScript->sourceCode == r2.prepareScript->sourceCode + && r1.outputArtifactsScript->sourceCode == r2.outputArtifactsScript->sourceCode && r1.inputs == r2.inputs + && r1.outputFileTags == r2.outputFileTags && r1.auxiliaryInputs == r2.auxiliaryInputs + && r1.excludedAuxiliaryInputs == r2.excludedAuxiliaryInputs && r1.usings == r2.usings && r1.explicitlyDependsOn == r2.explicitlyDependsOn && r1.multiplex == r2.multiplex; diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index b62c0a1ab..c1e4f63fb 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -34,6 +34,7 @@ #include "forward_decls.h" #include "jsimports.h" #include "propertymapinternal.h" +#include <buildgraph/artifactset.h> #include <buildgraph/forward_decls.h> #include <tools/codelocation.h> #include <tools/fileinfo.h> @@ -61,6 +62,7 @@ QT_END_NAMESPACE namespace qbs { namespace Internal { class BuildGraphLoader; +class BuildGraphVisitor; class FileTagger : public PersistentObject { @@ -235,6 +237,8 @@ public: ResolvedFileContextConstPtr fileContext; mutable QScriptValue scriptFunction; // cache + bool isValid() const; + private: ScriptFunction() {} @@ -279,19 +283,26 @@ public: static RulePtr create() { return RulePtr(new Rule); } ResolvedModuleConstPtr module; + QString name; ScriptFunctionPtr prepareScript; + FileTags outputFileTags; // unused, if artifacts is non-empty + ScriptFunctionPtr outputArtifactsScript; // unused, if artifacts is non-empty FileTags inputs; FileTags auxiliaryInputs; + FileTags excludedAuxiliaryInputs; FileTags usings; FileTags explicitlyDependsOn; bool multiplex; - QList<RuleArtifactPtr> artifacts; + QList<RuleArtifactPtr> artifacts; // unused, if outputFileTags/outputArtifactsScript is non-empty // members that we don't need to save int ruleGraphId; QString toString() const; + bool acceptsAsInput(Artifact *artifact) const; FileTags staticOutputFileTags() const; + FileTags collectedOutputFileTags() const; + bool isDynamic() const; private: Rule() : multiplex(false), ruleGraphId(-1) {} @@ -363,13 +374,25 @@ public: mutable QProcessEnvironment runEnvironment; // must not be saved QHash<QString, QString> executablePathCache; + void accept(BuildGraphVisitor *visitor) const; QList<SourceArtifactPtr> allFiles() const; QList<SourceArtifactPtr> allEnabledFiles() const; FileTags fileTagsForFileName(const QString &fileName) const; void setupBuildEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; void setupRunEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; - const QList<RuleConstPtr> &topSortedRules() const; + void registerAddedFileTag(const FileTag &fileTag, Artifact *artifact); + void registerAddedArtifact(Artifact *artifact); + void unregisterAddedArtifact(Artifact *artifact); + void registerRemovedFileTag(const FileTag &fileTag, Artifact *artifact); + void registerArtifactWithChangedInputs(Artifact *artifact); + void unregisterArtifactWithChangedInputs(Artifact *artifact); + void unmarkForReapplication(const RuleConstPtr &rule); + const ArtifactSet addedArtifactsByFileTag(const FileTag &tag) const; + const ArtifactSet removedArtifactsByFileTag(const FileTag &tag) const; + bool isMarkedForReapplication(const RuleConstPtr &rule) const; + ArtifactSet lookupArtifactsByFileTag(const FileTag &tag) const; + TopLevelProject *topLevelProject() const; QStringList generatedFiles(const QString &baseFile, const FileTags &tags) const; @@ -393,6 +416,8 @@ public: QList<ResolvedProjectPtr> subProjects; WeakPointer<ResolvedProject> parentProject; + void accept(BuildGraphVisitor *visitor) const; + void setProjectProperties(const QVariantMap &config) { m_projectProperties = config; } const QVariantMap &projectProperties() const { return m_projectProperties; } diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp index a5d952efd..4dcec436d 100644 --- a/src/lib/corelib/language/projectresolver.cpp +++ b/src/lib/corelib/language/projectresolver.cpp @@ -576,26 +576,44 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) RulePtr rule = Rule::create(); // read artifacts + bool hasArtifactChildren = false; bool hasAlwaysUpdatedArtifact = false; foreach (Item *child, item->children()) { if (Q_UNLIKELY(child->typeName() != QLatin1String("Artifact"))) throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), child->location()); + hasArtifactChildren = true; resolveRuleArtifact(rule, child, &hasAlwaysUpdatedArtifact); } - if (Q_UNLIKELY(!hasAlwaysUpdatedArtifact)) + if (Q_UNLIKELY(hasArtifactChildren && !hasAlwaysUpdatedArtifact)) throw ErrorInfo(Tr::tr("At least one output artifact of a rule " "must have alwaysUpdated set to true."), item->location()); + rule->name = m_evaluator->stringValue(item, QLatin1String("name")); rule->prepareScript = scriptFunctionValue(item, QLatin1String("prepare")); + rule->outputArtifactsScript = scriptFunctionValue(item, QLatin1String("outputArtifacts")); + if (rule->outputArtifactsScript->isValid()) { + if (hasArtifactChildren) + throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " + "that contain Artifact items."), + item->location()); + rule->outputFileTags = m_evaluator->fileTagsValue(item, "outputFileTags"); + if (rule->outputFileTags.isEmpty()) + throw ErrorInfo(Tr::tr("Rule.outputFileTags must be specified if " + "Rule.outputArtifacts is specified."), + item->location()); + } + rule->multiplex = m_evaluator->boolValue(item, QLatin1String("multiplex")); rule->inputs = m_evaluator->fileTagsValue(item, QLatin1String("inputs")); rule->usings = m_evaluator->fileTagsValue(item, QLatin1String("usings")); rule->auxiliaryInputs = m_evaluator->fileTagsValue(item, QLatin1String("auxiliaryInputs")); + rule->excludedAuxiliaryInputs + = m_evaluator->fileTagsValue(item, QLatin1String("excludedAuxiliaryInputs")); rule->explicitlyDependsOn = m_evaluator->fileTagsValue(item, QLatin1String("explicitlyDependsOn")); rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp index f9906ed01..cc751f889 100644 --- a/src/lib/corelib/tools/persistence.cpp +++ b/src/lib/corelib/tools/persistence.cpp @@ -40,7 +40,7 @@ namespace qbs { namespace Internal { -static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-61"; +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-62"; PersistentPool::PersistentPool(const Logger &logger) : m_logger(logger) { diff --git a/src/lib/qtprofilesetup/templates/core.qbs b/src/lib/qtprofilesetup/templates/core.qbs index 2b052d3d1..3199e0db5 100644 --- a/src/lib/qtprofilesetup/templates/core.qbs +++ b/src/lib/qtprofilesetup/templates/core.qbs @@ -203,50 +203,28 @@ Module { } Rule { - inputs: ["moc_cpp"] - - Artifact { - fileName: ModUtils.moduleProperty(product, "generatedFilesDir") - + '/' + input.completeBaseName + ".moc" - fileTags: ["hpp"] - } - - prepare: { - var cmd = new Command(Moc.fullPath(product), - Moc.args(product, input, output.fileName)); - cmd.description = 'moc ' + FileInfo.fileName(input.fileName); - cmd.highlight = 'codegen'; - return cmd; - } - } - - Rule { - inputs: ["moc_hpp"] - - Artifact { - fileName: ModUtils.moduleProperty(product, "generatedFilesDir") - + "/moc_" + input.completeBaseName + ".cpp" - fileTags: [ "cpp" ] - } - - prepare: { - var cmd = new Command(Moc.fullPath(product), - Moc.args(product, input, output.fileName)); - cmd.description = 'moc ' + FileInfo.fileName(input.fileName); - cmd.highlight = 'codegen'; - return cmd; - } - } - - Rule { - inputs: ["moc_hpp_inc"] - - Artifact { - fileName: ModUtils.moduleProperty(product, "generatedFilesDir") - + "/moc_" + input.completeBaseName + ".cpp" - fileTags: [ "hpp" ] + name: "QtCoreMocRule" + inputs: ["cpp", "hpp"] + auxiliaryInputs: ["qt_plugin_metadata"] + excludedAuxiliaryInputs: ["unmocable"] + outputFileTags: ["hpp", "cpp", "unmocable"] + outputArtifacts: { + var mocinfo = QtMocScanner.apply(input); + if (!mocinfo.hasQObjectMacro) + return []; + var artifact = { fileTags: ["unmocable"] }; + if (input.fileTags.contains("hpp")) { + artifact.filePath = ModUtils.moduleProperty(product, "generatedFilesDir") + + "/moc_" + input.completeBaseName + ".cpp"; + } else { + artifact.filePath = ModUtils.moduleProperty(product, "generatedFilesDir") + + '/' + input.completeBaseName + ".moc"; + } + artifact.fileTags.push(mocinfo.mustCompile ? "cpp" : "hpp"); + if (mocinfo.hasPluginMetaDataMacro) + artifact.explicitlyDependsOn = ["qt_plugin_metadata"]; + return [artifact]; } - prepare: { var cmd = new Command(Moc.fullPath(product), Moc.args(product, input, output.fileName)); diff --git a/src/plugins/scanner/cpp/cppscanner.cpp b/src/plugins/scanner/cpp/cppscanner.cpp index 2735aa378..4bee84dd3 100644 --- a/src/plugins/scanner/cpp/cppscanner.cpp +++ b/src/plugins/scanner/cpp/cppscanner.cpp @@ -257,7 +257,7 @@ static const char **additionalFileTags(void *opaq, int *size) { static const char *thMocCpp[] = { "moc_cpp" }; static const char *thMocHpp[] = { "moc_hpp" }; - static const char *thMocPluginHpp[] = { "moc_plugin_hpp" }; + static const char *thMocPluginHpp[] = { "moc_hpp_plugin" }; Opaq *opaque = static_cast<Opaq*>(opaq); if (opaque->hasQObjectMacro) { diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp index 7536f4302..3ef5d7364 100644 --- a/tests/auto/api/tst_api.cpp +++ b/tests/auto/api/tst_api.cpp @@ -326,6 +326,15 @@ void TestApi::changeContent() QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); } +static qbs::ErrorInfo forceRuleEvaluation(const qbs::Project project) +{ + qbs::BuildOptions buildOptions; + buildOptions.setDryRun(true); + QScopedPointer<qbs::BuildJob> buildJob(project.buildAllProducts(buildOptions)); + waitForFinished(buildJob.data()); + return buildJob->error(); +} + void TestApi::disabledInstallGroup() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); @@ -335,7 +344,11 @@ void TestApi::disabledInstallGroup() m_logSink, 0)); waitForFinished(job.data()); QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); - qbs::Project project = job->project(); + const qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + qbs::ProjectData projectData = project.projectData(); QCOMPARE(projectData.allProducts().count(), 1); qbs::ProductData product = projectData.allProducts().first(); @@ -358,6 +371,10 @@ void TestApi::fileTagsFilterOverride() waitForFinished(job.data()); QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + qbs::ProjectData projectData = project.projectData(); QCOMPARE(projectData.allProducts().count(), 1); const qbs::ProductData product = projectData.allProducts().first(); @@ -378,6 +395,10 @@ void TestApi::installableFiles() waitForFinished(job.data()); QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + qbs::ProjectData projectData = project.projectData(); QCOMPARE(projectData.allProducts().count(), 1); qbs::ProductData product = projectData.allProducts().first(); diff --git a/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/main.cpp b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/main.cpp new file mode 100644 index 000000000..940a7628d --- /dev/null +++ b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/main.cpp @@ -0,0 +1,7 @@ +#include "object.h" + +int main() +{ + Object o; + o.f(); +} diff --git a/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.cpp b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.cpp new file mode 100644 index 000000000..aab24f6c0 --- /dev/null +++ b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.cpp @@ -0,0 +1,13 @@ +#include "object.h" + +#include <QObject> + +// class InternalClass : public QObject +// { +// Q_OBJECT +// }; + +void Object::f() { } + + +// #include "object.moc" diff --git a/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.h b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.h new file mode 100644 index 000000000..37070b494 --- /dev/null +++ b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.h @@ -0,0 +1,4 @@ +class Object { +public: + void f(); +}; diff --git a/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/project.qbs b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/project.qbs new file mode 100644 index 000000000..6c8db1913 --- /dev/null +++ b/tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/project.qbs @@ -0,0 +1,7 @@ +import qbs + +CppApplication { + Depends { name: "Qt.core" } + files: ["main.cpp", "object.h", "object.cpp"] +} + diff --git a/tests/auto/blackbox/testdata/added-file-persistent/file.cpp b/tests/auto/blackbox/testdata/added-file-persistent/file.cpp new file mode 100644 index 000000000..8101b05dc --- /dev/null +++ b/tests/auto/blackbox/testdata/added-file-persistent/file.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/added-file-persistent/main.cpp b/tests/auto/blackbox/testdata/added-file-persistent/main.cpp new file mode 100644 index 000000000..1921f1feb --- /dev/null +++ b/tests/auto/blackbox/testdata/added-file-persistent/main.cpp @@ -0,0 +1,3 @@ +void f(); + +int main() { f(); } diff --git a/tests/auto/blackbox/testdata/added-file-persistent/project.qbs b/tests/auto/blackbox/testdata/added-file-persistent/project.qbs new file mode 100644 index 000000000..672886646 --- /dev/null +++ b/tests/auto/blackbox/testdata/added-file-persistent/project.qbs @@ -0,0 +1,8 @@ +import qbs + +CppApplication { + files: [ + 'main.cpp', + /* 'file.cpp' */ + ] +} diff --git a/tests/auto/blackbox/testdata/fileTagger/moc_cpp.qbs b/tests/auto/blackbox/testdata/fileTagger/moc_cpp.qbs index b56412bed..2bc02e23c 100644 --- a/tests/auto/blackbox/testdata/fileTagger/moc_cpp.qbs +++ b/tests/auto/blackbox/testdata/fileTagger/moc_cpp.qbs @@ -20,7 +20,7 @@ Project { Rule { inputs: ['text'] Artifact { - fileTags: ['cpp', 'moc_cpp'] + fileTags: ['cpp'] fileName: input.baseName + '.cpp' } prepare: { diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index d92896223..105af71a8 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -199,6 +199,63 @@ void TestBlackbox::initTestCase() ccp(testSourceDir, testDataDir); } +void TestBlackbox::addedFilePersistent() +{ + QDir::setCurrent(testDataDir + QLatin1String("/added-file-persistent")); + + // On the initial run, linking will fail. + QbsRunParameters failedRunParams; + failedRunParams.expectFailure = true; + QVERIFY(runQbs(failedRunParams) != 0); + + // Add a file. qbs must schedule it for rule application on the next build. + QFile projectFile("project.qbs"); + QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString())); + const QByteArray originalContent = projectFile.readAll(); + QByteArray addedFileContent = originalContent; + addedFileContent.replace("/* 'file.cpp' */", "'file.cpp'"); + projectFile.resize(0); + projectFile.write(addedFileContent); + waitForNewTimestamp(); + projectFile.flush(); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + + // Remove the file again. qbs must unschedule the rule application again. + // Consequently, the linking step must fail as in the initial run. + projectFile.resize(0); + projectFile.write(originalContent); + waitForNewTimestamp(); + projectFile.flush(); + QVERIFY(runQbs(failedRunParams) != 0); + + // Add the file again. qbs must schedule it for rule application on the next build. + projectFile.resize(0); + projectFile.write(addedFileContent); + waitForNewTimestamp(); + projectFile.close(); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + + // qbs must remember that a file was scheduled for rule application. The build must then + // succeed, as now all necessary symbols are linked in. + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::addQObjectMacroToCppFile() +{ + QDir::setCurrent(testDataDir + QLatin1String("/add-qobject-macro-to-cpp-file")); + QCOMPARE(runQbs(), 0); + + waitForNewTimestamp(); + QFile cppFile("object.cpp"); + QVERIFY2(cppFile.open(QIODevice::ReadWrite), qPrintable(cppFile.errorString())); + QByteArray contents = cppFile.readAll(); + contents.replace("// ", ""); + cppFile.resize(0); + cppFile.write(contents); + cppFile.close(); + QCOMPARE(runQbs(), 0); +} + void TestBlackbox::baseProperties() { QDir::setCurrent(testDataDir + QLatin1String("/baseProperties")); @@ -1331,22 +1388,6 @@ void TestBlackbox::propertyChanges() QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); QVERIFY(!m_qbsStdout.contains("generated.txt")); QVERIFY(m_qbsStdout.contains("Making output from input")); - - // Incremental build, irrelevant file tag of a rule in a module changed. - waitForNewTimestamp(); - QVERIFY(moduleFile.open(QIODevice::ReadWrite)); - contents = moduleFile.readAll(); - contents.replace("inputs: ['test-input']", "inputs: ['test-input', 'hupe']"); - moduleFile.resize(0); - moduleFile.write(contents); - moduleFile.close(); - QCOMPARE(runQbs(params), 0); - QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); - QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); - QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); - QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); - QVERIFY(!m_qbsStdout.contains("generated.txt")); - QVERIFY(!m_qbsStdout.contains("Making output from input")); } void TestBlackbox::disabledProduct() @@ -1388,7 +1429,6 @@ void TestBlackbox::dynamicLibs() void TestBlackbox::dynamicRuleOutputs() { - SKIP_TEST("QBS-370"); const QString testDir = testDataDir + "/dynamicRuleOutputs"; QDir::setCurrent(testDir); if (QFile::exists("work")) @@ -1585,6 +1625,30 @@ void TestBlackbox::inheritQbsSearchPaths() QCOMPARE(runQbs(), 0); } +void TestBlackbox::mocCppIncluded() +{ + QDir::setCurrent(testDataDir + "/moc_hpp_included"); + QCOMPARE(runQbs(), 0); // Initial build. + + // Touch header and try again. + waitForNewTimestamp(); + QFile headerFile("object.h"); + QVERIFY2(headerFile.open(QIODevice::WriteOnly | QIODevice::Append), + qPrintable(headerFile.errorString())); + headerFile.write("\n"); + headerFile.close(); + QCOMPARE(runQbs(), 0); + + // Touch cpp file and try again. + waitForNewTimestamp(); + QFile cppFile("object.cpp"); + QVERIFY2(cppFile.open(QIODevice::WriteOnly | QIODevice::Append), + qPrintable(cppFile.errorString())); + cppFile.write("\n"); + cppFile.close(); + QCOMPARE(runQbs(), 0); +} + void TestBlackbox::objC() { QDir::setCurrent(testDataDir + "/objc"); diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 0c516d497..b29b86c52 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -96,6 +96,8 @@ public slots: void initTestCase(); private slots: + void addedFilePersistent(); + void addQObjectMacroToCppFile(); void baseProperties(); void build_project_data(); void build_project(); @@ -117,6 +119,7 @@ private slots: void jsExtensionsPropertyList(); void jsExtensionsTextFile(); void inheritQbsSearchPaths(); + void mocCppIncluded(); void objC(); void properQuoting(); void propertiesBlocks(); |