aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoerg Bornemann <joerg.bornemann@digia.com>2014-02-10 18:08:01 +0100
committerJoerg Bornemann <joerg.bornemann@digia.com>2014-02-13 15:33:27 +0100
commite73f60919079fc7cb0f0ad6d50b1a364c7b0d2a6 (patch)
tree3ce15f1f9b336063d591999ce325ce28a6e21320
parenta3634a6bbb193c47cdec887a6b29356c979961aa (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>
-rw-r--r--doc/reference/items/rule.qdoc29
-rw-r--r--src/lib/corelib/api/project.cpp25
-rw-r--r--src/lib/corelib/buildgraph/artifact.cpp49
-rw-r--r--src/lib/corelib/buildgraph/artifact.h37
-rw-r--r--src/lib/corelib/buildgraph/artifactcleaner.cpp2
-rw-r--r--src/lib/corelib/buildgraph/artifactset.cpp38
-rw-r--r--src/lib/corelib/buildgraph/artifactset.h70
-rw-r--r--src/lib/corelib/buildgraph/artifactvisitor.cpp23
-rw-r--r--src/lib/corelib/buildgraph/artifactvisitor.h9
-rw-r--r--src/lib/corelib/buildgraph/automoc.cpp345
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp103
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.h23
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.pri13
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp130
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.h12
-rw-r--r--src/lib/corelib/buildgraph/buildgraphnode.cpp80
-rw-r--r--src/lib/corelib/buildgraph/buildgraphnode.h85
-rw-r--r--src/lib/corelib/buildgraph/buildgraphvisitor.h55
-rw-r--r--src/lib/corelib/buildgraph/command.cpp68
-rw-r--r--src/lib/corelib/buildgraph/command.h19
-rw-r--r--src/lib/corelib/buildgraph/cycledetector.cpp56
-rw-r--r--src/lib/corelib/buildgraph/cycledetector.h23
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp548
-rw-r--r--src/lib/corelib/buildgraph/executor.h42
-rw-r--r--src/lib/corelib/buildgraph/executorjob.cpp4
-rw-r--r--src/lib/corelib/buildgraph/forward_decls.h9
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp4
-rw-r--r--src/lib/corelib/buildgraph/jscommandexecutor.cpp2
-rw-r--r--src/lib/corelib/buildgraph/nodeset.cpp93
-rw-r--r--src/lib/corelib/buildgraph/nodeset.h122
-rw-r--r--src/lib/corelib/buildgraph/productbuilddata.cpp66
-rw-r--r--src/lib/corelib/buildgraph/productbuilddata.h27
-rw-r--r--src/lib/corelib/buildgraph/productinstaller.cpp2
-rw-r--r--src/lib/corelib/buildgraph/projectbuilddata.cpp268
-rw-r--r--src/lib/corelib/buildgraph/projectbuilddata.h10
-rw-r--r--src/lib/corelib/buildgraph/qtmocscanner.cpp189
-rw-r--r--src/lib/corelib/buildgraph/qtmocscanner.h (renamed from src/lib/corelib/buildgraph/automoc.h)73
-rw-r--r--src/lib/corelib/buildgraph/rescuableartifactdata.cpp70
-rw-r--r--src/lib/corelib/buildgraph/rescuableartifactdata.h72
-rw-r--r--src/lib/corelib/buildgraph/rulegraph.cpp62
-rw-r--r--src/lib/corelib/buildgraph/rulegraph.h13
-rw-r--r--src/lib/corelib/buildgraph/rulenode.cpp144
-rw-r--r--src/lib/corelib/buildgraph/rulenode.h76
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp178
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.h26
-rw-r--r--src/lib/corelib/buildgraph/timestampsupdater.cpp2
-rw-r--r--src/lib/corelib/buildgraph/transformer.cpp44
-rw-r--r--src/lib/corelib/buildgraph/transformer.h6
-rw-r--r--src/lib/corelib/buildgraph/tst_buildgraph.cpp6
-rw-r--r--src/lib/corelib/corelib.qbs13
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp9
-rw-r--r--src/lib/corelib/language/language.cpp171
-rw-r--r--src/lib/corelib/language/language.h29
-rw-r--r--src/lib/corelib/language/projectresolver.cpp20
-rw-r--r--src/lib/corelib/tools/persistence.cpp2
-rw-r--r--src/lib/qtprofilesetup/templates/core.qbs64
-rw-r--r--src/plugins/scanner/cpp/cppscanner.cpp2
-rw-r--r--tests/auto/api/tst_api.cpp23
-rw-r--r--tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/main.cpp7
-rw-r--r--tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.cpp13
-rw-r--r--tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/object.h4
-rw-r--r--tests/auto/blackbox/testdata/add-qobject-macro-to-cpp-file/project.qbs7
-rw-r--r--tests/auto/blackbox/testdata/added-file-persistent/file.cpp1
-rw-r--r--tests/auto/blackbox/testdata/added-file-persistent/main.cpp3
-rw-r--r--tests/auto/blackbox/testdata/added-file-persistent/project.qbs8
-rw-r--r--tests/auto/blackbox/testdata/fileTagger/moc_cpp.qbs2
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp98
-rw-r--r--tests/auto/blackbox/tst_blackbox.h3
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 &paramet
// 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 &paramet
} 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 &paramet
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();