aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/buildgraph
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/buildgraph')
-rw-r--r--src/lib/buildgraph/artifact.cpp97
-rw-r--r--src/lib/buildgraph/artifact.h112
-rw-r--r--src/lib/buildgraph/artifactlist.cpp57
-rw-r--r--src/lib/buildgraph/artifactlist.h109
-rw-r--r--src/lib/buildgraph/automoc.cpp273
-rw-r--r--src/lib/buildgraph/automoc.h86
-rw-r--r--src/lib/buildgraph/buildgraph.cpp1384
-rw-r--r--src/lib/buildgraph/buildgraph.h216
-rw-r--r--src/lib/buildgraph/buildgraph.pri26
-rw-r--r--src/lib/buildgraph/command.cpp231
-rw-r--r--src/lib/buildgraph/command.h154
-rw-r--r--src/lib/buildgraph/commandexecutor.cpp376
-rw-r--r--src/lib/buildgraph/commandexecutor.h106
-rw-r--r--src/lib/buildgraph/executor.cpp885
-rw-r--r--src/lib/buildgraph/executor.h147
-rw-r--r--src/lib/buildgraph/executorjob.cpp128
-rw-r--r--src/lib/buildgraph/executorjob.h86
-rw-r--r--src/lib/buildgraph/rulegraph.cpp201
-rw-r--r--src/lib/buildgraph/rulegraph.h77
-rw-r--r--src/lib/buildgraph/scanresultcache.cpp56
-rw-r--r--src/lib/buildgraph/scanresultcache.h71
-rw-r--r--src/lib/buildgraph/transformer.cpp119
-rw-r--r--src/lib/buildgraph/transformer.h85
23 files changed, 5082 insertions, 0 deletions
diff --git a/src/lib/buildgraph/artifact.cpp b/src/lib/buildgraph/artifact.cpp
new file mode 100644
index 000000000..d7cf5e60d
--- /dev/null
+++ b/src/lib/buildgraph/artifact.cpp
@@ -0,0 +1,97 @@
+/*************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).*
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+** GNU Lesser General Public License Usage
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "artifact.h"
+#include "transformer.h"
+#include "buildgraph.h"
+
+QT_BEGIN_NAMESPACE
+
+static QDataStream &operator >>(QDataStream &s, qbs::Artifact::ArtifactType &t)
+{
+ int i;
+ s >> i;
+ t = static_cast<qbs::Artifact::ArtifactType>(i);
+ return s;
+}
+
+static QDataStream &operator <<(QDataStream &s, const qbs::Artifact::ArtifactType &t)
+{
+ return s << (int)t;
+}
+
+QT_END_NAMESPACE
+
+namespace qbs {
+
+Artifact::Artifact(BuildProject *p)
+ : project(p)
+ , product(0)
+ , transformer(0)
+ , artifactType(Unknown)
+ , buildState(Untouched)
+ , outOfDateCheckPerformed(false)
+ , isOutOfDate(false)
+ , isExistingFile(false)
+{
+}
+
+Artifact::~Artifact()
+{
+}
+
+void Artifact::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+ fileName = pool.idLoadString(s);
+ fileTags = pool.idLoadStringSet(s);
+ configuration = pool.idLoadS<Configuration>(s);
+ transformer = pool.idLoadS<Transformer>(s);
+ s >> artifactType;
+ product = pool.idLoadS<BuildProduct>(s).data();
+}
+
+void Artifact::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s << pool.storeString(fileName);
+ s << pool.storeStringSet(fileTags);
+ s << pool.store(configuration);
+ s << pool.store(transformer);
+ s << artifactType;
+ s << pool.store(product);
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/artifact.h b/src/lib/buildgraph/artifact.h
new file mode 100644
index 000000000..7663410fe
--- /dev/null
+++ b/src/lib/buildgraph/artifact.h
@@ -0,0 +1,112 @@
+/*************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).*
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+** GNU Lesser General Public License Usage
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef ARTIFACT_H
+#define ARTIFACT_H
+
+#include "artifactlist.h"
+#include <language/language.h>
+
+#include <QtCore/QSet>
+#include <QtCore/QString>
+
+namespace qbs {
+
+class BuildProduct;
+class BuildProject;
+class Transformer;
+
+class Artifact : public PersistentObject
+{
+public:
+ Artifact(BuildProject *p = 0);
+ ~Artifact();
+
+ ArtifactList parents;
+ ArtifactList children;
+ ArtifactList fileDependencies;
+ ArtifactList sideBySideArtifacts; /// all artifacts that have been produced by the same rule
+ QString fileName;
+ QSet<QString> fileTags;
+ BuildProject *project;
+ BuildProduct *product; // Note: file dependency artifacts don't belong to a product.
+ QSharedPointer<Transformer> transformer;
+ Configuration::Ptr configuration;
+
+ enum ArtifactType
+ {
+ Unknown,
+ SourceFile,
+ Generated,
+ FileDependency
+ };
+ ArtifactType artifactType;
+
+ enum BuildState
+ {
+ Untouched = 0,
+ Buildable,
+ Building,
+ Built
+ };
+ BuildState buildState;
+
+ bool outOfDateCheckPerformed : 1;
+ bool isOutOfDate : 1;
+ bool isExistingFile : 1;
+
+private:
+ void load(PersistentPool &pool, PersistentObjectData &data);
+ void store(PersistentPool &pool, PersistentObjectData &data) const;
+};
+
+// debugging helper
+inline QString toString(Artifact::BuildState s)
+{
+ switch (s) {
+ case Artifact::Buildable:
+ return "Initialized";
+ case Artifact::Building:
+ return "Processing";
+ case Artifact::Built:
+ return "Finished";
+ default:
+ return "Unknown";
+ }
+}
+
+} // namespace qbs
+
+#endif // ARTIFACT_H
diff --git a/src/lib/buildgraph/artifactlist.cpp b/src/lib/buildgraph/artifactlist.cpp
new file mode 100644
index 000000000..f9e80b232
--- /dev/null
+++ b/src/lib/buildgraph/artifactlist.cpp
@@ -0,0 +1,57 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "artifactlist.h"
+#include <algorithm>
+
+namespace qbs {
+
+ArtifactList::ArtifactList()
+{}
+
+ArtifactList::ArtifactList(const ArtifactList &other)
+ : m_data(other.m_data)
+{}
+
+void ArtifactList::remove(Artifact *artifact)
+{
+ iterator it = m_data.find(artifact);
+ if (it != m_data.end())
+ m_data.erase(it);
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/artifactlist.h b/src/lib/buildgraph/artifactlist.h
new file mode 100644
index 000000000..018547740
--- /dev/null
+++ b/src/lib/buildgraph/artifactlist.h
@@ -0,0 +1,109 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef ARTIFACTLIST_H
+#define ARTIFACTLIST_H
+
+#include <set>
+
+namespace qbs {
+
+class Artifact;
+
+/**
+ * List that holds a bunch of build graph artifacts.
+ * This is faster than QSet when iterating over the container.
+ */
+class ArtifactList
+{
+public:
+ ArtifactList();
+ ArtifactList(const ArtifactList &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(); }
+
+ 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 m_data.size();
+ }
+
+ void reserve(int)
+ {
+ // no-op
+ }
+
+private:
+ mutable std::set<Artifact *> m_data;
+};
+
+} // namespace qbs
+
+#endif // ARTIFACTLIST_H
diff --git a/src/lib/buildgraph/automoc.cpp b/src/lib/buildgraph/automoc.cpp
new file mode 100644
index 000000000..3a7f03ffa
--- /dev/null
+++ b/src/lib/buildgraph/automoc.cpp
@@ -0,0 +1,273 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "automoc.h"
+#include "scanresultcache.h"
+#include <buildgraph/artifact.h>
+#include <tools/error.h>
+#include <tools/logger.h>
+#include <tools/scannerpluginmanager.h>
+
+namespace qbs {
+
+AutoMoc::AutoMoc()
+ : m_scanResultCache(0)
+{
+}
+
+void AutoMoc::setScanResultCache(ScanResultCache *scanResultCache)
+{
+ m_scanResultCache = scanResultCache;
+}
+
+void AutoMoc::apply(BuildProduct::Ptr product)
+{
+ if (scanners().isEmpty())
+ throw Error("C++ scanner cannot be loaded.");
+
+ QList<QPair<Artifact *, FileType> > artifactsToMoc;
+ QSet<QString> includedMocCppFiles;
+ QHash<QString, Artifact *>::const_iterator it = product->artifacts.begin();
+ for (; it != product->artifacts.end(); ++it) {
+ Artifact *artifact = it.value();
+ FileType fileType = UnknownFileType;
+ if (artifact->fileTags.contains("hpp"))
+ fileType = HppFileType;
+ if (artifact->fileTags.contains("cpp"))
+ fileType = CppFileType;
+ if (fileType == UnknownFileType)
+ continue;
+ QString mocFileTag;
+ bool alreadyMocced = isVictimOfMoc(artifact, fileType, mocFileTag);
+ bool hasQObjectMacro;
+ apply(artifact, hasQObjectMacro, includedMocCppFiles);
+ if (hasQObjectMacro && !alreadyMocced) {
+ artifactsToMoc += qMakePair(artifact, fileType);
+ } else if (!hasQObjectMacro && alreadyMocced) {
+ unmoc(artifact, mocFileTag);
+ }
+ }
+
+ QMap<QString, QSet<Artifact *> > 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;
+ QString fileTag;
+ if (fileType == CppFileType) {
+ fileTag = "moc_cpp";
+ } else if (fileType == HppFileType) {
+ QString mocFileName = generateMocFileName(artifact, fileType);
+ if (includedMocCppFiles.contains(mocFileName))
+ fileTag = "moc_hpp_inc";
+ else
+ fileTag = "moc_hpp";
+ }
+ artifactsPerFileTag[fileTag].insert(artifact);
+ }
+
+ BuildGraph *buildGraph = product->project->buildGraph();
+ if (!artifactsPerFileTag.isEmpty()) {
+ qbsInfo() << DontPrintLogLevel << "Applying moc rules for '" << product->rProduct->name << "'.";
+ buildGraph->applyRules(product.data(), artifactsPerFileTag);
+ }
+ buildGraph->updateNodesThatMustGetNewTransformer();
+}
+
+QString AutoMoc::generateMocFileName(Artifact *artifact, FileType fileType)
+{
+ QString mocFileName;
+ switch (fileType) {
+ case UnknownFileType:
+ break;
+ case HppFileType:
+ mocFileName = "moc_" + FileInfo::baseName(artifact->fileName) + ".cpp";
+ break;
+ case CppFileType:
+ mocFileName = FileInfo::baseName(artifact->fileName) + ".moc";
+ break;
+ }
+ return mocFileName;
+}
+
+void AutoMoc::apply(Artifact *artifact, bool &hasQObjectMacro, QSet<QString> &includedMocCppFiles)
+{
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] checks " << fileName(artifact);
+
+ hasQObjectMacro = false;
+ const int numFileTags = artifact->fileTags.count();
+ char **cFileTags = createCFileTags(artifact->fileTags);
+
+ foreach (ScannerPlugin *scanner, scanners()) {
+ void *opaq = scanner->open(artifact->fileName.utf16(), cFileTags, numFileTags);
+ if (!opaq || !scanner->additionalFileTags)
+ continue;
+
+ // HACK: misuse the file dependency scanner as provider for file tags
+ int length = 0;
+ const char **szFileTagsFromScanner = scanner->additionalFileTags(opaq, &length);
+ if (szFileTagsFromScanner && length > 0) {
+ for (int i=length; --i >= 0;) {
+ const QString fileTagFromScanner = QString::fromLocal8Bit(szFileTagsFromScanner[i]);
+ artifact->fileTags.insert(fileTagFromScanner);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] finds Q_OBJECT macro";
+ if (fileTagFromScanner.startsWith("moc"))
+ hasQObjectMacro = true;
+ }
+ }
+
+ scanner->close(opaq);
+
+ ScanResultCache::Result scanResult;
+ if (m_scanResultCache)
+ scanResult = m_scanResultCache->value(artifact->fileName);
+ if (!scanResult.visited) {
+ scanResult.visited = true;
+ opaq = scanner->open(artifact->fileName.utf16(), 0, 0);
+ if (!opaq)
+ continue;
+
+ 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.insert(includedFilePath, isLocalInclude);
+ }
+
+ scanner->close(opaq);
+ if (m_scanResultCache)
+ m_scanResultCache->insert(artifact->fileName, scanResult);
+ }
+
+ for (QHash<QString, bool>::const_iterator it = scanResult.deps.constBegin(); it != scanResult.deps.constEnd(); ++it) {
+ const QString &includedFilePath = it.key();
+ if (includedFilePath.startsWith("moc_") && includedFilePath.endsWith(".cpp")) {
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] finds included file: " << includedFilePath;
+ includedMocCppFiles += includedFilePath;
+ }
+ }
+ }
+
+ freeCFileTags(cFileTags, numFileTags);
+}
+
+bool AutoMoc::isVictimOfMoc(Artifact *artifact, FileType fileType, QString &foundMocFileTag)
+{
+ foundMocFileTag.clear();
+ switch (fileType) {
+ case UnknownFileType:
+ break;
+ case HppFileType:
+ if (artifact->fileTags.contains("moc_hpp"))
+ foundMocFileTag = "moc_hpp";
+ else if (artifact->fileTags.contains("moc_hpp_inc"))
+ foundMocFileTag = "moc_hpp_inc";
+ break;
+ case CppFileType:
+ if (artifact->fileTags.contains("moc_cpp"))
+ foundMocFileTag = "moc_cpp";
+ break;
+ }
+ return !foundMocFileTag.isEmpty();
+}
+
+void AutoMoc::unmoc(Artifact *artifact, const QString &mocFileTag)
+{
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] unmoc'ing " << fileName(artifact);
+
+ artifact->fileTags.remove(mocFileTag);
+
+ Artifact *generatedMocArtifact = 0;
+ foreach (Artifact *parent, artifact->parents) {
+ foreach (const QString &fileTag, parent->fileTags) {
+ if (fileTag == "hpp" || fileTag == "cpp") {
+ generatedMocArtifact = parent;
+ break;
+ }
+ }
+ }
+
+ if (!generatedMocArtifact) {
+ qbsTrace() << "[AUTOMOC] generated moc artifact could not be found";
+ return;
+ }
+
+ BuildGraph *buildGraph = artifact->project->buildGraph();
+ if (mocFileTag == "moc_hpp") {
+ Artifact *mocObjArtifact = 0;
+ foreach (Artifact *parent, generatedMocArtifact->parents) {
+ foreach (const QString &fileTag, parent->fileTags) {
+ if (fileTag == "obj" || fileTag == "fpicobj") {
+ mocObjArtifact = parent;
+ break;
+ }
+ }
+ }
+
+ if (!mocObjArtifact) {
+ qbsTrace() << "[AUTOMOC] generated moc obj artifact could not be found";
+ } else {
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] removing moc obj artifact " << fileName(mocObjArtifact);
+ buildGraph->remove(mocObjArtifact);
+ }
+ }
+
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[AUTOMOC] removing generated artifact " << fileName(generatedMocArtifact);
+ buildGraph->remove(generatedMocArtifact);
+ delete generatedMocArtifact;
+}
+
+QList<ScannerPlugin *> AutoMoc::scanners() const
+{
+ if (m_scanners.isEmpty())
+ m_scanners = ScannerPluginManager::scannersForFileTag("hpp");
+
+ return m_scanners;
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/automoc.h b/src/lib/buildgraph/automoc.h
new file mode 100644
index 000000000..ac0025dcc
--- /dev/null
+++ b/src/lib/buildgraph/automoc.h
@@ -0,0 +1,86 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef AUTOMOC_H
+#define AUTOMOC_H
+
+#include "buildgraph.h"
+
+struct ScannerPlugin;
+
+namespace qbs {
+
+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:
+ AutoMoc();
+
+ void setScanResultCache(ScanResultCache *scanResultCache);
+ void apply(BuildProduct::Ptr product);
+
+private:
+ enum FileType
+ {
+ UnknownFileType,
+ HppFileType,
+ CppFileType
+ };
+
+private:
+ static QString generateMocFileName(Artifact *artifact, FileType fileType);
+ void apply(Artifact *artifact, bool &hasQObjectMacro, QSet<QString> &includedMocCppFiles);
+ bool isVictimOfMoc(Artifact *artifact, FileType fileType, QString &foundMocFileTag);
+ void unmoc(Artifact *artifact, const QString &mocFileTag);
+ QList<ScannerPlugin *> scanners() const;
+private:
+ mutable QList<ScannerPlugin *> m_scanners;
+ ScanResultCache *m_scanResultCache;
+};
+
+} // namespace qbs
+
+#endif // AUTOMOC_H
diff --git a/src/lib/buildgraph/buildgraph.cpp b/src/lib/buildgraph/buildgraph.cpp
new file mode 100644
index 000000000..b9225881d
--- /dev/null
+++ b/src/lib/buildgraph/buildgraph.cpp
@@ -0,0 +1,1384 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "buildgraph.h"
+#include "artifact.h"
+#include "command.h"
+#include "rulegraph.h"
+#include "transformer.h"
+
+#include <language/loader.h>
+#include <tools/fileinfo.h>
+#include <tools/persistence.h>
+#include <tools/scannerpluginmanager.h>
+#include <tools/logger.h>
+#include <tools/scripttools.h>
+
+#include <QFileInfo>
+#include <QDebug>
+#include <QDir>
+#include <QtCore/QDirIterator>
+#include <QDataStream>
+#include <QtCore/QElapsedTimer>
+#include <QtCore/QMutex>
+#include <QtScript/QScriptProgram>
+#include <QtScript/QScriptValueIterator>
+
+namespace qbs {
+
+BuildProduct::BuildProduct()
+ : project(0)
+{
+}
+
+BuildProduct::~BuildProduct()
+{
+ qDeleteAll(artifacts);
+}
+
+const QList<Rule::Ptr> &BuildProduct::topSortedRules() const
+{
+ if (m_topSortedRules.isEmpty()) {
+ RuleGraph ruleGraph;
+ ruleGraph.build(rProduct->rules, rProduct->fileTags);
+// ruleGraph.dump();
+ m_topSortedRules = ruleGraph.topSorted();
+// int i=0;
+// foreach (Rule::Ptr r, m_topSortedRules)
+// qDebug() << ++i << r->toString() << (void*)r.data();
+ }
+ return m_topSortedRules;
+}
+
+BuildGraph::BuildGraph()
+{
+ ProcessCommand::setupForJavaScript(&m_scriptEngine);
+ JavaScriptCommand::setupForJavaScript(&m_scriptEngine);
+}
+
+BuildGraph::~BuildGraph()
+{
+}
+
+static void internalDump(BuildProduct *product, Artifact *n, QByteArray indent)
+{
+ Artifact *artifactInProduct = product->artifacts.value(n->fileName);
+ if (artifactInProduct && artifactInProduct != n) {
+ fprintf(stderr,"\ntree corrupted. %p ('%s') resolves to %p ('%s')\n",
+ n, qPrintable(n->fileName), product->artifacts.value(n->fileName),
+ qPrintable(product->artifacts.value(n->fileName)->fileName));
+
+ abort();
+ }
+ printf("%s", indent.constData());
+ printf("Artifact (%p) ", n);
+ printf("%s%s %s [%s]",
+ qPrintable(QString(toString(n->buildState).at(0))),
+ artifactInProduct ? "" : " SBS", // SBS == side-by-side artifact from other product
+ qPrintable(n->fileName),
+ qPrintable(QStringList(n->fileTags.toList()).join(",")));
+ printf("\n");
+ indent.append(" ");
+ foreach (Artifact *child, n->children) {
+ internalDump(product, child, indent);
+ }
+}
+
+void BuildGraph::dump(BuildProduct::Ptr product) const
+{
+ Q_ASSERT(product->artifacts.uniqueKeys() == product->artifacts.keys());
+
+ foreach (Artifact *n, product->artifacts)
+ if (n->parents.isEmpty())
+ internalDump(product.data(), n, QByteArray());
+}
+
+void BuildGraph::insert(BuildProduct::Ptr product, Artifact *n) const
+{
+ insert(product.data(), n);
+}
+
+void BuildGraph::insert(BuildProduct *product, Artifact *n) const
+{
+ Q_ASSERT(n->product == 0);
+ Q_ASSERT(!n->fileName.isEmpty());
+ Q_ASSERT(!product->artifacts.contains(n->fileName));
+#ifdef QT_DEBUG
+ foreach (BuildProduct::Ptr otherProduct, product->project->buildProducts()) {
+ if (otherProduct->artifacts.contains(n->fileName)) {
+ if (n->artifactType == Artifact::Generated) {
+ QString pl;
+ pl.append(QString(" - %1 \n").arg(product->rProduct->name));
+ foreach (BuildProduct::Ptr p, product->project->buildProducts()) {
+ if (p->artifacts.contains(n->fileName)) {
+ pl.append(QString(" - %1 \n").arg(p->rProduct->name));
+ }
+ }
+ throw Error(QString ("BUG: already inserted in this project: %1\n%2"
+ )
+ .arg(n->fileName)
+ .arg(pl)
+ );
+ }
+ }
+ }
+#endif
+ product->artifacts.insert(n->fileName, n);
+ n->product = product;
+ product->project->markDirty();
+
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] insert artifact '%s'", qPrintable(n->fileName));
+}
+
+void BuildGraph::setupScriptEngineForProduct(QScriptEngine *scriptEngine, ResolvedProduct::Ptr product, Rule::Ptr rule, BuildGraph *bg)
+{
+ ResolvedProduct *lastSetupProduct = (ResolvedProduct *)scriptEngine->property("lastSetupProduct").toULongLong();
+
+ QScriptValue productScriptValue;
+ if (lastSetupProduct != product.data()) {
+ scriptEngine->setProperty("lastSetupProduct", QVariant((qulonglong)product.data()));
+ productScriptValue = scriptEngine->toScriptValue(product->configuration->value());
+ productScriptValue.setProperty("name", product->name);
+ QString destinationDirectory = product->destinationDirectory;
+ if (destinationDirectory.isEmpty())
+ destinationDirectory = ".";
+ productScriptValue.setProperty("destinationDirectory", destinationDirectory);
+ scriptEngine->globalObject().setProperty("product", productScriptValue, QScriptValue::ReadOnly);
+ } else {
+ productScriptValue = scriptEngine->globalObject().property("product");
+ }
+
+ // If the Rule is in a Module, set up the 'module' property
+ if (!rule->module->name.isEmpty())
+ productScriptValue.setProperty("module", productScriptValue.property("modules").property(rule->module->name));
+
+ if (rule) {
+ for (JsImports::const_iterator it = rule->jsImports.begin(); it != rule->jsImports.end(); ++it) {
+ foreach (const QString &fileName, it.value()) {
+ QScriptValue jsImportValue;
+ if (bg)
+ jsImportValue = bg->m_jsImportCache.value(fileName, scriptEngine->undefinedValue());
+ if (jsImportValue.isUndefined()) {
+// qDebug() << "CACHE MISS" << fileName;
+ QFile file(fileName);
+ if (!file.open(QFile::ReadOnly))
+ throw Error(QString("Cannot open '%1'.").arg(fileName));
+ const QString sourceCode = QTextStream(&file).readAll();
+ QScriptProgram program(sourceCode, fileName);
+ addJSImport(scriptEngine, program, jsImportValue);
+ addJSImport(scriptEngine, jsImportValue, it.key());
+ if (bg)
+ bg->m_jsImportCache.insert(fileName, jsImportValue);
+ } else {
+// qDebug() << "CACHE HIT" << fileName;
+ addJSImport(scriptEngine, jsImportValue, it.key());
+ }
+ }
+ }
+ } else {
+ // ### TODO remove the imports we added before
+ }
+}
+
+void BuildGraph::setupScriptEngineForArtifact(BuildProduct *product, Artifact *artifact)
+{
+ QString inFileName = FileInfo::fileName(artifact->fileName);
+ QString inBaseName = FileInfo::baseName(artifact->fileName);
+ QString inCompleteBaseName = FileInfo::completeBaseName(artifact->fileName);
+
+ QString basedir;
+ if (artifact->artifactType == Artifact::SourceFile) {
+ QDir sourceDir(product->rProduct->sourceDirectory);
+ basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->fileName));
+ } else {
+ QDir buildDir(product->project->buildGraph()->buildDirectoryRoot() + product->project->resolvedProject()->id);
+ basedir = FileInfo::path(buildDir.relativeFilePath(artifact->fileName));
+ }
+
+ QScriptValue modulesScriptValue = artifact->configuration->cachedScriptValue(&m_scriptEngine);
+ if (!modulesScriptValue.isValid()) {
+ modulesScriptValue = m_scriptEngine.toScriptValue(artifact->configuration->value());
+ artifact->configuration->cacheScriptValue(&m_scriptEngine, modulesScriptValue);
+ }
+ modulesScriptValue = modulesScriptValue.property("modules");
+
+ // expose per file properties we want to use in an Artifact within a Rule
+ QScriptValue scriptValue = m_scriptEngine.newObject();
+ scriptValue.setProperty("fileName", inFileName);
+ scriptValue.setProperty("baseName", inBaseName);
+ scriptValue.setProperty("completeBaseName", inCompleteBaseName);
+ scriptValue.setProperty("baseDir", basedir);
+ scriptValue.setProperty("modules", modulesScriptValue);
+
+ QScriptValue globalObj = m_scriptEngine.globalObject();
+ globalObj.setProperty("input", scriptValue);
+}
+
+void BuildGraph::applyRules(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag)
+{
+ foreach (Rule::Ptr rule, product->topSortedRules())
+ applyRule(product, artifactsPerFileTag, rule);
+}
+
+/*!
+ * Runs a cycle detection on the BG and throws an exception if there is one.
+ */
+void BuildGraph::detectCycle(BuildProject *project)
+{
+ QElapsedTimer *t = 0;
+ if (qbsLogLevel(LoggerTrace)) {
+ t = new QElapsedTimer;
+ qbsTrace() << "[BG] running cycle detection on project '" + project->resolvedProject()->id + "'";
+ }
+
+ foreach (BuildProduct::Ptr product, project->buildProducts())
+ foreach (Artifact *artifact, product->targetArtifacts)
+ detectCycle(artifact);
+
+ if (qbsLogLevel(LoggerTrace)) {
+ qint64 elapsed = t->elapsed();
+ qbsTrace() << "[BG] cycle detection for project '" + project->resolvedProject()->id + "' took " << elapsed << " ms";
+ delete t;
+ }
+}
+
+void BuildGraph::detectCycle(Artifact *a)
+{
+ QSet<Artifact *> done, currentBranch;
+ detectCycle(a, done, currentBranch);
+}
+
+void BuildGraph::detectCycle(Artifact *v, QSet<Artifact *> &done, QSet<Artifact *> &currentBranch)
+{
+ currentBranch += v;
+ for (ArtifactList::const_iterator it = v->children.begin(); it != v->children.end(); ++it) {
+ Artifact *u = *it;
+ if (currentBranch.contains(u))
+ throw Error("Cycle in build graph detected.");
+ if (!done.contains(u))
+ detectCycle(u, done, currentBranch);
+ }
+ currentBranch -= v;
+ done += v;
+}
+
+static AbstractCommand *createCommandFromScriptValue(const QScriptValue &scriptValue)
+{
+ if (scriptValue.isUndefined() || !scriptValue.isValid())
+ return 0;
+ AbstractCommand *cmdBase = 0;
+ QString className = scriptValue.property("className").toString();
+ if (className == "Command")
+ cmdBase = new ProcessCommand;
+ else if (className == "JavaScriptCommand")
+ cmdBase = new JavaScriptCommand;
+ if (cmdBase)
+ cmdBase->fillFromScriptValue(&scriptValue);
+ return cmdBase;
+}
+
+void BuildGraph::applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag,
+ Rule::Ptr rule)
+{
+ setupScriptEngineForProduct(&m_scriptEngine, product->rProduct, rule, this);
+
+ if (rule->isMultiplexRule()) {
+ // apply the rule once for a set of inputs
+
+ QSet<Artifact*> inputArtifacts;
+ foreach (const QString &fileTag, rule->inputs)
+ inputArtifacts.unite(artifactsPerFileTag.value(fileTag));
+
+ if (!inputArtifacts.isEmpty())
+ applyRule(product, artifactsPerFileTag, rule, inputArtifacts);
+ } else {
+ // apply the rule once for each input
+
+ QSet<Artifact*> inputArtifacts;
+ foreach (const QString &fileTag, rule->inputs) {
+ foreach (Artifact *inputArtifact, artifactsPerFileTag.value(fileTag)) {
+ inputArtifacts.insert(inputArtifact);
+ applyRule(product, artifactsPerFileTag, rule, inputArtifacts);
+ inputArtifacts.clear();
+ }
+ }
+ }
+}
+
+void BuildGraph::createOutputArtifact(
+ BuildProduct *product,
+ const Rule::Ptr &rule, const RuleArtifact::Ptr &ruleArtifact,
+ const QSet<Artifact *> &inputArtifacts,
+ QList< QPair<RuleArtifact*, Artifact *> > *ruleArtifactArtifactMap,
+ QList<Artifact *> *outputArtifacts,
+ QSharedPointer<Transformer> &transformer)
+{
+ QScriptValue scriptValue = m_scriptEngine.evaluate(ruleArtifact->fileScript);
+ if (scriptValue.isError() || m_scriptEngine.hasUncaughtException())
+ throw Error("Error in Rule.Artifact fileName: " + scriptValue.toString());
+ QString outputPath = scriptValue.toString();
+ outputPath.replace("..", "dotdot"); // don't let the output artifact "escape" its build dir
+ outputPath = resolveOutPath(outputPath, product);
+
+ Artifact *outputArtifact = product->artifacts.value(outputPath);
+ if (outputArtifact) {
+ if (outputArtifact->transformer && outputArtifact->transformer != transformer) {
+ // This can happen when applying rules after scanning for additional file tags.
+ // We just regenerate the transformer.
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] regenerating transformer for '%s'", qPrintable(fileName(outputArtifact)));
+ transformer = outputArtifact->transformer;
+ transformer->inputs += inputArtifacts;
+
+ if (transformer->inputs.count() > 1 && !rule->isMultiplexRule()) {
+ QString th = "[" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+ QString e = tr("Conflicting rules for producing %1 %2 \n").arg(outputArtifact->fileName, th);
+ th = "[" + rule->inputs.join(", ")
+ + "] -> [" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+
+ e += QString(" while trying to apply: %1:%2:%3 %4\n")
+ .arg(rule->script->location.fileName)
+ .arg(rule->script->location.line)
+ .arg(rule->script->location.column)
+ .arg(th);
+
+ e += QString(" was already defined in: %1:%2:%3 %4\n")
+ .arg(outputArtifact->transformer->rule->script->location.fileName)
+ .arg(outputArtifact->transformer->rule->script->location.line)
+ .arg(outputArtifact->transformer->rule->script->location.column)
+ .arg(th);
+ throw Error(e);
+ }
+ }
+ outputArtifact->fileTags += ruleArtifact->fileTags.toSet();
+ } else {
+ outputArtifact = new Artifact(product->project);
+ outputArtifact->artifactType = Artifact::Generated;
+ outputArtifact->fileName = outputPath;
+ outputArtifact->fileTags = ruleArtifact->fileTags.toSet();
+ insert(product, outputArtifact);
+ }
+
+ if (rule->isMultiplexRule())
+ outputArtifact->configuration = product->rProduct->configuration;
+ else
+ outputArtifact->configuration = (*inputArtifacts.constBegin())->configuration;
+
+ foreach (Artifact *inputArtifact, inputArtifacts) {
+ Q_ASSERT(outputArtifact != inputArtifact);
+ loggedConnect(outputArtifact, inputArtifact);
+ }
+ ruleArtifactArtifactMap->append(qMakePair(ruleArtifact.data(), outputArtifact));
+ outputArtifacts->append(outputArtifact);
+
+ // create transformer if not already done so
+ if (!transformer) {
+ transformer = QSharedPointer<Transformer>(new Transformer);
+ transformer->rule = rule;
+ transformer->inputs = inputArtifacts;
+ }
+ outputArtifact->transformer = transformer;
+}
+
+void BuildGraph::applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag, Rule::Ptr rule, const QSet<Artifact *> &inputArtifacts)
+{
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[BG] apply rule " << rule->toString() << " " << toStringList(inputArtifacts).join(",\n ");
+
+ QList< QPair<RuleArtifact*, Artifact *> > ruleArtifactArtifactMap;
+ QList<Artifact *> outputArtifacts;
+
+ QSet<Artifact *> usingArtifacts;
+ foreach (BuildProduct *dep, product->usings) {
+ foreach (Artifact *targetArtifact, dep->targetArtifacts) {
+ ArtifactList sbsArtifacts = targetArtifact->sideBySideArtifacts;
+ sbsArtifacts.insert(targetArtifact);
+ foreach (Artifact *artifact, sbsArtifacts) {
+ QString matchingTag;
+ foreach (const QString &tag, rule->usings) {
+ if (artifact->fileTags.contains(tag)) {
+ matchingTag = tag;
+ break;
+ }
+ }
+ if (matchingTag.isEmpty())
+ continue;
+ usingArtifacts.insert(artifact);
+ }
+ }
+ }
+
+ // create the output artifacts from the set of input artifacts
+ QSharedPointer<Transformer> transformer;
+ foreach (RuleArtifact::Ptr ruleArtifact, rule->artifacts) {
+ if (!rule->isMultiplexRule()) {
+ foreach (Artifact *inputArtifact, inputArtifacts) {
+ setupScriptEngineForArtifact(product, inputArtifact);
+ QSet<Artifact *> oneInputArtifact;
+ oneInputArtifact.insert(inputArtifact);
+ createOutputArtifact(product, rule, ruleArtifact, oneInputArtifact,
+ &ruleArtifactArtifactMap, &outputArtifacts, transformer);
+ }
+ } else {
+ createOutputArtifact(product, rule, ruleArtifact, inputArtifacts,
+ &ruleArtifactArtifactMap, &outputArtifacts, transformer);
+ }
+ }
+
+ foreach (Artifact *outputArtifact, outputArtifacts) {
+ // insert the output artifacts into the pool of artifacts
+ foreach (const QString &fileTag, outputArtifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(outputArtifact);
+
+ // connect artifacts that match the file tags in explicitlyDependsOn
+ foreach (const QString &fileTag, rule->explicitlyDependsOn)
+ foreach (Artifact *dependency, artifactsPerFileTag.value(fileTag))
+ loggedConnect(outputArtifact, dependency);
+
+ // Transformer setup
+ transformer->outputs.insert(outputArtifact);
+ for (QSet<Artifact *>::const_iterator it = usingArtifacts.constBegin(); it != usingArtifacts.constEnd(); ++it) {
+ Artifact *dep = *it;
+ loggedConnect(outputArtifact, dep);
+ transformer->inputs.insert(dep);
+ foreach (Artifact *sideBySideDep, dep->sideBySideArtifacts) {
+ loggedConnect(outputArtifact, sideBySideDep);
+ transformer->inputs.insert(sideBySideDep);
+ }
+ }
+
+ m_artifactsThatMustGetNewTransformers -= outputArtifact;
+ }
+
+ // setup side-by-side artifacts
+ if (outputArtifacts.count() > 1)
+ foreach (Artifact *sbs1, outputArtifacts)
+ foreach (Artifact *sbs2, outputArtifacts)
+ if (sbs1 != sbs2)
+ sbs1->sideBySideArtifacts.insert(sbs2);
+
+ transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject());
+
+ // change the transformer outputs according to the bindings in Artifact
+ QScriptValue scriptValue;
+ for (int i=ruleArtifactArtifactMap.count(); --i >= 0;) {
+ RuleArtifact *ra = ruleArtifactArtifactMap.at(i).first;
+ if (ra->bindings.isEmpty())
+ continue;
+
+ // expose attributes of this artifact
+ Artifact *outputArtifact = ruleArtifactArtifactMap.at(i).second;
+ outputArtifact->configuration = Configuration::Ptr(new Configuration(*outputArtifact->configuration));
+
+ // ### clean m_scriptEngine first?
+ m_scriptEngine.globalObject().setProperty("fileName", m_scriptEngine.toScriptValue(outputArtifact->fileName), QScriptValue::ReadOnly);
+ m_scriptEngine.globalObject().setProperty("fileTags", toScriptValue(&m_scriptEngine, outputArtifact->fileTags), QScriptValue::ReadOnly);
+
+ QVariantMap artifactModulesCfg = outputArtifact->configuration->value().value("modules").toMap();
+ for (int i=0; i < ra->bindings.count(); ++i) {
+ const QStringList &name = ra->bindings.at(i).first;
+ const QString &code = ra->bindings.at(i).second;
+ scriptValue = m_scriptEngine.evaluate(code);
+ if (scriptValue.isError())
+ throw Error(QLatin1String("evaluating rule bindings: ") + scriptValue.toString());
+ setConfigProperty(artifactModulesCfg, name, scriptValue.toVariant());
+ }
+ QVariantMap outputArtifactConfiguration = outputArtifact->configuration->value();
+ outputArtifactConfiguration.insert("modules", artifactModulesCfg);
+ outputArtifact->configuration->setValue(outputArtifactConfiguration);
+ }
+
+ transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject());
+
+ // setup transform properties
+ {
+ const QVariantMap overriddenTransformProperties = product->rProduct->configuration->value().value("modules").toMap().value(rule->module->name).toMap().value(rule->objectId).toMap();
+ /*
+ overriddenTransformProperties contains the rule's transform properties that have been overridden in the project file.
+ For example, if you set cpp.compiler.defines in your project file, that property appears here.
+ */
+
+ QMap<QString, QScriptProgram>::const_iterator it = rule->transformProperties.begin();
+ for (; it != rule->transformProperties.end(); ++it)
+ {
+ const QString &propertyName = it.key();
+ QScriptValue sv;
+ if (overriddenTransformProperties.contains(propertyName)) {
+ sv = m_scriptEngine.toScriptValue(overriddenTransformProperties.value(propertyName));
+ } else {
+ const QScriptProgram &myProgram = it.value();
+ sv = m_scriptEngine.evaluate(myProgram);
+ if (m_scriptEngine.hasUncaughtException()) {
+ CodeLocation errorLocation;
+ errorLocation.fileName = m_scriptEngine.uncaughtExceptionBacktrace().join("\n");
+ errorLocation.line = m_scriptEngine.uncaughtExceptionLineNumber();
+ throw Error(QLatin1String("transform property evaluation: ") + m_scriptEngine.uncaughtException().toString(), errorLocation);
+ } else if (sv.isError()) {
+ CodeLocation errorLocation(myProgram.fileName(), myProgram.firstLineNumber());
+ throw Error(QLatin1String("transform property evaluation: ") + sv.toString(), errorLocation);
+ }
+ }
+ m_scriptEngine.globalObject().setProperty(propertyName, sv);
+ }
+ }
+
+ createTransformerCommands(rule->script, transformer.data());
+ if (transformer->commands.isEmpty())
+ throw Error(QString("There's a rule without commands: %1.").arg(rule->toString()), rule->script->location);
+}
+
+void BuildGraph::createTransformerCommands(RuleScript::Ptr script, Transformer *transformer)
+{
+ QScriptProgram &scriptProgram = m_scriptProgramCache[script->script];
+ if (scriptProgram.isNull())
+ scriptProgram = QScriptProgram(script->script);
+
+ QScriptValue scriptValue = m_scriptEngine.evaluate(scriptProgram);
+ if (m_scriptEngine.hasUncaughtException())
+ throw Error("evaluating prepare script: " + m_scriptEngine.uncaughtException().toString(),
+ script->location);
+
+ QList<AbstractCommand*> commands;
+ if (scriptValue.isArray()) {
+ const int count = scriptValue.property("length").toInt32();
+ for (qint32 i=0; i < count; ++i) {
+ QScriptValue item = scriptValue.property(i);
+ if (item.isValid() && !item.isUndefined()) {
+ AbstractCommand *cmd = createCommandFromScriptValue(item);
+ if (cmd)
+ commands += cmd;
+ }
+ }
+ } else {
+ AbstractCommand *cmd = createCommandFromScriptValue(scriptValue);
+ if (cmd)
+ commands += cmd;
+ }
+
+ transformer->commands = commands;
+}
+
+QString BuildGraph::buildDirectoryRoot() const
+{
+ Q_ASSERT(!m_outputDirectoryRoot.isEmpty());
+ QString path = FileInfo::resolvePath(m_outputDirectoryRoot, QLatin1String("build"));
+ if (!path.endsWith('/'))
+ path.append(QLatin1Char('/'));
+ return path;
+}
+
+/*
+ * c must be built before p
+ * p ----> c
+ * p.children = c
+ * c.parents = p
+ *
+ * also: children means i depend on or i am produced by
+ * parent means "produced by me" or "depends on me"
+ */
+void BuildGraph::connect(Artifact *p, Artifact *c)
+{
+ Q_ASSERT(p != c);
+ p->children.insert(c);
+ c->parents.insert(p);
+ p->project->markDirty();
+}
+
+void BuildGraph::loggedConnect(Artifact *u, Artifact *v)
+{
+ Q_ASSERT(u != v);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] connect '%s' -> '%s'",
+ qPrintable(fileName(u)),
+ qPrintable(fileName(v)));
+ connect(u, v);
+}
+
+static bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path)
+{
+ if (u == v) {
+ path.append(v);
+ return true;
+ }
+
+ for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) {
+ if (findPath(*it, v, path)) {
+ path.prepend(u);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool existsPath(Artifact *u, Artifact *v)
+{
+ if (u == v)
+ return true;
+
+ for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it)
+ if (existsPath(*it, v))
+ return true;
+
+ return false;
+}
+
+bool BuildGraph::safeConnect(Artifact *u, Artifact *v)
+{
+ Q_ASSERT(u != v);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] safeConnect: '%s' '%s'",
+ qPrintable(fileName(u)),
+ qPrintable(fileName(v)));
+
+ if (existsPath(v, u)) {
+ QList<Artifact *> circle;
+ findPath(v, u, circle);
+ qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle);
+ return false;
+ }
+
+ connect(u, v);
+ return true;
+}
+
+void BuildGraph::disconnect(Artifact *u, Artifact *v)
+{
+ u->children.remove(v);
+ v->parents.remove(u);
+}
+
+QSet<Artifact *> BuildGraph::disconnect(Artifact *n) const
+{
+ QSet<Artifact *> r;
+ if (n->children.count() == 1) {
+ Artifact * c = *(n->children.begin());
+ c->parents.remove(n);
+ n->children.clear();
+ r += n;
+ foreach (Artifact * p, n->parents) {
+ r += disconnect(p);
+ p->children.remove(n);
+ if (p->transformer)
+ p->transformer->inputs.remove(n);
+ }
+ }
+ return r;
+}
+
+void BuildGraph::remove(Artifact *artifact) const
+{
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[BG] remove artifact " << fileName(artifact);
+
+ if (artifact->artifactType == Artifact::Generated)
+ QFile::remove(artifact->fileName);
+ artifact->product->artifacts.remove(artifact->fileName);
+ artifact->product->targetArtifacts.remove(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ parent->children.remove(artifact);
+ if (parent->transformer) {
+ parent->transformer->inputs.remove(artifact);
+ m_artifactsThatMustGetNewTransformers += parent;
+ }
+ }
+ foreach (Artifact *child, artifact->children) {
+ child->parents.remove(artifact);
+ }
+ artifact->children.clear();
+ artifact->parents.clear();
+ artifact->project->markDirty();
+}
+
+/**
+ * Removes the artifact and all the artifacts that depend exclusively on it.
+ * Example: if you remove a cpp artifact then the obj artifact is removed but
+ * not the resulting application (if there's more then one cpp artifact).
+ */
+void BuildGraph::removeArtifactAndExclusiveDependents(Artifact *artifact, QList<Artifact*> *removedArtifacts)
+{
+ if (removedArtifacts)
+ removedArtifacts->append(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ if (parent->children.count() == 1)
+ removeArtifactAndExclusiveDependents(parent, removedArtifacts);
+ }
+ remove(artifact);
+}
+
+BuildProject::Ptr BuildGraph::resolveProject(ResolvedProject::Ptr rProject, QFutureInterface<bool> &futureInterface)
+{
+ BuildProject::Ptr project = BuildProject::Ptr(new BuildProject(this));
+ project->setResolvedProject(rProject);
+ foreach (ResolvedProduct::Ptr rProduct, rProject->products) {
+ resolveProduct(project.data(), rProduct, futureInterface);
+ }
+ detectCycle(project.data());
+ return project;
+}
+
+BuildProduct::Ptr BuildGraph::resolveProduct(BuildProject *project, ResolvedProduct::Ptr rProduct, QFutureInterface<bool> &futureInterface)
+{
+ BuildProduct::Ptr product = m_productCache.value(rProduct);
+ if (product)
+ return product;
+
+ futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ product = BuildProduct::Ptr(new BuildProduct);
+ m_productCache.insert(rProduct, product);
+ product->project = project;
+ product->rProduct = rProduct;
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+
+ foreach (ResolvedProduct::Ptr t2, rProduct->uses) {
+ if (t2 == rProduct) {
+ throw Error(tr("circular using"));
+ }
+ BuildProduct::Ptr referencedProduct = resolveProduct(project, t2, futureInterface);
+ product->usings.append(referencedProduct.data());
+ }
+
+ //add qbsFile artifact
+ Artifact *qbsFileArtifact = product->artifacts.value(rProduct->qbsFile);
+ if (!qbsFileArtifact) {
+ qbsFileArtifact = new Artifact(project);
+ qbsFileArtifact->artifactType = Artifact::SourceFile;
+ qbsFileArtifact->fileName = rProduct->qbsFile;
+ qbsFileArtifact->configuration = rProduct->configuration;
+ insert(product, qbsFileArtifact);
+ }
+ qbsFileArtifact->fileTags.insert("qbs");
+ artifactsPerFileTag["qbs"].insert(qbsFileArtifact);
+
+ // read sources
+ foreach (SourceArtifact::Ptr sourceArtifact, rProduct->sources) {
+ QString filePath = sourceArtifact->absoluteFilePath;
+ if (product->artifacts.contains(filePath)) {
+ // ignore duplicate artifacts
+ continue;
+ }
+
+ Artifact *artifact = createArtifact(product, sourceArtifact);
+
+ foreach (const QString &fileTag, artifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(artifact);
+ }
+
+ // read manually added transformers
+ QList<Artifact *> transformerOutputs;
+ foreach (const ResolvedTransformer::Ptr rtrafo, rProduct->transformers) {
+ QList<Artifact *> inputArtifacts;
+ foreach (const QString &inputFileName, rtrafo->inputs) {
+ Artifact *artifact = product->artifacts.value(inputFileName);
+ if (!artifact)
+ throw Error(QString("Can't find artifact '%0' in the list of source files.").arg(inputFileName));
+ if (artifact->fileTags.isEmpty())
+ artifact->fileTags += "unknown";
+ inputArtifacts += artifact;
+ }
+ QSharedPointer<Transformer> transformer(new Transformer);
+ transformer->inputs = inputArtifacts.toSet();
+ transformer->rule = Rule::Ptr(new Rule);
+ transformer->rule->inputs = rtrafo->inputs;
+ transformer->rule->jsImports = rtrafo->jsImports;
+ transformer->rule->module = ResolvedModule::Ptr(new ResolvedModule);
+ transformer->rule->module->name = rtrafo->module->name;
+ transformer->rule->script = rtrafo->transform;
+ foreach (SourceArtifact::Ptr sourceArtifact, rtrafo->outputs) {
+ Artifact *outputArtifact = createArtifact(product, sourceArtifact);
+ outputArtifact->artifactType = Artifact::Generated;
+ outputArtifact->transformer = transformer;
+ transformer->outputs += outputArtifact;
+ transformerOutputs += outputArtifact;
+ foreach (Artifact *inputArtifact, inputArtifacts)
+ safeConnect(outputArtifact, inputArtifact);
+ foreach (const QString &fileTag, outputArtifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(outputArtifact);
+
+ RuleArtifact::Ptr ruleArtifact(new RuleArtifact);
+ ruleArtifact->fileScript = outputArtifact->fileName;
+ ruleArtifact->fileTags = outputArtifact->fileTags.toList();
+ transformer->rule->artifacts += ruleArtifact;
+ }
+ setupScriptEngineForProduct(&m_scriptEngine, rProduct, transformer->rule, this);
+ transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject());
+ transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject());
+ createTransformerCommands(rtrafo->transform, transformer.data());
+ if (transformer->commands.isEmpty())
+ throw Error(QString("There's a transformer without commands."), rtrafo->transform->location);
+ }
+
+ applyRules(product.data(), artifactsPerFileTag);
+
+ QSet<Artifact *> productArtifactCandidates;
+ for (int i=0; i < product->rProduct->fileTags.count(); ++i)
+ foreach (Artifact *artifact, artifactsPerFileTag.value(product->rProduct->fileTags.at(i)))
+ if (artifact->artifactType == Artifact::Generated)
+ productArtifactCandidates += artifact;
+
+ if (productArtifactCandidates.isEmpty()) {
+ // this should already be catched in the rule graph
+ throw Error("The impossible happenend! The rules generate no product.");
+ }
+
+ foreach (Artifact *productArtifact, productArtifactCandidates) {
+ product->targetArtifacts.insert(productArtifact);
+ project->addBuildProduct(product);
+
+ foreach (Artifact *trafoOutputArtifact, transformerOutputs)
+ if (productArtifact != trafoOutputArtifact)
+ loggedConnect(productArtifact, trafoOutputArtifact);
+ }
+
+ return product;
+}
+
+void BuildGraph::onProductChanged(BuildProduct::Ptr product, ResolvedProduct::Ptr changedProduct)
+{
+ qbsDebug() << "[BG] product '" << product->rProduct->name << "' changed.";
+
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+ QList<Artifact *> addedArtifacts, artifactsToRemove;
+ QHash<QString, SourceArtifact::Ptr> oldArtifacts, newArtifacts;
+ foreach (SourceArtifact::Ptr a, product->rProduct->sources)
+ oldArtifacts.insert(a->absoluteFilePath, a);
+ foreach (SourceArtifact::Ptr a, changedProduct->sources) {
+ newArtifacts.insert(a->absoluteFilePath, a);
+ if (!oldArtifacts.contains(a->absoluteFilePath)) {
+ // artifact added
+ qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' added to product " << product->rProduct->name;
+ product->rProduct->sources.insert(a);
+ addedArtifacts += createArtifact(product, a);
+ }
+ }
+ foreach (SourceArtifact::Ptr a, product->rProduct->sources) {
+ SourceArtifact::Ptr changedArtifact = newArtifacts.value(a->absoluteFilePath);
+ if (!changedArtifact) {
+ // artifact removed
+ qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' removed from product " << product->rProduct->name;
+ Artifact *artifact = product->artifacts.value(a->absoluteFilePath);
+ Q_ASSERT(artifact);
+ removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove);
+ continue;
+ }
+ if (changedArtifact->fileTags != a->fileTags) {
+ // artifact's filetags have changed
+ qbsDebug() << "[BG] filetags have changed for artifact '" << a->absoluteFilePath
+ << "' from " << a->fileTags << " to " << changedArtifact->fileTags;
+ Artifact *artifact = product->artifacts.value(a->absoluteFilePath);
+ Q_ASSERT(artifact);
+
+ // handle added filetags
+ foreach (const QString &addedFileTag, changedArtifact->fileTags - a->fileTags)
+ artifactsPerFileTag[addedFileTag] += artifact;
+
+ // handle removed filetags
+ foreach (const QString &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
+ removeArtifactAndExclusiveDependents(parent, &artifactsToRemove);
+ }
+ }
+ }
+ }
+ if (changedArtifact->configuration->value() != a->configuration->value()) // ### TODO
+ {
+ qWarning("Some properties changed. Consider rebuild or fix QBS-7. File name: %s", qPrintable(changedArtifact->absoluteFilePath));
+ QVariantMap m = a->configuration->value();
+
+ for (QVariantMap::iterator it = m.begin(); it != m.end(); ++it) {
+ if (it.value() != changedArtifact->configuration->value().value(it.key())) {
+ qDebug() << " old:" << it.value();
+ qDebug() << " new:" << changedArtifact->configuration->value().value(it.key());
+ }
+ }
+ }
+ }
+
+ // apply rules for new artifacts
+ foreach (Artifact *artifact, addedArtifacts)
+ foreach (const QString &ft, artifact->fileTags)
+ artifactsPerFileTag[ft] += artifact;
+ applyRules(product.data(), artifactsPerFileTag);
+
+ // parents of removed artifacts must update their transformers
+ foreach (Artifact *removedArtifact, artifactsToRemove)
+ foreach (Artifact *parent, removedArtifact->parents)
+ m_artifactsThatMustGetNewTransformers += parent;
+ updateNodesThatMustGetNewTransformer();
+
+ // delete all removed artifacts physically from the disk
+ foreach (Artifact *artifact, artifactsToRemove) {
+ if (artifact->artifactType == Artifact::Generated) {
+ qbsDebug() << "[BG] deleting stale artifact " << artifact->fileName;
+ QFile::remove(artifact->fileName);
+ }
+ delete artifact;
+ }
+}
+
+void BuildGraph::updateNodesThatMustGetNewTransformer()
+{
+ foreach (Artifact *artifact, m_artifactsThatMustGetNewTransformers)
+ updateNodeThatMustGetNewTransformer(artifact);
+ m_artifactsThatMustGetNewTransformers.clear();
+}
+
+void BuildGraph::updateNodeThatMustGetNewTransformer(Artifact *artifact)
+{
+ Q_CHECK_PTR(artifact->transformer);
+
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[BG] updating transformer for " << fileName(artifact);
+
+ Rule::Ptr rule = artifact->transformer->rule;
+ artifact->product->project->markDirty();
+ artifact->transformer = QSharedPointer<Transformer>();
+
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+ foreach (Artifact *input, artifact->children)
+ foreach (const QString &fileTag, input->fileTags)
+ artifactsPerFileTag[fileTag] += input;
+
+ applyRule(artifact->product, artifactsPerFileTag, rule);
+}
+
+Artifact *BuildGraph::createArtifact(BuildProduct::Ptr product, SourceArtifact::Ptr sourceArtifact)
+{
+ Artifact *artifact = new Artifact(product->project);
+ artifact->artifactType = Artifact::SourceFile;
+ artifact->fileName = sourceArtifact->absoluteFilePath;
+ artifact->fileTags = sourceArtifact->fileTags;
+ artifact->configuration = sourceArtifact->configuration;
+ insert(product, artifact);
+ return artifact;
+}
+
+QString BuildGraph::resolveOutPath(const QString &path, BuildProduct *product) const
+{
+ QString result;
+ QString buildDir = product->rProduct->buildDirectory;
+ result = FileInfo::resolvePath(buildDir, path);
+
+ Q_ASSERT(result.startsWith(buildDir));
+ result = QDir::cleanPath(result);
+ return result;
+}
+
+void Transformer::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+ rule = pool.idLoadS<Rule>(s);
+ loadContainer(inputs, s, pool);
+ loadContainer(outputs, s, pool);
+ int count, cmdType;
+ s >> count;
+ commands.reserve(count);
+ while (--count >= 0) {
+ s >> cmdType;
+ AbstractCommand *cmd = AbstractCommand::createByType(static_cast<AbstractCommand::CommandType>(cmdType));
+ cmd->load(s);
+ commands += cmd;
+ }
+}
+
+void Transformer::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s << pool.store(rule);
+ storeContainer(inputs, s, pool);
+ storeContainer(outputs, s, pool);
+ s << commands.count();
+ foreach (AbstractCommand *cmd, commands) {
+ s << int(cmd->type());
+ cmd->store(s);
+ }
+}
+
+void BuildProduct::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+ int i, count;
+
+ // artifacts
+ artifacts.clear();
+ s >> count;
+ for (i = count; --i >= 0;) {
+ QString key;
+ s >> key;
+ artifacts.insert(key, pool.idLoad<Artifact>(s));
+ }
+
+ // edges
+ for (i = count; --i >= 0;) {
+ Artifact *artifact = pool.idLoad<Artifact>(s);
+ int count2, j;
+ s >> count2;
+ artifact->parents.clear();
+ artifact->parents.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->parents.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->children.clear();
+ artifact->children.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->children.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->fileDependencies.clear();
+ artifact->fileDependencies.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->fileDependencies.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->sideBySideArtifacts.clear();
+ artifact->sideBySideArtifacts.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->sideBySideArtifacts.insert(pool.idLoad<Artifact>(s));
+ }
+
+ // other data
+ rProduct = pool.idLoadS<ResolvedProduct>(s);
+ loadContainer(targetArtifacts, s, pool);
+ loadContainer(usings, s, pool);
+}
+
+void BuildProduct::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s << artifacts.count();
+
+ //artifacts
+ for (QHash<QString, Artifact *>::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) {
+ s << i.key();
+ PersistentObjectId artifactId = pool.store(i.value());
+ s << artifactId;
+ }
+
+ // edges
+ for (QHash<QString, Artifact *>::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) {
+ Artifact * artifact = i.value();
+ s << pool.store(artifact);
+
+ s << artifact->parents.count();
+ foreach (Artifact * n, artifact->parents)
+ s << pool.store(n);
+ s << artifact->children.count();
+ foreach (Artifact * n, artifact->children)
+ s << pool.store(n);
+ s << artifact->fileDependencies.count();
+ foreach (Artifact * n, artifact->fileDependencies)
+ s << pool.store(n);
+ s << artifact->sideBySideArtifacts.count();
+ foreach (Artifact *n, artifact->sideBySideArtifacts)
+ s << pool.store(n);
+ }
+
+ // other data
+ s << pool.store(rProduct);
+ storeContainer(targetArtifacts, s, pool);
+ storeContainer(usings, s, pool);
+}
+
+BuildProject::BuildProject(BuildGraph *bg)
+ : m_buildGraph(bg)
+ , m_dirty(false)
+{
+}
+
+BuildProject::~BuildProject()
+{
+ qDeleteAll(m_dependencyArtifacts);
+}
+
+static bool isConfigCompatible(const QVariantMap &userCfg, const QVariantMap &projectCfg)
+{
+ QVariantMap::const_iterator it = userCfg.begin();
+ for (; it != userCfg.end(); ++it) {
+ if (it.value().type() == QVariant::Map) {
+ if (!isConfigCompatible(it.value().toMap(), projectCfg.value(it.key()).toMap()))
+ return false;
+ } else {
+ QVariant value = projectCfg.value(it.key());
+ if (!value.isNull() && value != it.value()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+BuildProject::Ptr BuildProject::load(BuildGraph *bg, const FileTime &minTimeStamp, Configuration::Ptr cfg, const QStringList &loaderSearchPaths)
+{
+ PersistentPool pool;
+ QString fileName;
+ QStringList bgFiles = storedProjectFiles(bg);
+ foreach (const QString &fn, bgFiles) {
+ if (!pool.load(fn, PersistentPool::LoadHeadData))
+ continue;
+ PersistentPool::HeadData headData = pool.headData();
+ if (isConfigCompatible(cfg->value(), headData.projectConfig)) {
+ fileName = fn;
+ break;
+ }
+ }
+ if (fileName.isNull()) {
+ qbsDebug() << "[BG] No stored build graph found that's compatible to the desired build configuration.";
+ return BuildProject::Ptr();
+ }
+
+ BuildProject::Ptr project;
+ qbsDebug() << "[BG] trying to load: " << fileName;
+ FileInfo bgfi(fileName);
+ if (!bgfi.exists()) {
+ qbsDebug() << "[BG] stored build graph file does not exist";
+ return project;
+ }
+ if (!pool.load(fileName))
+ throw Error("Cannot load stored build graph.");
+ project = BuildProject::Ptr(new BuildProject(bg));
+ PersistentObjectData data = pool.getData(0);
+ project->load(pool, data);
+ project->resolvedProject()->configuration = Configuration::Ptr(new Configuration);
+ project->resolvedProject()->configuration->setValue(pool.headData().projectConfig);
+ qbsDebug() << "[BG] stored project loaded.";
+
+ bool projectFileChanged = false;
+ if (bgfi.lastModified() < minTimeStamp) {
+ projectFileChanged = true;
+ }
+
+ QList<BuildProduct::Ptr> changedProducts;
+ foreach (BuildProduct::Ptr product, project->buildProducts()) {
+ FileInfo pfi(product->rProduct->qbsFile);
+ if (!pfi.exists())
+ throw Error(QString("The product file '%1' is gone.").arg(product->rProduct->qbsFile));
+ if (bgfi.lastModified() < pfi.lastModified())
+ changedProducts += product;
+ }
+
+ if (projectFileChanged || !changedProducts.isEmpty()) {
+
+ Loader ldr;
+ ldr.setSearchPaths(loaderSearchPaths);
+ ldr.loadProject(project->resolvedProject()->qbsFile);
+ QFutureInterface<bool> dummyFutureInterface;
+ ResolvedProject::Ptr changedProject = ldr.resolveProject(bg->buildDirectoryRoot(), cfg, dummyFutureInterface);
+ if (!changedProject) {
+ QString msg("Trying to load '%1' failed.");
+ throw Error(msg.arg(project->resolvedProject()->qbsFile));
+ }
+
+ if (projectFileChanged) {
+ qWarning("[BG] project file changed: %s", qPrintable(project->resolvedProject()->qbsFile));
+ qWarning("[BG] ### HANDLING THAT PROPERLY IS NOT YET IMPLEMENTED");
+ qWarning("[BG] ### CONSIDER DELETING THE STORED BUILD GRAPH");
+ }
+
+ QMap<QString, ResolvedProduct::Ptr> changedProductsMap;
+ foreach (BuildProduct::Ptr product, changedProducts) {
+ if (changedProductsMap.isEmpty())
+ foreach (ResolvedProduct::Ptr cp, changedProject->products)
+ changedProductsMap.insert(cp->name, cp);
+ bg->onProductChanged(product, changedProductsMap.value(product->rProduct->name));
+ }
+
+ BuildGraph::detectCycle(project.data());
+ }
+
+ return project;
+}
+
+void BuildProject::store()
+{
+ if (!dirty()) {
+ qbsDebug() << "[BG] build graph is unchanged in project " << resolvedProject()->id << ".";
+ return;
+ }
+ const QString fileName = storedProjectFilePath(buildGraph(), resolvedProject()->id);
+ qbsDebug() << "[BG] storing: " << fileName;
+ PersistentPool pool;
+ PersistentPool::HeadData headData;
+ headData.projectConfig = resolvedProject()->configuration->value();
+ pool.setHeadData(headData);
+ PersistentObjectData data;
+ store(pool, data);
+ pool.setData(0, data);
+ pool.store(fileName);
+}
+
+QString BuildProject::storedProjectFilePath(BuildGraph *bg, const QString &projectId)
+{
+ return bg->buildDirectoryRoot() + projectId + ".bg";
+}
+
+QStringList BuildProject::storedProjectFiles(BuildGraph *bg)
+{
+ QStringList result;
+ QDirIterator dirit(bg->buildDirectoryRoot(), QStringList() << "*.bg", QDir::Files);
+ while (dirit.hasNext())
+ result += dirit.next();
+ return result;
+}
+
+void BuildProject::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+
+ setResolvedProject(pool.idLoadS<ResolvedProject>(s));
+
+ int count, i;
+ s >> count;
+ for (i = count; --i >= 0;) {
+ BuildProduct::Ptr product = pool.idLoadS<BuildProduct>(s);
+ product->project = this;
+ foreach (Artifact *artifact, product->artifacts)
+ artifact->project = this;
+ addBuildProduct(product);
+ }
+
+ s >> count;
+ m_dependencyArtifacts.clear();
+ m_dependencyArtifacts.reserve(count);
+ for (i = count; --i >= 0;) {
+ Artifact *artifact = pool.idLoad<Artifact>(s);
+ artifact->project = this;
+ m_dependencyArtifacts.insert(artifact->fileName, artifact);
+ }
+}
+
+void BuildProject::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+
+ s << pool.store(resolvedProject());
+ storeContainer(m_buildProducts, s, pool);
+ storeHashContainer(m_dependencyArtifacts, s, pool);
+}
+
+char **createCFileTags(const QSet<QString> &fileTags)
+{
+ if (fileTags.isEmpty())
+ return 0;
+
+ char **buf = new char*[fileTags.count()];
+ size_t i = 0;
+ foreach (const QString &fileTag, fileTags) {
+ buf[i] = qstrdup(fileTag.toLocal8Bit().data());
+ ++i;
+ }
+ return buf;
+}
+
+void freeCFileTags(char **cFileTags, int numFileTags)
+{
+ if (!cFileTags)
+ return;
+ for (int i = numFileTags; --i >= 0;)
+ delete[] cFileTags[i];
+ delete[] cFileTags;
+}
+
+BuildGraph * BuildProject::buildGraph() const
+{
+ return m_buildGraph;
+}
+
+ResolvedProject::Ptr BuildProject::resolvedProject() const
+{
+ return m_resolvedProject;
+}
+
+QSet<BuildProduct::Ptr> BuildProject::buildProducts() const
+{
+ return m_buildProducts;
+}
+
+QHash<QString, Artifact *> &BuildProject::dependencyArtifacts()
+{
+ return m_dependencyArtifacts;
+}
+
+bool BuildProject::dirty() const
+{
+ return m_dirty;
+}
+
+Artifact *BuildProject::findArtifact(const QString &filePath) const
+{
+ Artifact *artifact = m_dependencyArtifacts.value(filePath);
+ if (!artifact) {
+ foreach (const BuildProduct::Ptr &product, m_buildProducts) {
+ artifact = product->artifacts.value(filePath);
+ if (artifact)
+ break;
+ }
+ }
+ return artifact;
+}
+
+void BuildProject::markDirty()
+{
+ m_dirty = true;
+}
+
+void BuildProject::addBuildProduct(const BuildProduct::Ptr &product)
+{
+ m_buildProducts.insert(product);
+}
+
+void BuildProject::setResolvedProject(const ResolvedProject::Ptr &resolvedProject)
+{
+ m_resolvedProject = resolvedProject;
+}
+
+QString fileName(Artifact *n)
+{
+ class BuildGraph *bg = n->project->buildGraph();
+ QString str = n->fileName;
+ if (str.startsWith(bg->outputDirectoryRoot()))
+ str.remove(0, bg->outputDirectoryRoot().count());
+ if (str.startsWith('/'))
+ str.remove(0, 1);
+ return str;
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/buildgraph.h b/src/lib/buildgraph/buildgraph.h
new file mode 100644
index 000000000..3c9992748
--- /dev/null
+++ b/src/lib/buildgraph/buildgraph.h
@@ -0,0 +1,216 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef BUILDGRAPH_H
+#define BUILDGRAPH_H
+
+#include "artifactlist.h"
+
+#include <language/language.h>
+#include <tools/error.h>
+#include <tools/persistence.h>
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QSet>
+#include <QtCore/QSharedPointer>
+#include <QtCore/QStringList>
+
+#include <QtCore/QDir>
+#include <QtCore/QVector>
+#include <QtCore/QVariant>
+#include <QtScript/QScriptEngine>
+#include <QtCore/QFutureInterface>
+
+namespace qbs {
+
+class Artifact;
+class Transformer;
+class BuildProject;
+
+class BuildProduct : public PersistentObject
+{
+public:
+ typedef QSharedPointer<BuildProduct> Ptr;
+
+ BuildProduct();
+ ~BuildProduct();
+
+ const QList<Rule::Ptr> &topSortedRules() const;
+
+ BuildProject *project;
+ ResolvedProduct::Ptr rProduct;
+ QSet<Artifact *> targetArtifacts;
+ QList<BuildProduct *> usings;
+ QHash<QString, Artifact *> artifacts;
+
+private:
+ void load(PersistentPool &pool, PersistentObjectData &data);
+ void store(PersistentPool &pool, PersistentObjectData &data) const;
+
+private:
+ mutable QList<Rule::Ptr> m_topSortedRules;
+};
+
+class BuildGraph;
+
+class BuildProject : public PersistentObject
+{
+ friend class BuildGraph;
+public:
+ typedef QSharedPointer<BuildProject> Ptr;
+
+ BuildProject(BuildGraph *bg);
+ ~BuildProject();
+
+ static BuildProject::Ptr load(BuildGraph *bg, const FileTime &minTimeStamp, Configuration::Ptr cfg, const QStringList &loaderSearchPaths);
+ void store();
+
+ BuildGraph *buildGraph() const;
+ ResolvedProject::Ptr resolvedProject() const;
+ QSet<BuildProduct::Ptr> buildProducts() const;
+ QHash<QString, Artifact *> &dependencyArtifacts();
+ bool dirty() const;
+ Artifact *findArtifact(const QString &filePath) const;
+
+private:
+ static QString storedProjectFilePath(BuildGraph *bg, const QString &configId);
+ static QStringList storedProjectFiles(BuildGraph *bg);
+ void load(PersistentPool &pool, PersistentObjectData &data);
+ void store(PersistentPool &pool, PersistentObjectData &data) const;
+ void markDirty();
+ void addBuildProduct(const BuildProduct::Ptr &product);
+ void setResolvedProject(const ResolvedProject::Ptr & resolvedProject);
+
+private:
+ BuildGraph *m_buildGraph;
+ ResolvedProject::Ptr m_resolvedProject;
+ QSet<BuildProduct::Ptr> m_buildProducts;
+ QHash<QString, Artifact *> m_dependencyArtifacts;
+ bool m_dirty;
+};
+
+class BuildGraphListener;
+
+/**
+ * N artifact, T transformer, parent -> child
+ * parent depends on child, child is a dependency of parent,
+ * parent is a dependent of child.
+ *
+ * N a.out -> N main.o -> N main.cpp
+ *
+ * Every artifact can point to a transformer which contains the commands.
+ * Multiple artifacts can point to the same transformer.
+ */
+class BuildGraph
+{
+ Q_DECLARE_TR_FUNCTIONS(BuildGraph)
+public:
+ BuildGraph();
+ ~BuildGraph();
+
+ BuildProject::Ptr resolveProject(ResolvedProject::Ptr, QFutureInterface<bool> &futureInterface);
+ BuildProduct::Ptr resolveProduct(BuildProject *, ResolvedProduct::Ptr, QFutureInterface<bool> &futureInterface);
+
+ void dump(BuildProduct::Ptr) const;
+ void applyRules(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag);
+ static void detectCycle(BuildProject *project);
+ static void detectCycle(Artifact *a);
+
+ void setOutputDirectoryRoot(const QString &buildDirectoryRoot) { m_outputDirectoryRoot = buildDirectoryRoot; }
+ const QString &outputDirectoryRoot() const { return m_outputDirectoryRoot; }
+ QString buildDirectoryRoot() const;
+ QString resolveOutPath(const QString &path, BuildProduct *) const;
+
+ void connect(Artifact *p, Artifact *c);
+ void loggedConnect(Artifact *u, Artifact *v);
+ bool safeConnect(Artifact *u, Artifact *v);
+ void insert(BuildProduct::Ptr target, Artifact *n) const;
+ void insert(BuildProduct *target, Artifact *n) const;
+ void remove(Artifact *artifact) const;
+ void removeArtifactAndExclusiveDependents(Artifact *artifact, QList<Artifact*> *removedArtifacts = 0);
+
+ void createOutputArtifact(BuildProduct *product,
+ const Rule::Ptr &rule, const RuleArtifact::Ptr &ruleArtifact,
+ const QSet<Artifact *> &inputArtifacts,
+ QList<QPair<RuleArtifact *, Artifact *> > *ruleArtifactArtifactMap,
+ QList<Artifact *> *outputArtifacts,
+ QSharedPointer<Transformer> &transformer);
+
+ void onProductChanged(BuildProduct::Ptr product, ResolvedProduct::Ptr changedProduct);
+ void updateNodesThatMustGetNewTransformer();
+
+ static void setupScriptEngineForProduct(QScriptEngine *scriptEngine, ResolvedProduct::Ptr product, Rule::Ptr rule, BuildGraph *bg = 0);
+
+private:
+ Artifact *createArtifact(BuildProduct::Ptr product, SourceArtifact::Ptr sourceArtifact);
+ void applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag, Rule::Ptr rule);
+ void applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag, Rule::Ptr rule, const QSet<Artifact *> &inputArtifacts);
+ void createTransformerCommands(RuleScript::Ptr script, Transformer *transformer);
+ static void disconnect(Artifact *u, Artifact *v);
+ QSet<Artifact *> disconnect(Artifact *n) const;
+ void setupScriptEngineForArtifact(BuildProduct *product, Artifact *artifact);
+ void updateNodeThatMustGetNewTransformer(Artifact *artifact);
+ static void detectCycle(Artifact *v, QSet<Artifact *> &done, QSet<Artifact *> &currentBranch);
+
+private:
+ QString m_outputDirectoryRoot; /// The directory where the 'build' and 'targets' subdirectories end up.
+ QScriptEngine m_scriptEngine;
+ QHash<ResolvedProduct::Ptr, BuildProduct::Ptr> m_productCache;
+ QHash<QString, QScriptValue> m_jsImportCache;
+ QHash<QString, QScriptProgram> m_scriptProgramCache;
+ mutable QSet<Artifact *> m_artifactsThatMustGetNewTransformers;
+};
+
+// debugging helper
+QString fileName(Artifact *n);
+
+// debugging helper
+template <typename T>
+static QStringList toStringList(const T &artifactContainer)
+{
+ QStringList l;
+ foreach (Artifact *n, artifactContainer)
+ l.append(fileName(n));
+ return l;
+}
+
+char **createCFileTags(const QSet<QString> &fileTags);
+void freeCFileTags(char **cFileTags, int numFileTags);
+
+} // namespace qbs
+
+#endif // BUILDGRAPH_H
diff --git a/src/lib/buildgraph/buildgraph.pri b/src/lib/buildgraph/buildgraph.pri
new file mode 100644
index 000000000..779994a74
--- /dev/null
+++ b/src/lib/buildgraph/buildgraph.pri
@@ -0,0 +1,26 @@
+INCLUDEPATH += $$PWD
+SOURCES += \
+ $$PWD/automoc.cpp\
+ $$PWD/buildgraph.cpp\
+ $$PWD/executor.cpp\
+ $$PWD/executorjob.cpp\
+ $$PWD/rulegraph.cpp\
+ $$PWD/scanresultcache.cpp \
+ $$PWD/artifactlist.cpp \
+ $$PWD/command.cpp \
+ $$PWD/commandexecutor.cpp \
+ $$PWD/transformer.cpp \
+ $$PWD/artifact.cpp
+
+HEADERS += \
+ $$PWD/automoc.h\
+ $$PWD/buildgraph.h\
+ $$PWD/executor.h\
+ $$PWD/executorjob.h\
+ $$PWD/rulegraph.h\
+ $$PWD/scanresultcache.h \
+ $$PWD/artifactlist.h \
+ $$PWD/command.h \
+ $$PWD/commandexecutor.h \
+ $$PWD/transformer.h \
+ $$PWD/artifact.h
diff --git a/src/lib/buildgraph/command.cpp b/src/lib/buildgraph/command.cpp
new file mode 100644
index 000000000..189f04b50
--- /dev/null
+++ b/src/lib/buildgraph/command.cpp
@@ -0,0 +1,231 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#include "command.h"
+
+#include <QDebug>
+#include <QScriptEngine>
+#include <QtScript/QScriptValueIterator>
+
+namespace qbs {
+
+AbstractCommand::AbstractCommand()
+ : m_silent(true)
+{
+}
+
+AbstractCommand::~AbstractCommand()
+{
+}
+
+AbstractCommand *AbstractCommand::createByType(AbstractCommand::CommandType commandType)
+{
+ switch (commandType) {
+ case qbs::AbstractCommand::AbstractCommandType:
+ break;
+ case qbs::AbstractCommand::ProcessCommandType:
+ return new ProcessCommand;
+ case qbs::AbstractCommand::JavaScriptCommandType:
+ return new JavaScriptCommand;
+ }
+ return 0;
+}
+
+void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue)
+{
+ m_description = scriptValue->property("description").toString();
+ m_highlight = scriptValue->property("highlight").toString();
+ m_silent = scriptValue->property("silent").toBool();
+}
+
+void AbstractCommand::load(QDataStream &s)
+{
+ s >> m_description >> m_highlight >> m_silent;
+}
+
+void AbstractCommand::store(QDataStream &s)
+{
+ s << m_description << m_highlight << m_silent;
+}
+
+static QScriptValue js_CommandBase(QScriptContext *context, QScriptEngine *engine)
+{
+ static AbstractCommand commandPrototype;
+ QScriptValue cmd = context->thisObject();
+ cmd.setProperty("description", engine->toScriptValue(commandPrototype.description()));
+ cmd.setProperty("highlight", engine->toScriptValue(commandPrototype.highlight()));
+ cmd.setProperty("silent", engine->toScriptValue(commandPrototype.isSilent()));
+ return cmd;
+}
+
+static QScriptValue js_Command(QScriptContext *context, QScriptEngine *engine)
+{
+ if (context->argumentCount() != 2) {
+ return context->throwError(QScriptContext::SyntaxError,
+ "Command c'tor expects 2 arguments");
+ }
+
+ static ProcessCommand commandPrototype;
+
+ QScriptValue program = context->argument(0);
+ if (program.isUndefined())
+ program = engine->toScriptValue(commandPrototype.program());
+ QScriptValue arguments = context->argument(1);
+ if (arguments.isUndefined())
+ arguments = engine->toScriptValue(commandPrototype.arguments());
+ QScriptValue cmd = js_CommandBase(context, engine);
+ cmd.setProperty("className", engine->toScriptValue(QString("Command")));
+ cmd.setProperty("program", program);
+ cmd.setProperty("arguments", arguments);
+ cmd.setProperty("workingDir", engine->toScriptValue(commandPrototype.workingDir()));
+ cmd.setProperty("maxExitCode", engine->toScriptValue(commandPrototype.maxExitCode()));
+ cmd.setProperty("stdoutFilterFunction", engine->toScriptValue(commandPrototype.stdoutFilterFunction()));
+ cmd.setProperty("stderrFilterFunction", engine->toScriptValue(commandPrototype.stderrFilterFunction()));
+ cmd.setProperty("responseFileThreshold", engine->toScriptValue(commandPrototype.responseFileThreshold()));
+ cmd.setProperty("responseFileUsagePrefix", engine->toScriptValue(commandPrototype.responseFileUsagePrefix()));
+ return cmd;
+}
+
+
+void ProcessCommand::setupForJavaScript(QScriptEngine *engine)
+{
+ QScriptValue ctor = engine->newFunction(js_Command, 2);
+ engine->globalObject().setProperty("Command", ctor);
+}
+
+ProcessCommand::ProcessCommand()
+ : m_maxExitCode(0),
+ m_responseFileThreshold(32000)
+{
+}
+
+void ProcessCommand::fillFromScriptValue(const QScriptValue *scriptValue)
+{
+ AbstractCommand::fillFromScriptValue(scriptValue);
+ m_program = scriptValue->property("program").toString();
+ m_arguments = scriptValue->property("arguments").toVariant().toStringList();
+ m_workingDir = scriptValue->property("workingDirectory").toString();
+ m_maxExitCode = scriptValue->property("maxExitCode").toInt32();
+ m_stdoutFilterFunction = scriptValue->property("stdoutFilterFunction").toString();
+ m_stderrFilterFunction = scriptValue->property("stderrFilterFunction").toString();
+ m_responseFileThreshold = scriptValue->property("responseFileThreshold").toInt32();
+ m_responseFileUsagePrefix = scriptValue->property("responseFileUsagePrefix").toString();
+}
+
+void ProcessCommand::load(QDataStream &s)
+{
+ AbstractCommand::load(s);
+ s >> m_program
+ >> m_arguments
+ >> m_workingDir
+ >> m_maxExitCode
+ >> m_stdoutFilterFunction
+ >> m_stderrFilterFunction
+ >> m_responseFileThreshold
+ >> m_responseFileUsagePrefix;
+}
+
+void ProcessCommand::store(QDataStream &s)
+{
+ AbstractCommand::store(s);
+ s << m_program
+ << m_arguments
+ << m_workingDir
+ << m_maxExitCode
+ << m_stdoutFilterFunction
+ << m_stderrFilterFunction
+ << m_responseFileThreshold
+ << m_responseFileUsagePrefix;
+}
+
+static QScriptValue js_JavaScriptCommand(QScriptContext *context, QScriptEngine *engine)
+{
+ if (context->argumentCount() != 0) {
+ return context->throwError(QScriptContext::SyntaxError,
+ "JavaScriptCommand c'tor doesn't take arguments.");
+ }
+
+ static JavaScriptCommand commandPrototype;
+ QScriptValue cmd = js_CommandBase(context, engine);
+ cmd.setProperty("className", engine->toScriptValue(QString("JavaScriptCommand")));
+ cmd.setProperty("sourceCode", engine->toScriptValue(commandPrototype.sourceCode()));
+ return cmd;
+}
+
+void JavaScriptCommand::setupForJavaScript(QScriptEngine *engine)
+{
+ QScriptValue ctor = engine->newFunction(js_JavaScriptCommand, 0);
+ engine->globalObject().setProperty("JavaScriptCommand", ctor);
+}
+
+JavaScriptCommand::JavaScriptCommand()
+{
+}
+
+void JavaScriptCommand::fillFromScriptValue(const QScriptValue *scriptValue)
+{
+ AbstractCommand::fillFromScriptValue(scriptValue);
+ QScriptValue sourceCode = scriptValue->property("sourceCode");
+ if (sourceCode.isFunction()) {
+ m_sourceCode = "(" + sourceCode.toString() + ")()";
+ } else {
+ m_sourceCode = sourceCode.toString();
+ }
+ static QSet<QString> predefinedProperties = QSet<QString>()
+ << "description" << "highlight" << "silent" << "className" << "sourceCode";
+
+ QScriptValueIterator it(*scriptValue);
+ while (it.hasNext()) {
+ it.next();
+ if (predefinedProperties.contains(it.name()))
+ continue;
+ m_properties.insert(it.name(), it.value().toVariant());
+ }
+}
+
+void JavaScriptCommand::load(QDataStream &s)
+{
+ AbstractCommand::load(s);
+ s >> m_sourceCode >> m_properties;
+}
+
+void JavaScriptCommand::store(QDataStream &s)
+{
+ AbstractCommand::store(s);
+ s << m_sourceCode << m_properties;
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/command.h b/src/lib/buildgraph/command.h
new file mode 100644
index 000000000..14bd11f00
--- /dev/null
+++ b/src/lib/buildgraph/command.h
@@ -0,0 +1,154 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <QtCore/QStringList>
+#include <QtCore/QVariantMap>
+
+QT_BEGIN_NAMESPACE
+class QScriptEngine;
+class QScriptValue;
+QT_END_NAMESPACE
+
+namespace qbs {
+
+class AbstractCommand
+{
+public:
+ AbstractCommand();
+ virtual ~AbstractCommand();
+
+ enum CommandType {
+ AbstractCommandType,
+ ProcessCommandType,
+ JavaScriptCommandType
+ };
+
+ static AbstractCommand *createByType(CommandType commandType);
+
+ virtual CommandType type() const { return AbstractCommandType; }
+ virtual void fillFromScriptValue(const QScriptValue *scriptValue);
+ virtual void load(QDataStream &s);
+ virtual void store(QDataStream &s);
+
+ const QString description() const { return m_description; }
+ void setDescription(const QString &str) { m_description = str; }
+
+ const QString highlight() const { return m_highlight; }
+ void setHighlight(const QString &str) { m_highlight = str; }
+
+ bool isSilent() const { return m_silent; }
+ void setSilent(bool b) { m_silent = b; }
+
+private:
+ QString m_description;
+ QString m_highlight;
+ bool m_silent;
+};
+
+class ProcessCommand : public AbstractCommand
+{
+public:
+ static void setupForJavaScript(QScriptEngine *engine);
+
+ ProcessCommand();
+
+ CommandType type() const { return ProcessCommandType; }
+ void fillFromScriptValue(const QScriptValue *scriptValue);
+ void load(QDataStream &s);
+ void store(QDataStream &s);
+
+ const QString program() const { return m_program; }
+ void setProgram(const QString &str) { m_program = str; }
+
+ const QStringList arguments() const { return m_arguments; }
+ void setArguments(const QStringList &l) { m_arguments = l; }
+
+ const QString workingDir() const { return m_workingDir; }
+ void setWorkingDir(const QString &str) { m_workingDir = str; }
+
+ int maxExitCode() const { return m_maxExitCode; }
+ void setMaxExitCode(int n) { m_maxExitCode = n; }
+
+ QString stdoutFilterFunction() const { return m_stdoutFilterFunction; }
+ void setStdoutFilterFunction(const QString &filter) { m_stdoutFilterFunction = filter; }
+
+ QString stderrFilterFunction() const { return m_stderrFilterFunction; }
+ void setStderrFilterFunction(const QString &filter) { m_stderrFilterFunction = filter; }
+
+ int responseFileThreshold() const { return m_responseFileThreshold; }
+ void setResponseFileThreshold(int n) { m_responseFileThreshold = n; }
+
+ QString responseFileUsagePrefix() const { return m_responseFileUsagePrefix; }
+ void setResponseFileUsagePrefix(const QString &function) { m_responseFileUsagePrefix = function; }
+
+private:
+ QString m_program;
+ QStringList m_arguments;
+ QString m_workingDir;
+ int m_maxExitCode;
+ QString m_stdoutFilterFunction;
+ QString m_stderrFilterFunction;
+ int m_responseFileThreshold; // When to use response files? In bytes of (program name + arguments).
+ QString m_responseFileUsagePrefix;
+};
+
+class JavaScriptCommand : public AbstractCommand
+{
+public:
+ static void setupForJavaScript(QScriptEngine *engine);
+
+ JavaScriptCommand();
+
+ virtual CommandType type() const { return JavaScriptCommandType; }
+ void fillFromScriptValue(const QScriptValue *scriptValue);
+ void load(QDataStream &s);
+ void store(QDataStream &s);
+
+ const QString &sourceCode() const { return m_sourceCode; }
+ void setSourceCode(const QString &str) { m_sourceCode = str; }
+ const QVariantMap &properties() const { return m_properties; }
+
+private:
+ QString m_sourceCode;
+ QVariantMap m_properties;
+};
+
+} // namespace qbs
+
+#endif // COMMAND_H
diff --git a/src/lib/buildgraph/commandexecutor.cpp b/src/lib/buildgraph/commandexecutor.cpp
new file mode 100644
index 000000000..7a72a6c98
--- /dev/null
+++ b/src/lib/buildgraph/commandexecutor.cpp
@@ -0,0 +1,376 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#include "commandexecutor.h"
+#include "command.h"
+#include "buildgraph.h"
+#include "processoutput.h"
+
+#include <buildgraph/artifact.h>
+#include <buildgraph/transformer.h>
+#include <tools/logger.h>
+
+#include <QtConcurrentRun>
+#include <QDebug>
+#include <QFutureWatcher>
+#include <QProcess>
+#include <QScriptEngine>
+#include <QThread>
+#include <QTemporaryFile>
+
+namespace qbs {
+
+struct JavaScriptCommandFutureResult
+{
+ bool success;
+ QString errorMessage;
+};
+
+class JavaScriptCommandFutureWatcher : public QFutureWatcher<JavaScriptCommandFutureResult>
+{
+public:
+ JavaScriptCommandFutureWatcher(QObject *parent)
+ : QFutureWatcher<JavaScriptCommandFutureResult>(parent)
+ {}
+};
+
+CommandExecutor::CommandExecutor(QObject *parent)
+ : QObject(parent)
+ , m_processCommand(0)
+ , m_process(0)
+ , m_mainThreadScriptEngine(0)
+ , m_transformer(0)
+ , m_jsCommand(0)
+ , m_jsFutureWatcher(0)
+{
+ connect(&m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onProcessError(QProcess::ProcessError)));
+ connect(&m_process, SIGNAL(finished(int)), this, SLOT(onProcessFinished(int)));
+}
+
+void CommandExecutor::setProcessEnvironment(const QProcessEnvironment &processEnvironment)
+{
+ m_process.setProcessEnvironment(processEnvironment);
+}
+
+void CommandExecutor::waitForFinished()
+{
+ if (m_process.state() == QProcess::Running)
+ m_process.waitForFinished(-1);
+ if (m_jsFutureWatcher && m_jsFutureWatcher->isRunning())
+ m_jsFutureWatcher->waitForFinished();
+}
+
+void CommandExecutor::start(Transformer *transformer, AbstractCommand *cmd)
+{
+ m_processCommand = 0;
+ m_jsCommand = 0;
+
+ switch (cmd->type()) {
+ case AbstractCommand::AbstractCommandType:
+ qWarning("CommandExecutor can't execute abstract commands.");
+ return;
+ case AbstractCommand::ProcessCommandType:
+ m_processCommand = static_cast<ProcessCommand*>(cmd);
+ startProcessCommand();
+ return;
+ case AbstractCommand::JavaScriptCommandType:
+ m_jsCommand = static_cast<JavaScriptCommand*>(cmd);
+ m_transformer = transformer;
+ startJavaScriptCommand();
+ return;
+ }
+
+ emit error("CommandExecutor: unknown command type.");
+ return;
+}
+
+static QHash<QString, TextColor> setupColorTable()
+{
+ QHash<QString, TextColor> colorTable;
+ colorTable["compiler"] = TextColorDefault;
+ colorTable["linker"] = TextColorDarkGreen;
+ colorTable["codegen"] = TextColorDarkYellow;
+ return colorTable;
+}
+
+void CommandExecutor::printCommandInfo(AbstractCommand *cmd)
+{
+ if (!cmd->description().isEmpty()) {
+ static QHash<QString, TextColor> colorTable = setupColorTable();
+ qbsInfo() << DontPrintLogLevel << LogOutputStdOut
+ << colorTable.value(cmd->highlight(), TextColorDefault)
+ << cmd->description();
+ }
+}
+
+void CommandExecutor::startProcessCommand()
+{
+ Q_ASSERT(m_process.state() == QProcess::NotRunning);
+
+ printCommandInfo(m_processCommand);
+ if (!m_processCommand->isSilent()) {
+ QString commandLine = m_processCommand->program() + QLatin1Char(' ') + m_processCommand->arguments().join(" ");
+ qbsInfo() << DontPrintLogLevel << LogOutputStdOut << commandLine;
+ }
+ if (qbsLogLevel(LoggerDebug)) {
+ qbsDebug() << "[EXEC] " << m_processCommand->program() + QLatin1Char(' ') + m_processCommand->arguments().join(" ");
+ }
+
+ // Automatically use response files, if the command line gets to long.
+ QStringList arguments = m_processCommand->arguments();
+ if (!m_processCommand->responseFileUsagePrefix().isEmpty()) {
+ int commandLineLength = m_processCommand->program().length() + 1;
+ for (int i = m_processCommand->arguments().count(); --i >= 0;)
+ commandLineLength += m_processCommand->arguments().at(i).length();
+ if (m_processCommand->responseFileThreshold() >= 0 && commandLineLength > m_processCommand->responseFileThreshold()) {
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] Using response file. Threshold is %d. Command line length %d.", m_processCommand->responseFileThreshold(), commandLineLength);
+
+ // The QTemporaryFile keeps a handle on the file, even if closed.
+ // On Windows, some commands (e.g. msvc link.exe) won't accept that.
+ // We need to delete the file manually, later.
+ QTemporaryFile responseFile;
+ responseFile.setAutoRemove(false);
+ responseFile.setFileTemplate(QDir::tempPath() + "/qbsresp");
+ if (!responseFile.open()) {
+ QString errorMessage = "Cannot create response file.";
+ emit error(errorMessage);
+ return;
+ }
+ for (int i = 0; i < m_processCommand->arguments().count(); ++i) {
+ responseFile.write(m_processCommand->arguments().at(i).toLocal8Bit());
+ responseFile.write("\n");
+ }
+ responseFile.close();
+ arguments.clear();
+ arguments += QDir::toNativeSeparators(m_processCommand->responseFileUsagePrefix() + responseFile.fileName());
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] command line with response file: %s %s", qPrintable(m_processCommand->program()), qPrintable(arguments.join(" ")));
+ }
+ }
+
+ m_process.setWorkingDirectory(m_processCommand->workingDir());
+ m_process.start(m_processCommand->program(), arguments);
+}
+
+QByteArray CommandExecutor::filterProcessOutput(const QByteArray &output, const QString &filterFunctionSource)
+{
+ if (filterFunctionSource.isEmpty())
+ return output;
+
+ QScriptValue filterFunction = m_mainThreadScriptEngine->evaluate("var f = " + filterFunctionSource + "; f");
+ if (!filterFunction.isFunction()) {
+ emit error(QString("Error in filter function: %1.\n%2").arg(filterFunctionSource, filterFunction.toString()));
+ return output;
+ }
+
+ QScriptValue outputArg = m_mainThreadScriptEngine->newArray(1);
+ outputArg.setProperty(0, m_mainThreadScriptEngine->toScriptValue(QString::fromLatin1(output)));
+ QScriptValue filteredOutput = filterFunction.call(m_mainThreadScriptEngine->undefinedValue(), outputArg);
+ if (filteredOutput.isError()) {
+ emit error(QString("Error when calling ouput filter function: %1").arg(filteredOutput.toString()));
+ return output;
+ }
+
+ return filteredOutput.toString().toLocal8Bit();
+}
+
+void CommandExecutor::sendProcessOutput(bool logCommandLine)
+{
+ QString commandLine = m_processCommand->program();
+ if (!m_processCommand->arguments().isEmpty()) {
+ commandLine += ' ';
+ commandLine += m_processCommand->arguments().join(" ");
+ }
+
+ QByteArray processStdOut = filterProcessOutput(m_process.readAllStandardOutput(), m_processCommand->stdoutFilterFunction());
+ QByteArray processStdErr = filterProcessOutput(m_process.readAllStandardError(), m_processCommand->stderrFilterFunction());
+
+ bool processOutputEmpty = processStdOut.isEmpty() && processStdErr.isEmpty();
+ if (logCommandLine || !processOutputEmpty) {
+ qbsInfo() << DontPrintLogLevel << commandLine << (processOutputEmpty ? "" : "\n")
+ << processStdOut << processStdErr;
+ }
+
+ ProcessOutput processOutput;
+ processOutput.setCommandLine(commandLine);
+ processOutput.setStandardOutput(processStdOut);
+ processOutput.setStandardError(processStdErr);
+ Logger::instance().sendProcessOutput(processOutput);
+}
+
+void CommandExecutor::onProcessError(QProcess::ProcessError processError)
+{
+ removeResponseFile();
+ sendProcessOutput(true);
+ QString errorMessage;
+ switch (processError) {
+ case QProcess::FailedToStart:
+ errorMessage = "Process could not be started.";
+ break;
+ case QProcess::Crashed:
+ errorMessage = "Process crashed.";
+ break;
+ case QProcess::Timedout:
+ errorMessage = "Process timed out.";
+ break;
+ case QProcess::ReadError:
+ errorMessage = "Error when reading process output.";
+ break;
+ case QProcess::WriteError:
+ errorMessage = "Error when writing to process.";
+ break;
+ default:
+ errorMessage = "Unknown process error.";
+ break;
+ }
+ emit error(errorMessage);
+}
+
+void CommandExecutor::onProcessFinished(int exitCode)
+{
+ removeResponseFile();
+ bool errorOccurred = exitCode > m_processCommand->maxExitCode();
+ sendProcessOutput(errorOccurred);
+ if (errorOccurred) {
+ QString msg = "Process failed with exit code %1.";
+ emit error(msg.arg(exitCode));
+ return;
+ }
+
+ emit finished();
+}
+
+class JSRunner
+{
+public:
+ typedef JavaScriptCommandFutureResult result_type;
+
+ JSRunner(JavaScriptCommand *jsCommand)
+ : m_jsCommand(jsCommand)
+ {}
+
+ JavaScriptCommandFutureResult operator() (Transformer *transformer)
+ {
+ result_type result;
+ result.success = true;
+ QThread *currentThread = QThread::currentThread();
+ QScriptEngine *scriptEngine = m_scriptEnginesPerThread.value(currentThread);
+ if (!scriptEngine) {
+ scriptEngine = new QScriptEngine();
+ m_scriptEnginesPerThread.insert(currentThread, scriptEngine);
+
+ // import script extension plugins
+ foreach (const QString &name, scriptEngine->availableExtensions()) {
+ if (!name.startsWith("qbs"))
+ continue;
+ QScriptValue e = scriptEngine->importExtension(name);
+ if (e.isError()) {
+ qbsWarning("JS thread %x, unable to load %s into QScriptEngine: %s",
+ (void*)currentThread,
+ qPrintable(name),
+ qPrintable(e.toString()));
+ }
+ qbsDebug("JS thread %x, script plugin loaded: %s", (void*)currentThread, qPrintable(name));
+ }
+ }
+
+ QString trafoPtrStr = QString::number((qulonglong)transformer);
+ if (scriptEngine->globalObject().property("_qbs_transformer_ptr").toString() != trafoPtrStr) {
+ scriptEngine->globalObject().setProperty("_qbs_transformer_ptr", scriptEngine->toScriptValue(trafoPtrStr));
+
+ Artifact *someOutputArtifact = *transformer->outputs.begin();
+ if (someOutputArtifact->product) {
+ ResolvedProduct::Ptr product = someOutputArtifact->product->rProduct;
+ BuildGraph::setupScriptEngineForProduct(scriptEngine, product, transformer->rule);
+ }
+ transformer->setupInputs(scriptEngine, scriptEngine->globalObject());
+ transformer->setupOutputs(scriptEngine, scriptEngine->globalObject());
+ }
+
+ scriptEngine->pushContext();
+ for (QVariantMap::const_iterator it = m_jsCommand->properties().constBegin(); it != m_jsCommand->properties().constEnd(); ++it)
+ scriptEngine->currentContext()->activationObject().setProperty(it.key(), scriptEngine->toScriptValue(it.value()));
+ scriptEngine->evaluate(m_jsCommand->sourceCode());
+ if (scriptEngine->hasUncaughtException()) {
+ result.success = false;
+ result.errorMessage = scriptEngine->uncaughtException().toString();
+ }
+ scriptEngine->popContext();
+ return result;
+ }
+
+private:
+ static QHash<QThread *, QScriptEngine *> m_scriptEnginesPerThread;
+ JavaScriptCommand *m_jsCommand;
+};
+
+QHash<QThread *, QScriptEngine *> JSRunner::m_scriptEnginesPerThread;
+
+void CommandExecutor::startJavaScriptCommand()
+{
+ printCommandInfo(m_jsCommand);
+ QFuture<JSRunner::result_type> future = QtConcurrent::run(JSRunner(m_jsCommand), m_transformer);
+ if (!m_jsFutureWatcher) {
+ m_jsFutureWatcher = new JavaScriptCommandFutureWatcher(this);
+ connect(m_jsFutureWatcher, SIGNAL(finished()), SLOT(onJavaScriptCommandFinished()));
+ }
+ m_jsFutureWatcher->setFuture(future);
+}
+
+void CommandExecutor::onJavaScriptCommandFinished()
+{
+ JavaScriptCommandFutureResult result = m_jsFutureWatcher->future().result();
+ if (result.success) {
+ emit finished();
+ } else {
+ qbsInfo() << DontPrintLogLevel << "JS context:\n" << m_jsCommand->properties();
+ qbsInfo() << DontPrintLogLevel << "JS code:\n" << m_jsCommand->sourceCode();
+ QString msg = "Error while executing JavaScriptCommand: ";
+ msg += result.errorMessage;
+ emit error(msg);
+ }
+}
+
+void CommandExecutor::removeResponseFile()
+{
+ if (m_responseFileName.isEmpty())
+ return;
+ QFile::remove(m_responseFileName);
+ m_responseFileName.clear();
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/commandexecutor.h b/src/lib/buildgraph/commandexecutor.h
new file mode 100644
index 000000000..3b5a1273f
--- /dev/null
+++ b/src/lib/buildgraph/commandexecutor.h
@@ -0,0 +1,106 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#ifndef COMMANDEXECUTOR_H
+#define COMMANDEXECUTOR_H
+
+#include <QObject>
+#include <QProcess>
+
+QT_BEGIN_NAMESPACE
+class QProcess;
+class QScriptEngine;
+QT_END_NAMESPACE
+
+namespace qbs {
+
+class AbstractCommand;
+class ProcessCommand;
+class JavaScriptCommand;
+class JavaScriptCommandFutureWatcher;
+class Transformer;
+
+class CommandExecutor : public QObject
+{
+ Q_OBJECT
+public:
+ explicit CommandExecutor(QObject *parent = 0);
+
+ void setMainThreadScriptEngine(QScriptEngine *engine) { m_mainThreadScriptEngine = engine; }
+ void setDryRunEnabled(bool enabled) { dryRun = enabled; }
+ void setProcessEnvironment(const QProcessEnvironment &processEnvironment);
+ void waitForFinished();
+
+signals:
+ void error(QString errorString);
+ void finished();
+
+public slots:
+ void start(Transformer *transformer, AbstractCommand *cmd);
+
+protected:
+ void printCommandInfo(AbstractCommand *cmd);
+ void startProcessCommand();
+ QByteArray filterProcessOutput(const QByteArray &output, const QString &filterFunctionSource);
+ void sendProcessOutput(bool logCommandLine = false);
+ void startJavaScriptCommand();
+
+protected slots:
+ void onProcessError(QProcess::ProcessError);
+ void onProcessFinished(int exitCode);
+ void onJavaScriptCommandFinished();
+
+private:
+ void removeResponseFile();
+
+private:
+ bool dryRun;
+
+ // members for executing ProcessCommand
+ ProcessCommand *m_processCommand;
+ QProcess m_process;
+ QString m_responseFileName;
+ QScriptEngine *m_mainThreadScriptEngine;
+
+ // members for executing JavaScriptCommand members
+ Transformer *m_transformer;
+ JavaScriptCommand *m_jsCommand;
+ JavaScriptCommandFutureWatcher *m_jsFutureWatcher;
+};
+
+} // namespace qbs
+
+#endif // COMMANDEXECUTOR_H
diff --git a/src/lib/buildgraph/executor.cpp b/src/lib/buildgraph/executor.cpp
new file mode 100644
index 000000000..4b3e8b38b
--- /dev/null
+++ b/src/lib/buildgraph/executor.cpp
@@ -0,0 +1,885 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "executor.h"
+#include "executorjob.h"
+#include "scanresultcache.h"
+#include "automoc.h"
+
+#include <buildgraph/transformer.h>
+#include <language/language.h>
+#include <tools/fileinfo.h>
+#include <tools/logger.h>
+#include <tools/scannerpluginmanager.h>
+
+#ifdef Q_OS_WIN32
+#include <Windows.h>
+#endif
+
+namespace qbs {
+
+static QHashDummyValue hashDummy;
+
+Executor::Executor(int maxJobs)
+ : m_scriptEngine(0)
+ , m_runOnceAndForgetMode(false)
+ , m_state(ExecutorIdle)
+ , m_keepGoing(false)
+ , m_maximumJobNumber(maxJobs)
+ , m_futureInterface(0)
+{
+ m_autoMoc = new AutoMoc;
+ m_autoMoc->setScanResultCache(&m_scanResultCache);
+ if (!m_runOnceAndForgetMode) {
+ connect(this, SIGNAL(finished()), SLOT(resetArtifactsToUntouched()));
+ }
+ qbsDebug("[EXEC] preparing executor for %d jobs in parallel", maxJobs);
+ addExecutorJobs(maxJobs);
+}
+
+Executor::~Executor()
+{
+ // jobs must be destroyed before deleting the shared scan result cache
+ foreach (ExecutorJob *job, m_availableJobs)
+ delete job;
+ foreach (ExecutorJob *job, m_processingJobs.keys())
+ delete job;
+ delete m_autoMoc;
+}
+
+void Executor::build(const QList<BuildProject::Ptr> projectsToBuild, const QStringList &changedFiles, const QStringList &selectedProductNames,
+ QFutureInterface<bool> &futureInterface)
+{
+ Q_ASSERT(m_state != ExecutorRunning);
+ m_leaves.clear();
+ m_futureInterface = &futureInterface;
+ bool success = true;
+
+ setState(ExecutorRunning);
+ Artifact::BuildState initialBuildState = changedFiles.isEmpty() ? Artifact::Buildable : Artifact::Built;
+
+ if (!m_scriptEngine) {
+ m_scriptEngine = new QScriptEngine(this);
+ foreach (ExecutorJob *job, findChildren<ExecutorJob *>())
+ job->setMainThreadScriptEngine(m_scriptEngine);
+ }
+
+ // determine the products we want to build
+ m_projectsToBuild = projectsToBuild;
+ if (selectedProductNames.isEmpty()) {
+ // Use all products we have in the build graph.
+ m_productsToBuild.clear();
+ foreach (BuildProject::Ptr project, m_projectsToBuild)
+ foreach (BuildProduct::Ptr product, project->buildProducts())
+ m_productsToBuild += product;
+ } else {
+ // Try to find the selected products and their dependencies.
+ QHash<QString, BuildProduct::Ptr> productsPerName;
+ foreach (BuildProject::Ptr project, m_projectsToBuild)
+ foreach (BuildProduct::Ptr product, project->buildProducts())
+ productsPerName.insert(product->rProduct->name.toLower(), product);
+
+ QSet<BuildProduct::Ptr> selectedProducts;
+ foreach (const QString &productName, selectedProductNames) {
+ BuildProduct::Ptr product = productsPerName.value(productName.toLower());
+ if (!product) {
+ qbsWarning() << "Selected product " << productName << " not found.";
+ continue;
+ }
+ selectedProducts += product;
+ }
+ QSet<BuildProduct::Ptr> s = selectedProducts;
+ do {
+ QSet<BuildProduct::Ptr> t;
+ foreach (const BuildProduct::Ptr &product, s)
+ foreach (BuildProduct *dependency, product->usings)
+ t += productsPerName.value(dependency->rProduct->name.toLower());
+ selectedProducts += t;
+ s = t;
+ } while (!s.isEmpty());
+ m_productsToBuild = selectedProducts.toList();
+ }
+
+ QList<Artifact *> changedArtifacts;
+ foreach (const QString &filePath, changedFiles) {
+ Artifact *artifact = 0;
+ foreach (BuildProject::Ptr project, m_projectsToBuild) {
+ artifact = project->findArtifact(filePath);
+ if (artifact)
+ break;
+ }
+ if (!artifact) {
+ qbsWarning() << QString("Out of date file '%1' provided but not found.").arg(QDir::toNativeSeparators(filePath));
+ continue;
+ }
+ changedArtifacts += artifact;
+ }
+
+ // prepare products
+ const QProcessEnvironment systemEnvironment = QProcessEnvironment::systemEnvironment();
+ foreach (BuildProduct::Ptr product, m_productsToBuild) {
+ try {
+ product->rProduct->setupBuildEnvironment(m_scriptEngine, systemEnvironment);
+ } catch (Error &e) {
+ setError(e.toString());
+ return;
+ }
+ foreach (Artifact *artifact, product->artifacts)
+ if (artifact->artifactType == Artifact::SourceFile)
+ artifact->buildState = initialBuildState;
+ }
+
+ // find the root nodes
+ m_roots.clear();
+ foreach (BuildProduct::Ptr product, m_productsToBuild)
+ foreach (Artifact *rootArtifact, product->targetArtifacts)
+ m_roots += rootArtifact;
+
+ // mark the artifacts we want to build
+ prepareBuildGraph(initialBuildState);
+
+ // determine which artifacts are out of date
+ const bool changedFilesProvided = !changedFiles.isEmpty();
+ if (!changedFilesProvided) {
+ doOutOfDateCheck();
+ }
+
+ if (success)
+ success = runAutoMoc();
+ if (success) {
+ if (changedFilesProvided) {
+ foreach (Artifact *artifact, changedArtifacts)
+ scanFileDependencies(artifact);
+ } else {
+ doDependencyScanTopDown();
+ updateBuildGraph(initialBuildState);
+ }
+ initLeaves(changedArtifacts);
+ }
+
+ m_futureInterface->setProgressRange(0 , m_leaves.count());
+
+ if (success) {
+ bool stillArtifactsToExecute = run(futureInterface);
+
+ if (!stillArtifactsToExecute)
+ finish();
+ }
+}
+
+void Executor::setDryRun(bool b)
+{
+ foreach (ExecutorJob *job, m_availableJobs)
+ job->setDryRun(b);
+}
+
+void Executor::setMaximumJobs(int numberOfJobs)
+{
+ if (numberOfJobs == m_maximumJobNumber)
+ return;
+
+ m_maximumJobNumber = numberOfJobs;
+ int actualJobNumber = m_availableJobs.count() + m_processingJobs.count();
+ if (actualJobNumber > m_maximumJobNumber) {
+ removeExecutorJobs(actualJobNumber - m_maximumJobNumber);
+ } else {
+ addExecutorJobs(m_maximumJobNumber - actualJobNumber);
+ }
+}
+
+int Executor::maximumJobs() const
+{
+ return m_maximumJobNumber;
+}
+
+bool Executor::isLeaf(Artifact *artifact)
+{
+ foreach (Artifact *child, artifact->children)
+ if (child->buildState != Artifact::Built)
+ return false;
+ return true;
+}
+
+static void markAsOutOfDateBottomUp(Artifact *artifact)
+{
+ if (artifact->buildState == Artifact::Untouched)
+ return;
+ artifact->buildState = Artifact::Buildable;
+ artifact->outOfDateCheckPerformed = true;
+ artifact->isOutOfDate = true;
+ artifact->isExistingFile = FileInfo(artifact->fileName).exists();
+ foreach (Artifact *parent, artifact->parents)
+ markAsOutOfDateBottomUp(parent);
+}
+
+void Executor::initLeaves(const QList<Artifact *> &changedArtifacts)
+{
+ if (changedArtifacts.isEmpty()) {
+ QSet<Artifact *> seenArtifacts;
+ foreach (Artifact *root, m_roots)
+ initLeavesTopDown(root, seenArtifacts);
+ } else {
+ foreach (Artifact *artifact, changedArtifacts) {
+ m_leaves.insert(artifact, hashDummy);
+ markAsOutOfDateBottomUp(artifact);
+ }
+ }
+}
+
+void Executor::initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts)
+{
+ if (seenArtifacts.contains(artifact))
+ return;
+ seenArtifacts += artifact;
+
+
+ if (artifact->children.isEmpty()) {
+ m_leaves.insert(artifact, hashDummy);
+ } else {
+ foreach (Artifact *child, artifact->children)
+ initLeavesTopDown(child, seenArtifacts);
+ }
+}
+
+/**
+ * Returns true if there are still artifacts to traverse.
+ */
+bool Executor::run(QFutureInterface<bool> &futureInterface)
+{
+ while (m_state == ExecutorRunning) {
+ if (m_futureInterface->isCanceled()) {
+ qbsInfo() << "Build canceled.";
+ cancelJobs();
+ return false;
+ }
+
+ futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ if (m_leaves.isEmpty())
+ return !m_processingJobs.isEmpty();
+
+ Artifact *leaf = m_leaves.begin().key();
+ if (!execute(leaf))
+ return true;
+ }
+ return false;
+}
+
+FileTime Executor::timeStamp(Artifact *artifact)
+{
+ FileTime result = m_timeStampCache.value(artifact);
+ if (result.isValid())
+ return result;
+
+ FileInfo fi(artifact->fileName);
+ if (!fi.exists())
+ return FileTime::currentTime();
+
+ result = fi.lastModified();
+ foreach (Artifact *child, artifact->children) {
+ const FileTime childTime = timeStamp(child);
+ if (result < childTime)
+ result = childTime;
+ }
+ foreach (Artifact *fileDependency, artifact->fileDependencies) {
+ const FileTime ft = timeStamp(fileDependency);
+ if (result < ft)
+ result = ft;
+ }
+
+ m_timeStampCache.insert(artifact, result);
+ return result;
+}
+
+bool Executor::isOutOfDate(Artifact *artifact, bool &fileExists)
+{
+ FileInfo fi(artifact->fileName);
+ fileExists = fi.exists();
+ if (!fileExists)
+ return true;
+
+ FileTime artifactTimeStamp = fi.lastModified();
+ foreach (Artifact *child, artifact->children) {
+ if (artifactTimeStamp < timeStamp(child))
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns false if the artifact cannot be executed right now
+ * and should be looked at later.
+ */
+bool Executor::execute(Artifact *artifact)
+{
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[EXEC] " << fileName(artifact);
+
+// artifact->project->buildGraph()->dump(*artifact->products.begin());
+
+ if (m_availableJobs.isEmpty()) {
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] No jobs available. Trying later.");
+ return false;
+ }
+
+ if (!artifact->outOfDateCheckPerformed)
+ doOutOfDateCheck(artifact);
+ bool fileExists = artifact->isExistingFile;
+ bool isDirty = artifact->isOutOfDate;
+ m_leaves.remove(artifact);
+
+ if (!fileExists && artifact->artifactType == Artifact::SourceFile) {
+ setError(QString("Can't find source file '%1'.").arg(artifact->fileName));
+ return true;
+ }
+
+ if (!artifact->transformer) {
+ if (!fileExists)
+ qbsWarning() << tr("No transformer builds '%1'").arg(QDir::toNativeSeparators(artifact->fileName));
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] No transformer. Skipping.");
+ finishArtifact(artifact);
+ return true;
+ } else if (!isDirty) {
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] Up to date. Skipping.");
+ finishArtifact(artifact);
+ return true;
+ } else {
+ foreach (Artifact *sideBySideArtifact, artifact->sideBySideArtifacts) {
+ if (sideBySideArtifact->transformer != artifact->transformer)
+ continue;
+ switch (sideBySideArtifact->buildState)
+ {
+ case Artifact::Untouched:
+ case Artifact::Buildable:
+ break;
+ case Artifact::Built:
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] Side by side artifact already finished. Skipping.");
+ finishArtifact(artifact);
+ return true;
+ case Artifact::Building:
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug("[EXEC] Side by side artifact processing. Skipping.");
+ artifact->buildState = Artifact::Building;
+ return true;
+ }
+ }
+
+ foreach (Artifact *output, artifact->transformer->outputs) {
+ // create the output directories
+ QDir outDir = QFileInfo(output->fileName).absoluteDir();
+ if (!outDir.exists())
+ outDir.mkpath(".");
+ }
+
+ ExecutorJob *job = m_availableJobs.takeFirst();
+ artifact->buildState = Artifact::Building;
+ m_processingJobs.insert(job, artifact);
+
+ if (!artifact->product)
+ qbsError() << QString("BUG: Generated artifact %1 belongs to no product.").arg(QDir::toNativeSeparators(artifact->fileName));
+
+ job->run(artifact->transformer.data(), artifact->product);
+ }
+
+ return true;
+}
+
+void Executor::finishArtifact(Artifact *leaf)
+{
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[EXEC] finishArtifact " << fileName(leaf);
+
+ leaf->buildState = Artifact::Built;
+ foreach (Artifact *parent, leaf->parents) {
+ if (parent->buildState != Artifact::Buildable)
+ continue;
+
+ if (isLeaf(parent)) {
+ m_leaves.insert(parent, hashDummy);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[EXEC] finishArtifact adds leaf " << fileName(parent) << " " << toString(parent->buildState);
+ }
+ }
+
+ foreach (Artifact *sideBySideArtifact, leaf->sideBySideArtifacts)
+ if (leaf->transformer == sideBySideArtifact->transformer && sideBySideArtifact->buildState == Artifact::Building)
+ finishArtifact(sideBySideArtifact);
+}
+
+void Executor::handleDependencies(Artifact *processedArtifact, Artifact *scannedArtifact, const QSet<QString> &resolvedDependencies)
+{
+ qbsTrace() << "[DEPSCAN] dependencies found for '" << fileName(scannedArtifact)
+ << "' while processing '" << fileName(processedArtifact) << "'";
+
+ foreach (const QString &dependencyFilePath, resolvedDependencies)
+ handleDependency(processedArtifact, dependencyFilePath);
+}
+
+void Executor::handleDependency(Artifact *processedArtifact, const QString &dependencyFilePath)
+{
+ BuildProduct *product = processedArtifact->product;
+ Artifact *dependency = 0;
+ bool insertIntoProduct = true;
+ Q_ASSERT(processedArtifact->artifactType == Artifact::Generated);
+ Q_CHECK_PTR(processedArtifact->product);
+ if ((dependency = product->artifacts.value(dependencyFilePath))) {
+ qbsTrace("[DEPSCAN] ok in product '%s'", qPrintable(dependencyFilePath));
+ insertIntoProduct = false;
+ } else if ((dependency = processedArtifact->project->dependencyArtifacts().value(dependencyFilePath))) {
+ qbsTrace("[DEPSCAN] ok in deps '%s'", qPrintable(dependencyFilePath));
+ } else {
+ // try to find the dependency in other products of this project
+ foreach (BuildProduct::Ptr otherProduct, product->project->buildProducts()) {
+ if (otherProduct == product)
+ continue;
+ if ((dependency = otherProduct->artifacts.value(dependencyFilePath))) {
+ insertIntoProduct = false;
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[DEPSCAN] found in product '%s': '%s'", qPrintable(otherProduct->rProduct->name), qPrintable(dependencyFilePath));
+ break;
+ }
+ }
+ }
+
+ // dependency not found in the whole build graph, thus create a new artifact
+ if (!dependency) {
+ qbsTrace("[DEPSCAN] + '%s'", qPrintable(dependencyFilePath));
+ dependency = new Artifact(processedArtifact->project);
+ dependency->artifactType = Artifact::FileDependency;
+ dependency->configuration = processedArtifact->configuration;
+ dependency->fileName = dependencyFilePath;
+ processedArtifact->project->dependencyArtifacts().insert(dependencyFilePath, dependency);
+ }
+
+ if (processedArtifact == dependency)
+ return;
+
+ if (dependency->artifactType == Artifact::FileDependency) {
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[DEPSCAN] new file dependency " << fileName(dependency);
+ processedArtifact->fileDependencies.insert(dependency);
+ } else {
+ if (processedArtifact->children.contains(dependency))
+ return;
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[DEPSCAN] new artifact dependency " << fileName(dependency);
+ BuildGraph *buildGraph = product->project->buildGraph();
+ if (insertIntoProduct && !product->artifacts.contains(dependency->fileName))
+ buildGraph->insert(product, dependency);
+ buildGraph->safeConnect(processedArtifact, dependency);
+ }
+}
+
+void Executor::cancelJobs()
+{
+ QList<ExecutorJob *> jobs = m_processingJobs.keys();
+ foreach (ExecutorJob *job, jobs)
+ job->cancel();
+ foreach (ExecutorJob *job, jobs)
+ job->waitForFinished();
+}
+
+void Executor::addExecutorJobs(int jobNumber)
+{
+ if (jobNumber < 1)
+ qbsError() << tr("Maximum job number must be larger than zero.");
+ for (int i = 1; i <= jobNumber; i++) {
+ ExecutorJob *job = new ExecutorJob(this);
+ job->setObjectName(QString(QLatin1String("J%1")).arg(i));
+ m_availableJobs.append(job);
+ connect(job, SIGNAL(error(QString)),
+ this, SLOT(onProcessError(QString)));
+ connect(job, SIGNAL(success()),
+ this, SLOT(onProcessSuccess()));
+ }
+}
+
+void Executor::removeExecutorJobs(int jobNumber)
+{
+ if (jobNumber >= m_availableJobs.count()) {
+ qDeleteAll(m_availableJobs);
+ m_availableJobs.clear();
+ } else {
+ for (int i = 1; i <= jobNumber; i++) {
+ delete m_availableJobs.takeLast();
+ }
+ }
+}
+
+bool Executor::runAutoMoc()
+{
+ bool autoMocApplied = false;
+ foreach (const BuildProduct::Ptr &product, m_productsToBuild) {
+ // HACK call the automoc thingy here only if we have use qt/core module
+ foreach (ResolvedModule::Ptr m, product->rProduct->modules) {
+ if (m->name == "qt/core") {
+ try {
+ autoMocApplied = true;
+ m_autoMoc->apply(product);
+ } catch (Error &e) {
+ setError(e.toString());
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ if (autoMocApplied)
+ foreach (BuildProject::Ptr prj, m_projectsToBuild)
+ BuildGraph::detectCycle(prj.data());
+
+ return true;
+}
+
+static bool scanWithScannerPlugin(ScannerPlugin *scannerPlugin,
+ const QString &filePathToBeScanned,
+ ScanResultCache *scanResultCache,
+ ScanResultCache::Result *scanResult)
+{
+ void *scannerHandle = scannerPlugin->open(filePathToBeScanned.utf16(), 0, 0);
+ if (!scannerHandle)
+ return false;
+ while (true) {
+ int flags = 0;
+ int length = 0;
+ const char *szOutFilePath = scannerPlugin->next(scannerHandle, &length, &flags);
+ if (szOutFilePath == 0)
+ break;
+ QString outFilePath = QString::fromLocal8Bit(szOutFilePath, length);
+ if (outFilePath.isEmpty())
+ continue;
+ bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG);
+ scanResult->deps.insert(outFilePath, isLocalInclude);
+ }
+ scannerPlugin->close(scannerHandle);
+ scanResult->visited = true;
+ scanResultCache->insert(filePathToBeScanned, *scanResult);
+ return true;
+}
+
+static void collectIncludePaths(const QVariantMap &modules, QSet<QString> *collectedPaths)
+{
+ QMapIterator<QString, QVariant> iterator(modules);
+ while (iterator.hasNext()) {
+ iterator.next();
+ if (iterator.key() == "cpp") {
+ QVariant includePathsVariant = iterator .value().toMap().value("includePaths");
+ if (includePathsVariant.isValid())
+ collectedPaths->unite(QSet<QString>::fromList(includePathsVariant.toStringList()));
+ } else {
+ collectIncludePaths(iterator .value().toMap().value("modules").toMap(), collectedPaths);
+ }
+ }
+}
+
+static QStringList collectIncludePaths(const QVariantMap &modules)
+{
+ QSet<QString> collectedPaths;
+
+ collectIncludePaths(modules, &collectedPaths);
+ return QStringList(collectedPaths.toList());
+}
+
+void Executor::scanFileDependencies(Artifact *processedArtifact)
+{
+ if (!processedArtifact->transformer)
+ return;
+
+ foreach (Artifact *output, processedArtifact->transformer->outputs) {
+ // clear the file dependencies - they will be regenerated
+ output->fileDependencies.clear();
+ }
+
+ foreach (Artifact *inputArtifact, processedArtifact->transformer->inputs) {
+ QStringList includePaths;
+ foreach (const QString &fileTag, inputArtifact->fileTags) {
+ QList<ScannerPlugin *> scanners = ScannerPluginManager::scannersForFileTag(fileTag);
+ foreach (ScannerPlugin *scanner, scanners) {
+ if (includePaths.isEmpty())
+ includePaths = collectIncludePaths(inputArtifact->configuration->value().value("modules").toMap());
+
+ scanForFileDependencies(scanner, includePaths, processedArtifact, inputArtifact);
+ }
+ }
+ }
+}
+
+void Executor::scanForFileDependencies(ScannerPlugin *scannerPlugin, const QStringList &includePaths, Artifact *processedArtifact, Artifact *inputArtifact)
+{
+ qbsDebug("scanning %s [%s]", qPrintable(inputArtifact->fileName), scannerPlugin->fileTag);
+ qbsDebug(" from %s", qPrintable(processedArtifact->fileName));
+
+ QSet<QString> resolvedDependencies;
+ QSet<QString> visitedFilePaths;
+ QStringList filePathsToScan;
+ filePathsToScan.append(inputArtifact->fileName);
+
+ while (!filePathsToScan.isEmpty()) {
+ const QString filePathToBeScanned = filePathsToScan.takeFirst();
+ if (visitedFilePaths.contains(filePathToBeScanned))
+ continue;
+ visitedFilePaths.insert(filePathToBeScanned);
+
+ ScanResultCache::Result scanResult = m_scanResultCache.value(filePathToBeScanned);
+ if (!scanResult.visited) {
+ bool canScan = scanWithScannerPlugin(scannerPlugin, filePathToBeScanned, &m_scanResultCache, &scanResult);
+ if (!canScan)
+ continue;
+ }
+
+ resolveScanResultDependencies(includePaths, inputArtifact, scanResult, filePathToBeScanned, &resolvedDependencies, &filePathsToScan);
+ }
+
+ handleDependencies(processedArtifact, inputArtifact, resolvedDependencies);
+}
+
+static bool resolveWithIncludePath(const QString &includePath, QString &outFilePath, BuildProduct *buildProduct)
+{
+ QString filePath = FileInfo::resolvePath(includePath, outFilePath);
+ if (buildProduct->artifacts.contains(filePath)) {
+ outFilePath = filePath;
+ return true;
+ } else if (FileInfo::exists(filePath)) {
+ outFilePath = filePath;
+ return true;
+ }
+ return false;
+}
+
+void Executor::resolveScanResultDependencies(const QStringList &includePaths,
+ Artifact *inputArtifact,
+ const ScanResultCache::Result &scanResult,
+ const QString &filePathToBeScanned,
+ QSet<QString> *dependencies,
+ QStringList *filePathsToScan)
+{
+ QString baseDirOfInFilePath;
+ for (QHash<QString, bool>::const_iterator iterator = scanResult.deps.constBegin(); iterator != scanResult.deps.constEnd(); ++iterator) {
+ QString outFilePath = iterator.key();
+ const bool isLocalInclude = iterator.value();
+ bool resolved = FileInfo::isAbsolute(outFilePath);
+
+ if (!resolved && isLocalInclude) {
+ // try base directory of source file
+ if (baseDirOfInFilePath.isNull())
+ baseDirOfInFilePath = FileInfo::path(filePathToBeScanned);
+ resolved = resolveWithIncludePath(baseDirOfInFilePath, outFilePath, inputArtifact->product);
+ }
+
+ if (!resolved) {
+ // try include paths
+ foreach (const QString &includePath, includePaths) {
+ if (resolveWithIncludePath(includePath, outFilePath, inputArtifact->product)) {
+ resolved = true;
+ break;
+ }
+ }
+ }
+
+ if (resolved) {
+ outFilePath = QDir::cleanPath(outFilePath);
+ dependencies->insert(outFilePath);
+ filePathsToScan->append(outFilePath);
+ }
+ }
+}
+
+void Executor::onProcessError(QString errorString)
+{
+ if (m_keepGoing) {
+ qbsWarning() << tr("ignoring error: %1").arg(errorString);
+ onProcessSuccess();
+ } else {
+ setError(errorString);
+ cancelJobs();
+ finish();
+ }
+}
+
+void Executor::onProcessSuccess()
+{
+ ExecutorJob *job = qobject_cast<ExecutorJob *>(sender());
+ Q_ASSERT(job);
+ Artifact *processedArtifact = m_processingJobs.value(job);
+ Q_ASSERT(processedArtifact);
+ m_processingJobs.remove(job);
+ m_availableJobs.append(job);
+ finishArtifact(processedArtifact);
+
+ if (m_state == ExecutorRunning && !run(*m_futureInterface))
+ finish();
+}
+
+void Executor::finish()
+{
+ if (m_state == ExecutorIdle)
+ return;
+
+ QStringList unbuiltProductNames;
+ foreach (BuildProduct::Ptr buildProduct, m_productsToBuild) {
+ foreach (Artifact *artifact, buildProduct->targetArtifacts) {
+ if (artifact->buildState != Artifact::Built) {
+ unbuiltProductNames += buildProduct->rProduct->name;
+ break;
+ }
+ }
+ }
+ if (unbuiltProductNames.isEmpty()) {
+ qbsInfo() << DontPrintLogLevel << LogOutputStdOut << TextColorGreen
+ << "Build done.";
+ } else {
+ setError(tr("The following products could not be built: %1.").arg(unbuiltProductNames.join(", ")));
+ qbsInfo() << DontPrintLogLevel << LogOutputStdOut << TextColorRed
+ << "Build failed.";
+ return;
+ }
+
+ setState(ExecutorIdle);
+ emit finished();
+}
+
+/**
+ * Resets the state of all artifacts in the graph to "untouched".
+ * This must be done before doing another build.
+ */
+void Executor::resetArtifactsToUntouched()
+{
+ foreach (const BuildProduct::Ptr &product, m_productsToBuild) {
+ foreach (Artifact *artifact, product->artifacts) {
+ artifact->buildState = Artifact::Untouched;
+ artifact->isExistingFile = false;
+ artifact->isOutOfDate = false;
+ }
+ }
+}
+
+void Executor::prepareBuildGraph(Artifact::BuildState buildState)
+{
+ foreach (Artifact *root, m_roots)
+ prepareBuildGraph_impl(root, buildState);
+}
+
+void Executor::prepareBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState)
+{
+ if (artifact->buildState != Artifact::Untouched)
+ return;
+
+ artifact->buildState = buildState;
+
+ foreach (Artifact *child, artifact->children)
+ prepareBuildGraph_impl(child, buildState);
+}
+
+void Executor::updateBuildGraph(Artifact::BuildState buildState)
+{
+ QSet<Artifact *> seenArtifacts;
+ foreach (Artifact *root, m_roots)
+ updateBuildGraph_impl(root, buildState, seenArtifacts);
+}
+
+void Executor::updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts)
+{
+ if (seenArtifacts.contains(artifact))
+ return;
+
+ seenArtifacts += artifact;
+ artifact->buildState = buildState;
+
+ foreach (Artifact *child, artifact->children)
+ updateBuildGraph_impl(child, buildState, seenArtifacts);
+}
+
+void Executor::doOutOfDateCheck()
+{
+ foreach (Artifact *root, m_roots)
+ doOutOfDateCheck(root);
+}
+
+void Executor::doOutOfDateCheck(Artifact *artifact)
+{
+ if (artifact->outOfDateCheckPerformed)
+ return;
+ bool fileExists;
+ if (isOutOfDate(artifact, fileExists))
+ artifact->isOutOfDate = true;
+ artifact->isExistingFile = fileExists;
+ artifact->outOfDateCheckPerformed = true;
+ foreach (Artifact *child, artifact->children)
+ doOutOfDateCheck(child);
+}
+
+void Executor::doDependencyScanTopDown()
+{
+ qbsInfo() << DontPrintLogLevel << "Scanning for file dependencies...";
+ QSet<Artifact *> seenArtifacts;
+ foreach (Artifact *root, m_roots)
+ doDependencyScan_impl(root, seenArtifacts);
+}
+
+void Executor::doDependencyScan_impl(Artifact *artifact, QSet<Artifact *> &seenArtifacts)
+{
+ if (!artifact->transformer || seenArtifacts.contains(artifact))
+ return;
+ seenArtifacts += artifact;
+ if (!artifact->outOfDateCheckPerformed)
+ doOutOfDateCheck(artifact);
+ if (artifact->isOutOfDate)
+ scanFileDependencies(artifact);
+ foreach (Artifact *child, artifact->children)
+ doDependencyScan_impl(child, seenArtifacts);
+}
+
+void Executor::setState(ExecutorState s)
+{
+ if (m_state == s)
+ return;
+ m_state = s;
+ emit stateChanged(s);
+}
+
+void Executor::setError(const QString &errorMessage)
+{
+ setState(ExecutorError);
+ qbsError() << errorMessage;
+ emit error();
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/executor.h b/src/lib/buildgraph/executor.h
new file mode 100644
index 000000000..45e86280b
--- /dev/null
+++ b/src/lib/buildgraph/executor.h
@@ -0,0 +1,147 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef BUILDGRAPHEXECUTOR_H
+#define BUILDGRAPHEXECUTOR_H
+
+#include "buildgraph.h"
+
+#include <buildgraph/artifact.h>
+#include <Qbs/processoutput.h>
+#include <tools/settings.h>
+#include <tools/scannerpluginmanager.h>
+#include <buildgraph/scanresultcache.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QVariant>
+
+namespace qbs {
+
+class AutoMoc;
+class ExecutorJob;
+class ScanResultCache;
+
+class Executor : public QObject
+{
+ Q_OBJECT
+public:
+ Executor(int maxJobs = 1);
+ ~Executor();
+
+ void build(const QList<BuildProject::Ptr> projectsToBuild, const QStringList &changedFiles, const QStringList &selectedProductNames, QFutureInterface<bool> &futureInterface);
+
+ enum ExecutorState {
+ ExecutorIdle,
+ ExecutorRunning,
+ ExecutorError
+ };
+
+ void setRunOnceAndForgetModeEnabled(bool enabled) { m_runOnceAndForgetMode = enabled; }
+ void setDryRun(bool b);
+ void setKeepGoing(bool b) { m_keepGoing = b; }
+ bool isKeepGoingSet() const { return m_keepGoing; }
+ ExecutorState state() const { return m_state; }
+
+ void setMaximumJobs(int numberOfJobs);
+ int maximumJobs() const;
+
+signals:
+ void error();
+ void finished();
+ void stateChanged(ExecutorState);
+ void progress(int jobsToDo, int jobCount, const QString &description);
+ void newProcessOutput(const Qbs::ProcessOutput &processOutput);
+
+protected slots:
+ void onProcessError(QString errorString);
+ void onProcessSuccess();
+ void resetArtifactsToUntouched();
+
+protected:
+ void prepareBuildGraph(Artifact::BuildState buildState);
+ void prepareBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState);
+ void updateBuildGraph(Artifact::BuildState buildState);
+ void updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts);
+ void doOutOfDateCheck();
+ void doOutOfDateCheck(Artifact *root);
+ void doDependencyScanTopDown();
+ void doDependencyScan_impl(Artifact *artifact, QSet<Artifact *> &seenArtifacts);
+ static bool isLeaf(Artifact *artifact);
+ void initLeaves(const QList<Artifact *> &changedArtifacts);
+ void initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts);
+ bool run(QFutureInterface<bool> &futureInterface);
+ qbs::FileTime timeStamp(Artifact *artifact);
+ bool isOutOfDate(Artifact *artifact, bool &fileExists);
+ bool execute(Artifact *artifact);
+ void finishArtifact(Artifact *artifact);
+ void finish();
+ void setState(ExecutorState);
+ void setError(const QString &errorMessage);
+ void addExecutorJobs(int jobNumber);
+ void removeExecutorJobs(int jobNumber);
+ bool runAutoMoc();
+ void scanFileDependencies(Artifact *processedArtifact);
+ void scanForFileDependencies(ScannerPlugin *scannerPlugin, const QStringList &includePaths, Artifact *processedArtifact, Artifact *inputArtifact);
+ static void resolveScanResultDependencies(const QStringList &includePaths, Artifact *inputArtifact, const ScanResultCache::Result &scanResult,
+ const QString &filePathToBeScanned, QSet<QString> *dependencies, QStringList *filePathsToScan);
+ void handleDependencies(Artifact *processedArtifact, Artifact *scannedArtifact, const QSet<QString> &resolvedDependencies);
+ void handleDependency(Artifact *processedArtifact, const QString &dependencyFilePath);
+ void cancelJobs();
+
+private:
+ QScriptEngine *m_scriptEngine;
+ bool m_runOnceAndForgetMode; // This is true for the command line version.
+ QList<ExecutorJob*> m_availableJobs;
+ QHash<ExecutorJob*, Artifact *> m_processingJobs;
+ ExecutorState m_state;
+ bool m_keepGoing;
+ QList<BuildProject::Ptr> m_projectsToBuild;
+ QList<BuildProduct::Ptr> m_productsToBuild;
+ QList<Artifact *> m_roots;
+ QMap<Artifact *, QHashDummyValue> m_leaves;
+ QHash<Artifact *, qbs::FileTime> m_timeStampCache;
+ ScanResultCache m_scanResultCache;
+ AutoMoc *m_autoMoc;
+
+ friend class ExecutorJob;
+ int m_maximumJobNumber;
+ QFutureInterface<bool> *m_futureInterface; // TODO: its a hack
+};
+
+} // namespace qbs
+
+#endif // BUILDGRAPHEXECUTOR_H
diff --git a/src/lib/buildgraph/executorjob.cpp b/src/lib/buildgraph/executorjob.cpp
new file mode 100644
index 000000000..b7a16828a
--- /dev/null
+++ b/src/lib/buildgraph/executorjob.cpp
@@ -0,0 +1,128 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "executorjob.h"
+#include "executor.h"
+#include "commandexecutor.h"
+#include <buildgraph/transformer.h>
+#include <QtCore/QThread>
+
+namespace qbs {
+
+ExecutorJob::ExecutorJob(Executor *parent)
+ : QObject(parent)
+ , m_executor(parent)
+ , m_commandExecutor(new CommandExecutor(this))
+ , m_transformer(0)
+ , m_lastUsedProduct(0)
+ , m_currentCommandIdx(-1)
+{
+ connect(m_commandExecutor, SIGNAL(error(QString)), SLOT(onCommandError(QString)));
+ connect(m_commandExecutor, SIGNAL(finished()), SLOT(onCommandFinished()));
+}
+
+ExecutorJob::~ExecutorJob()
+{
+}
+
+void ExecutorJob::setMainThreadScriptEngine(QScriptEngine *engine)
+{
+ m_commandExecutor->setMainThreadScriptEngine(engine);
+}
+
+void ExecutorJob::setDryRun(bool enabled)
+{
+ m_commandExecutor->setDryRunEnabled(enabled);
+}
+
+void ExecutorJob::run(Transformer *t, BuildProduct *buildProduct)
+{
+ Q_ASSERT(m_currentCommandIdx == -1);
+
+ if (t->commands.isEmpty()) {
+ emit success();
+ return;
+ }
+
+ if (m_lastUsedProduct != buildProduct)
+ m_commandExecutor->setProcessEnvironment(buildProduct->rProduct->buildEnvironment);
+
+ m_transformer = t;
+ runNextCommand();
+}
+
+void ExecutorJob::cancel()
+{
+ if (!m_transformer)
+ return;
+ m_currentCommandIdx = m_transformer->commands.count();
+}
+
+void ExecutorJob::waitForFinished()
+{
+ m_commandExecutor->waitForFinished();
+}
+
+void ExecutorJob::runNextCommand()
+{
+ ++m_currentCommandIdx;
+ if (m_currentCommandIdx >= m_transformer->commands.count()) {
+ m_transformer = 0;
+ m_currentCommandIdx = -1;
+ emit success();
+ return;
+ }
+
+ AbstractCommand *command = m_transformer->commands.at(m_currentCommandIdx);
+ m_commandExecutor->start(m_transformer, command);
+}
+
+void ExecutorJob::onCommandError(QString errorString)
+{
+ m_transformer = 0;
+ m_currentCommandIdx = -1;
+ emit error(errorString);
+}
+
+void ExecutorJob::onCommandFinished()
+{
+ if (!m_transformer)
+ return;
+ runNextCommand();
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/executorjob.h b/src/lib/buildgraph/executorjob.h
new file mode 100644
index 000000000..db3073a9c
--- /dev/null
+++ b/src/lib/buildgraph/executorjob.h
@@ -0,0 +1,86 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef EXECUTORJOB_H
+#define EXECUTORJOB_H
+
+#include <QtCore/QObject>
+
+QT_BEGIN_NAMESPACE
+class QScriptEngine;
+QT_END_NAMESPACE
+
+namespace qbs {
+
+class CommandExecutor;
+class BuildProduct;
+class Executor;
+class Transformer;
+
+class ExecutorJob : public QObject
+{
+ Q_OBJECT
+public:
+ ExecutorJob(Executor *parent);
+ ~ExecutorJob();
+
+ void setMainThreadScriptEngine(QScriptEngine *engine);
+ void setDryRun(bool enabled);
+ void run(Transformer *t, BuildProduct *buildProduct);
+ void cancel();
+ void waitForFinished();
+
+protected slots:
+ void runNextCommand();
+ void onCommandError(QString errorString);
+ void onCommandFinished();
+
+signals:
+ void error(QString errorString);
+ void success();
+
+private:
+ Executor *m_executor;
+ CommandExecutor *m_commandExecutor;
+ Transformer *m_transformer;
+ BuildProduct *m_lastUsedProduct;
+ int m_currentCommandIdx;
+};
+
+} // namespace qbs
+
+#endif // EXECUTORJOB_H
diff --git a/src/lib/buildgraph/rulegraph.cpp b/src/lib/buildgraph/rulegraph.cpp
new file mode 100644
index 000000000..11d7712e8
--- /dev/null
+++ b/src/lib/buildgraph/rulegraph.cpp
@@ -0,0 +1,201 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "rulegraph.h"
+#include <tools/error.h>
+#include <QtCore/QDebug>
+
+namespace qbs {
+
+RuleGraph::RuleGraph()
+{
+}
+
+void RuleGraph::build(const QSet<Rule::Ptr> &rules, const QStringList &productFileTags)
+{
+ QMap<QString, QList<Rule*> > inputFileTagToRule;
+ m_artifacts.reserve(rules.count());
+ foreach (const Rule::Ptr &rule, rules) {
+ foreach (const QString &fileTag, rule->outputFileTags())
+ m_outputFileTagToRule[fileTag].append(rule.data());
+ insert(rule);
+ }
+
+ m_parents.resize(rules.count());
+ m_children.resize(rules.count());
+
+ foreach (Rule::Ptr rule, m_artifacts) {
+ QStringList inFileTags = rule->inputs;
+ inFileTags += rule->explicitlyDependsOn;
+ foreach (const QString &fileTag, inFileTags) {
+ inputFileTagToRule[fileTag].append(rule.data());
+ foreach (Rule *consumingRule, m_outputFileTagToRule.value(fileTag)) {
+ connect(rule.data(), consumingRule);
+ }
+ }
+ }
+
+ QList<Rule*> productRules;
+ for (int i=0; i < productFileTags.count(); ++i) {
+ QList<Rule*> rules = m_outputFileTagToRule.value(productFileTags.at(i));
+ productRules += rules;
+ //### check: the rule graph must be a in valid shape!
+ }
+ foreach (Rule *r, productRules)
+ m_rootRules += r->ruleGraphId;
+}
+
+QList<Rule::Ptr> RuleGraph::topSorted()
+{
+ QSet<int> rootRules = m_rootRules;
+ QList<Rule::Ptr> result;
+ foreach (int rootIndex, rootRules) {
+ Rule::Ptr rule = m_artifacts.at(rootIndex);
+ result.append(topSort(rule));
+ }
+
+ // remove duplicates from the result of our post-order traversal
+ QSet<Rule*> seenRules;
+ seenRules.reserve(result.count());
+ for (int i = 0; i < result.count();) {
+ Rule *rule = result.at(i).data();
+ if (seenRules.contains(rule))
+ result.removeAt(i);
+ else
+ ++i;
+ seenRules.insert(rule);
+ }
+
+ return result;
+}
+
+void RuleGraph::dump() const
+{
+ QByteArray indent;
+ printf("---rule graph dump:\n");
+ QSet<int> rootRules;
+ foreach (Rule::Ptr rule, m_artifacts)
+ if (m_parents[rule->ruleGraphId].isEmpty())
+ rootRules += rule->ruleGraphId;
+ foreach (int idx, rootRules) {
+ dump_impl(indent, idx);
+ }
+}
+
+void RuleGraph::dump_impl(QByteArray &indent, int rootIndex) const
+{
+ Rule::Ptr r = m_artifacts[rootIndex];
+ printf("%s", indent.constData());
+ printf("%s", qPrintable(r->toString()));
+ printf("\n");
+
+ indent.append(" ");
+ foreach (int childIndex, m_children[rootIndex])
+ dump_impl(indent, childIndex);
+ indent.chop(2);
+}
+
+int RuleGraph::insert(Rule::Ptr rule)
+{
+ rule->ruleGraphId = m_artifacts.count();
+ m_artifacts.append(rule);
+ return rule->ruleGraphId;
+}
+
+void RuleGraph::connect(Rule *creatingRule, Rule *consumingRule)
+{
+ int maxIndex = qMax(creatingRule->ruleGraphId, consumingRule->ruleGraphId);
+ if (m_parents.count() <= maxIndex) {
+ const int c = maxIndex + 1;
+ m_parents.resize(c);
+ m_children.resize(c);
+ }
+ m_parents[consumingRule->ruleGraphId].append(creatingRule->ruleGraphId);
+ m_children[creatingRule->ruleGraphId].append(consumingRule->ruleGraphId);
+}
+
+void RuleGraph::remove(Rule *rule)
+{
+ m_parents[rule->ruleGraphId].clear();
+ m_children[rule->ruleGraphId].clear();
+ m_artifacts[rule->ruleGraphId] = Rule::Ptr();
+ rule->ruleGraphId = -1;
+}
+
+void RuleGraph::removeParents(Rule *rule)
+{
+ foreach (int parentIndex, m_parents[rule->ruleGraphId]) {
+ Rule::Ptr parent = m_artifacts.at(parentIndex);
+ removeParents(parent.data());
+ remove(parent.data());
+ }
+ m_parents[rule->ruleGraphId].clear();
+}
+
+void RuleGraph::removeSiblings(Rule *rule)
+{
+ foreach (int childIndex, m_children[rule->ruleGraphId]) {
+ Rule::Ptr child = m_artifacts.at(childIndex);
+ QList<int> toRemove;
+ foreach (int siblingIndex, m_parents.at(child->ruleGraphId)) {
+ Rule::Ptr sibling = m_artifacts.at(siblingIndex);
+ if (sibling == rule)
+ continue;
+ toRemove.append(sibling->ruleGraphId);
+ remove(sibling.data());
+ }
+ QVector<int> &parents = m_parents[child->ruleGraphId];
+ qSort(parents);
+ foreach (int id, toRemove) {
+ QVector<int>::iterator it = qBinaryFind(parents.begin(), parents.end(), id);
+ if (it != parents.end())
+ parents.erase(it);
+ }
+ }
+}
+
+QList<Rule::Ptr> RuleGraph::topSort(Rule::Ptr rule)
+{
+ QList<Rule::Ptr> result;
+ foreach (int childIndex, m_children.at(rule->ruleGraphId))
+ result.append(topSort(m_artifacts.at(childIndex)));
+
+ result.append(rule);
+ return result;
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/rulegraph.h b/src/lib/buildgraph/rulegraph.h
new file mode 100644
index 000000000..d5a88b940
--- /dev/null
+++ b/src/lib/buildgraph/rulegraph.h
@@ -0,0 +1,77 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef RULEGRAPH_H
+#define RULEGRAPH_H
+
+#include <language.h>
+
+#include <QtCore/QSet>
+#include <QtCore/QVector>
+
+namespace qbs {
+
+class RuleGraph
+{
+public:
+ RuleGraph();
+
+ void build(const QSet<qbs::Rule::Ptr> &rules, const QStringList &productFileTag);
+ QList<qbs::Rule::Ptr> topSorted();
+
+ void dump() const;
+
+private:
+ void dump_impl(QByteArray &indent, int rootIndex) const;
+ int insert(qbs::Rule::Ptr rule);
+ void connect(qbs::Rule *creatingRule, qbs::Rule *consumingRule);
+ void remove(qbs::Rule *rule);
+ void removeParents(qbs::Rule *rule);
+ void removeSiblings(qbs::Rule *rule);
+ QList<qbs::Rule::Ptr> topSort(qbs::Rule::Ptr rule);
+
+private:
+ QMap<QString, QList<qbs::Rule*> > m_outputFileTagToRule;
+ QVector<qbs::Rule::Ptr> m_artifacts;
+ QVector< QVector<int> > m_parents;
+ QVector< QVector<int> > m_children;
+ QSet<int> m_rootRules;
+};
+
+} // namespace qbs
+
+#endif // RULEGRAPH_H
diff --git a/src/lib/buildgraph/scanresultcache.cpp b/src/lib/buildgraph/scanresultcache.cpp
new file mode 100644
index 000000000..70d683337
--- /dev/null
+++ b/src/lib/buildgraph/scanresultcache.cpp
@@ -0,0 +1,56 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#include "scanresultcache.h"
+
+namespace qbs {
+
+ScanResultCache::Result ScanResultCache::value(const QString &fileName) const
+{
+ m_mutex.lock();
+ Result result = m_data.value(fileName);
+ m_mutex.unlock();
+ return result;
+}
+
+void ScanResultCache::insert(const QString &fileName, const ScanResultCache::Result &value)
+{
+ m_mutex.lock();
+ m_data.insert(fileName, value);
+ m_mutex.unlock();
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/scanresultcache.h b/src/lib/buildgraph/scanresultcache.h
new file mode 100644
index 000000000..23e7d811f
--- /dev/null
+++ b/src/lib/buildgraph/scanresultcache.h
@@ -0,0 +1,71 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+#ifndef SCANRESULTCACHE_H
+#define SCANRESULTCACHE_H
+
+#include <QtCore/QHash>
+#include <QtCore/QMutex>
+#include <QtCore/QSet>
+#include <QtCore/QString>
+
+namespace qbs {
+
+class ScanResultCache
+{
+public:
+
+ struct Result
+ {
+ Result()
+ : visited(false)
+ {}
+
+ QHash<QString, bool> deps;
+ bool visited;
+ };
+
+ Result value(const QString &fileName) const;
+ void insert(const QString &fileName, const Result &value);
+
+private:
+ mutable QMutex m_mutex;
+ QHash<QString, Result> m_data;
+};
+
+} // namespace qbs
+
+#endif // SCANRESULTCACHE_H
diff --git a/src/lib/buildgraph/transformer.cpp b/src/lib/buildgraph/transformer.cpp
new file mode 100644
index 000000000..e9327849a
--- /dev/null
+++ b/src/lib/buildgraph/transformer.cpp
@@ -0,0 +1,119 @@
+/*************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).*
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+** GNU Lesser General Public License Usage
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "transformer.h"
+#include "artifact.h"
+#include "command.h"
+
+namespace qbs {
+
+Transformer::Transformer()
+{
+}
+
+Transformer::~Transformer()
+{
+ qDeleteAll(commands);
+}
+
+QScriptValue Transformer::translateFileConfig(QScriptEngine *scriptEngine, Artifact *artifact, const QString &defaultModuleName)
+{
+ QScriptValue config = artifact->configuration->cachedScriptValue(scriptEngine);
+ if (!config.isValid()) {
+ config = scriptEngine->toScriptValue(artifact->configuration->value());
+ artifact->configuration->cacheScriptValue(scriptEngine, config);
+ }
+
+ QScriptValue artifactConfig = scriptEngine->newObject();
+ artifactConfig.setPrototype(config);
+ artifactConfig.setProperty(QLatin1String("fileName"), artifact->fileName);
+ QStringList fileTags = artifact->fileTags.toList();
+ artifactConfig.setProperty(QLatin1String("fileTags"), scriptEngine->toScriptValue(fileTags));
+ if (!defaultModuleName.isEmpty())
+ artifactConfig.setProperty(QLatin1String("module"), config.property("modules").property(defaultModuleName));
+ return artifactConfig;
+}
+
+QScriptValue Transformer::translateInOutputs(QScriptEngine *scriptEngine, const QSet<Artifact*> &artifacts, const QString &defaultModuleName)
+{
+ typedef QMap<QString, QList<Artifact*> > TagArtifactsMap;
+ TagArtifactsMap tagArtifactsMap;
+ foreach (Artifact *artifact, artifacts)
+ foreach (const QString &fileTag, artifact->fileTags)
+ tagArtifactsMap[fileTag].append(artifact);
+
+ QScriptValue jsTagFiles = scriptEngine->newObject();
+ for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) {
+ const QList<Artifact*> &artifactList = tag.value();
+ QScriptValue jsFileConfig = scriptEngine->newArray(artifactList.count());
+ int i=0;
+ foreach (Artifact *artifact, artifactList) {
+ jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact, defaultModuleName));
+ }
+ jsTagFiles.setProperty(tag.key(), jsFileConfig);
+ }
+
+ return jsTagFiles;
+}
+
+void Transformer::setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue)
+{
+ const QString &defaultModuleName = rule->module->name;
+ QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName);
+ targetScriptValue.setProperty("inputs", scriptValue, QScriptValue::ReadOnly);
+ if (inputs.count() == 1) {
+ Artifact *input = *inputs.begin();
+ const QSet<QString> &fileTags = input->fileTags;
+ QScriptValue inputsForFileTag = scriptValue.property(*fileTags.begin());
+ QScriptValue inputScriptValue = inputsForFileTag.property(0);
+ targetScriptValue.setProperty("input", inputScriptValue, QScriptValue::ReadOnly);
+ }
+}
+
+void Transformer::setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue)
+{
+ const QString &defaultModuleName = rule->module->name;
+ QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName);
+ targetScriptValue.setProperty("outputs", scriptValue, QScriptValue::ReadOnly);
+ if (outputs.count() == 1) {
+ Artifact *output = *outputs.begin();
+ const QSet<QString> &fileTags = output->fileTags;
+ QScriptValue outputsForFileTag = scriptValue.property(*fileTags.begin());
+ QScriptValue outputScriptValue = outputsForFileTag.property(0);
+ targetScriptValue.setProperty("output", outputScriptValue, QScriptValue::ReadOnly);
+ }
+}
+
+} // namespace qbs
diff --git a/src/lib/buildgraph/transformer.h b/src/lib/buildgraph/transformer.h
new file mode 100644
index 000000000..d52d9dd3b
--- /dev/null
+++ b/src/lib/buildgraph/transformer.h
@@ -0,0 +1,85 @@
+/*************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).*
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+** GNU Lesser General Public License Usage
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#ifndef TRANSFORMER_H
+#define TRANSFORMER_H
+
+#include "tools/persistence.h"
+
+#include <QtCore/QSet>
+#include <QtCore/QSharedPointer>
+#include <QtScript/QScriptEngine>
+
+QT_BEGIN_NAMESPACE
+
+QT_END_NAMESPACE
+
+namespace qbs {
+
+class Artifact;
+class AbstractCommand;
+class Rule;
+
+class Transformer : public PersistentObject
+{
+public:
+ typedef QSharedPointer<Transformer> Ptr;
+
+ Transformer();
+ ~Transformer();
+
+ QSet<Artifact*> inputs; // can be different from "children of all outputs"
+ QSet<Artifact*> outputs;
+ QSharedPointer<Rule> rule;
+ QList<AbstractCommand *> commands;
+
+ static QScriptValue translateFileConfig(QScriptEngine *scriptEngine,
+ Artifact *artifact,
+ const QString &defaultModuleName);
+ static QScriptValue translateInOutputs(QScriptEngine *scriptEngine,
+ const QSet<Artifact*> &artifacts,
+ const QString &defaultModuleName);
+
+ void setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue);
+ void setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue);
+
+private:
+ void load(PersistentPool &pool, PersistentObjectData &data);
+ void store(PersistentPool &pool, PersistentObjectData &data) const;
+};
+
+} // namespace qbs
+
+#endif // TRANSFORMER_H