diff options
Diffstat (limited to 'src/lib/buildgraph')
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 *> ¤tBranch) +{ + 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 *> ¤tBranch); + +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 |