aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/buildgraph/buildgraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/buildgraph/buildgraph.cpp')
-rw-r--r--src/lib/buildgraph/buildgraph.cpp1384
1 files changed, 1384 insertions, 0 deletions
diff --git a/src/lib/buildgraph/buildgraph.cpp b/src/lib/buildgraph/buildgraph.cpp
new file mode 100644
index 000000000..b9225881d
--- /dev/null
+++ b/src/lib/buildgraph/buildgraph.cpp
@@ -0,0 +1,1384 @@
+/**************************************************************************
+**
+** This file is part of the Qt Build Suite
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file.
+** Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**************************************************************************/
+
+#include "buildgraph.h"
+#include "artifact.h"
+#include "command.h"
+#include "rulegraph.h"
+#include "transformer.h"
+
+#include <language/loader.h>
+#include <tools/fileinfo.h>
+#include <tools/persistence.h>
+#include <tools/scannerpluginmanager.h>
+#include <tools/logger.h>
+#include <tools/scripttools.h>
+
+#include <QFileInfo>
+#include <QDebug>
+#include <QDir>
+#include <QtCore/QDirIterator>
+#include <QDataStream>
+#include <QtCore/QElapsedTimer>
+#include <QtCore/QMutex>
+#include <QtScript/QScriptProgram>
+#include <QtScript/QScriptValueIterator>
+
+namespace qbs {
+
+BuildProduct::BuildProduct()
+ : project(0)
+{
+}
+
+BuildProduct::~BuildProduct()
+{
+ qDeleteAll(artifacts);
+}
+
+const QList<Rule::Ptr> &BuildProduct::topSortedRules() const
+{
+ if (m_topSortedRules.isEmpty()) {
+ RuleGraph ruleGraph;
+ ruleGraph.build(rProduct->rules, rProduct->fileTags);
+// ruleGraph.dump();
+ m_topSortedRules = ruleGraph.topSorted();
+// int i=0;
+// foreach (Rule::Ptr r, m_topSortedRules)
+// qDebug() << ++i << r->toString() << (void*)r.data();
+ }
+ return m_topSortedRules;
+}
+
+BuildGraph::BuildGraph()
+{
+ ProcessCommand::setupForJavaScript(&m_scriptEngine);
+ JavaScriptCommand::setupForJavaScript(&m_scriptEngine);
+}
+
+BuildGraph::~BuildGraph()
+{
+}
+
+static void internalDump(BuildProduct *product, Artifact *n, QByteArray indent)
+{
+ Artifact *artifactInProduct = product->artifacts.value(n->fileName);
+ if (artifactInProduct && artifactInProduct != n) {
+ fprintf(stderr,"\ntree corrupted. %p ('%s') resolves to %p ('%s')\n",
+ n, qPrintable(n->fileName), product->artifacts.value(n->fileName),
+ qPrintable(product->artifacts.value(n->fileName)->fileName));
+
+ abort();
+ }
+ printf("%s", indent.constData());
+ printf("Artifact (%p) ", n);
+ printf("%s%s %s [%s]",
+ qPrintable(QString(toString(n->buildState).at(0))),
+ artifactInProduct ? "" : " SBS", // SBS == side-by-side artifact from other product
+ qPrintable(n->fileName),
+ qPrintable(QStringList(n->fileTags.toList()).join(",")));
+ printf("\n");
+ indent.append(" ");
+ foreach (Artifact *child, n->children) {
+ internalDump(product, child, indent);
+ }
+}
+
+void BuildGraph::dump(BuildProduct::Ptr product) const
+{
+ Q_ASSERT(product->artifacts.uniqueKeys() == product->artifacts.keys());
+
+ foreach (Artifact *n, product->artifacts)
+ if (n->parents.isEmpty())
+ internalDump(product.data(), n, QByteArray());
+}
+
+void BuildGraph::insert(BuildProduct::Ptr product, Artifact *n) const
+{
+ insert(product.data(), n);
+}
+
+void BuildGraph::insert(BuildProduct *product, Artifact *n) const
+{
+ Q_ASSERT(n->product == 0);
+ Q_ASSERT(!n->fileName.isEmpty());
+ Q_ASSERT(!product->artifacts.contains(n->fileName));
+#ifdef QT_DEBUG
+ foreach (BuildProduct::Ptr otherProduct, product->project->buildProducts()) {
+ if (otherProduct->artifacts.contains(n->fileName)) {
+ if (n->artifactType == Artifact::Generated) {
+ QString pl;
+ pl.append(QString(" - %1 \n").arg(product->rProduct->name));
+ foreach (BuildProduct::Ptr p, product->project->buildProducts()) {
+ if (p->artifacts.contains(n->fileName)) {
+ pl.append(QString(" - %1 \n").arg(p->rProduct->name));
+ }
+ }
+ throw Error(QString ("BUG: already inserted in this project: %1\n%2"
+ )
+ .arg(n->fileName)
+ .arg(pl)
+ );
+ }
+ }
+ }
+#endif
+ product->artifacts.insert(n->fileName, n);
+ n->product = product;
+ product->project->markDirty();
+
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] insert artifact '%s'", qPrintable(n->fileName));
+}
+
+void BuildGraph::setupScriptEngineForProduct(QScriptEngine *scriptEngine, ResolvedProduct::Ptr product, Rule::Ptr rule, BuildGraph *bg)
+{
+ ResolvedProduct *lastSetupProduct = (ResolvedProduct *)scriptEngine->property("lastSetupProduct").toULongLong();
+
+ QScriptValue productScriptValue;
+ if (lastSetupProduct != product.data()) {
+ scriptEngine->setProperty("lastSetupProduct", QVariant((qulonglong)product.data()));
+ productScriptValue = scriptEngine->toScriptValue(product->configuration->value());
+ productScriptValue.setProperty("name", product->name);
+ QString destinationDirectory = product->destinationDirectory;
+ if (destinationDirectory.isEmpty())
+ destinationDirectory = ".";
+ productScriptValue.setProperty("destinationDirectory", destinationDirectory);
+ scriptEngine->globalObject().setProperty("product", productScriptValue, QScriptValue::ReadOnly);
+ } else {
+ productScriptValue = scriptEngine->globalObject().property("product");
+ }
+
+ // If the Rule is in a Module, set up the 'module' property
+ if (!rule->module->name.isEmpty())
+ productScriptValue.setProperty("module", productScriptValue.property("modules").property(rule->module->name));
+
+ if (rule) {
+ for (JsImports::const_iterator it = rule->jsImports.begin(); it != rule->jsImports.end(); ++it) {
+ foreach (const QString &fileName, it.value()) {
+ QScriptValue jsImportValue;
+ if (bg)
+ jsImportValue = bg->m_jsImportCache.value(fileName, scriptEngine->undefinedValue());
+ if (jsImportValue.isUndefined()) {
+// qDebug() << "CACHE MISS" << fileName;
+ QFile file(fileName);
+ if (!file.open(QFile::ReadOnly))
+ throw Error(QString("Cannot open '%1'.").arg(fileName));
+ const QString sourceCode = QTextStream(&file).readAll();
+ QScriptProgram program(sourceCode, fileName);
+ addJSImport(scriptEngine, program, jsImportValue);
+ addJSImport(scriptEngine, jsImportValue, it.key());
+ if (bg)
+ bg->m_jsImportCache.insert(fileName, jsImportValue);
+ } else {
+// qDebug() << "CACHE HIT" << fileName;
+ addJSImport(scriptEngine, jsImportValue, it.key());
+ }
+ }
+ }
+ } else {
+ // ### TODO remove the imports we added before
+ }
+}
+
+void BuildGraph::setupScriptEngineForArtifact(BuildProduct *product, Artifact *artifact)
+{
+ QString inFileName = FileInfo::fileName(artifact->fileName);
+ QString inBaseName = FileInfo::baseName(artifact->fileName);
+ QString inCompleteBaseName = FileInfo::completeBaseName(artifact->fileName);
+
+ QString basedir;
+ if (artifact->artifactType == Artifact::SourceFile) {
+ QDir sourceDir(product->rProduct->sourceDirectory);
+ basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->fileName));
+ } else {
+ QDir buildDir(product->project->buildGraph()->buildDirectoryRoot() + product->project->resolvedProject()->id);
+ basedir = FileInfo::path(buildDir.relativeFilePath(artifact->fileName));
+ }
+
+ QScriptValue modulesScriptValue = artifact->configuration->cachedScriptValue(&m_scriptEngine);
+ if (!modulesScriptValue.isValid()) {
+ modulesScriptValue = m_scriptEngine.toScriptValue(artifact->configuration->value());
+ artifact->configuration->cacheScriptValue(&m_scriptEngine, modulesScriptValue);
+ }
+ modulesScriptValue = modulesScriptValue.property("modules");
+
+ // expose per file properties we want to use in an Artifact within a Rule
+ QScriptValue scriptValue = m_scriptEngine.newObject();
+ scriptValue.setProperty("fileName", inFileName);
+ scriptValue.setProperty("baseName", inBaseName);
+ scriptValue.setProperty("completeBaseName", inCompleteBaseName);
+ scriptValue.setProperty("baseDir", basedir);
+ scriptValue.setProperty("modules", modulesScriptValue);
+
+ QScriptValue globalObj = m_scriptEngine.globalObject();
+ globalObj.setProperty("input", scriptValue);
+}
+
+void BuildGraph::applyRules(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag)
+{
+ foreach (Rule::Ptr rule, product->topSortedRules())
+ applyRule(product, artifactsPerFileTag, rule);
+}
+
+/*!
+ * Runs a cycle detection on the BG and throws an exception if there is one.
+ */
+void BuildGraph::detectCycle(BuildProject *project)
+{
+ QElapsedTimer *t = 0;
+ if (qbsLogLevel(LoggerTrace)) {
+ t = new QElapsedTimer;
+ qbsTrace() << "[BG] running cycle detection on project '" + project->resolvedProject()->id + "'";
+ }
+
+ foreach (BuildProduct::Ptr product, project->buildProducts())
+ foreach (Artifact *artifact, product->targetArtifacts)
+ detectCycle(artifact);
+
+ if (qbsLogLevel(LoggerTrace)) {
+ qint64 elapsed = t->elapsed();
+ qbsTrace() << "[BG] cycle detection for project '" + project->resolvedProject()->id + "' took " << elapsed << " ms";
+ delete t;
+ }
+}
+
+void BuildGraph::detectCycle(Artifact *a)
+{
+ QSet<Artifact *> done, currentBranch;
+ detectCycle(a, done, currentBranch);
+}
+
+void BuildGraph::detectCycle(Artifact *v, QSet<Artifact *> &done, QSet<Artifact *> &currentBranch)
+{
+ currentBranch += v;
+ for (ArtifactList::const_iterator it = v->children.begin(); it != v->children.end(); ++it) {
+ Artifact *u = *it;
+ if (currentBranch.contains(u))
+ throw Error("Cycle in build graph detected.");
+ if (!done.contains(u))
+ detectCycle(u, done, currentBranch);
+ }
+ currentBranch -= v;
+ done += v;
+}
+
+static AbstractCommand *createCommandFromScriptValue(const QScriptValue &scriptValue)
+{
+ if (scriptValue.isUndefined() || !scriptValue.isValid())
+ return 0;
+ AbstractCommand *cmdBase = 0;
+ QString className = scriptValue.property("className").toString();
+ if (className == "Command")
+ cmdBase = new ProcessCommand;
+ else if (className == "JavaScriptCommand")
+ cmdBase = new JavaScriptCommand;
+ if (cmdBase)
+ cmdBase->fillFromScriptValue(&scriptValue);
+ return cmdBase;
+}
+
+void BuildGraph::applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag,
+ Rule::Ptr rule)
+{
+ setupScriptEngineForProduct(&m_scriptEngine, product->rProduct, rule, this);
+
+ if (rule->isMultiplexRule()) {
+ // apply the rule once for a set of inputs
+
+ QSet<Artifact*> inputArtifacts;
+ foreach (const QString &fileTag, rule->inputs)
+ inputArtifacts.unite(artifactsPerFileTag.value(fileTag));
+
+ if (!inputArtifacts.isEmpty())
+ applyRule(product, artifactsPerFileTag, rule, inputArtifacts);
+ } else {
+ // apply the rule once for each input
+
+ QSet<Artifact*> inputArtifacts;
+ foreach (const QString &fileTag, rule->inputs) {
+ foreach (Artifact *inputArtifact, artifactsPerFileTag.value(fileTag)) {
+ inputArtifacts.insert(inputArtifact);
+ applyRule(product, artifactsPerFileTag, rule, inputArtifacts);
+ inputArtifacts.clear();
+ }
+ }
+ }
+}
+
+void BuildGraph::createOutputArtifact(
+ BuildProduct *product,
+ const Rule::Ptr &rule, const RuleArtifact::Ptr &ruleArtifact,
+ const QSet<Artifact *> &inputArtifacts,
+ QList< QPair<RuleArtifact*, Artifact *> > *ruleArtifactArtifactMap,
+ QList<Artifact *> *outputArtifacts,
+ QSharedPointer<Transformer> &transformer)
+{
+ QScriptValue scriptValue = m_scriptEngine.evaluate(ruleArtifact->fileScript);
+ if (scriptValue.isError() || m_scriptEngine.hasUncaughtException())
+ throw Error("Error in Rule.Artifact fileName: " + scriptValue.toString());
+ QString outputPath = scriptValue.toString();
+ outputPath.replace("..", "dotdot"); // don't let the output artifact "escape" its build dir
+ outputPath = resolveOutPath(outputPath, product);
+
+ Artifact *outputArtifact = product->artifacts.value(outputPath);
+ if (outputArtifact) {
+ if (outputArtifact->transformer && outputArtifact->transformer != transformer) {
+ // This can happen when applying rules after scanning for additional file tags.
+ // We just regenerate the transformer.
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] regenerating transformer for '%s'", qPrintable(fileName(outputArtifact)));
+ transformer = outputArtifact->transformer;
+ transformer->inputs += inputArtifacts;
+
+ if (transformer->inputs.count() > 1 && !rule->isMultiplexRule()) {
+ QString th = "[" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+ QString e = tr("Conflicting rules for producing %1 %2 \n").arg(outputArtifact->fileName, th);
+ th = "[" + rule->inputs.join(", ")
+ + "] -> [" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+
+ e += QString(" while trying to apply: %1:%2:%3 %4\n")
+ .arg(rule->script->location.fileName)
+ .arg(rule->script->location.line)
+ .arg(rule->script->location.column)
+ .arg(th);
+
+ e += QString(" was already defined in: %1:%2:%3 %4\n")
+ .arg(outputArtifact->transformer->rule->script->location.fileName)
+ .arg(outputArtifact->transformer->rule->script->location.line)
+ .arg(outputArtifact->transformer->rule->script->location.column)
+ .arg(th);
+ throw Error(e);
+ }
+ }
+ outputArtifact->fileTags += ruleArtifact->fileTags.toSet();
+ } else {
+ outputArtifact = new Artifact(product->project);
+ outputArtifact->artifactType = Artifact::Generated;
+ outputArtifact->fileName = outputPath;
+ outputArtifact->fileTags = ruleArtifact->fileTags.toSet();
+ insert(product, outputArtifact);
+ }
+
+ if (rule->isMultiplexRule())
+ outputArtifact->configuration = product->rProduct->configuration;
+ else
+ outputArtifact->configuration = (*inputArtifacts.constBegin())->configuration;
+
+ foreach (Artifact *inputArtifact, inputArtifacts) {
+ Q_ASSERT(outputArtifact != inputArtifact);
+ loggedConnect(outputArtifact, inputArtifact);
+ }
+ ruleArtifactArtifactMap->append(qMakePair(ruleArtifact.data(), outputArtifact));
+ outputArtifacts->append(outputArtifact);
+
+ // create transformer if not already done so
+ if (!transformer) {
+ transformer = QSharedPointer<Transformer>(new Transformer);
+ transformer->rule = rule;
+ transformer->inputs = inputArtifacts;
+ }
+ outputArtifact->transformer = transformer;
+}
+
+void BuildGraph::applyRule(BuildProduct *product, QMap<QString, QSet<Artifact *> > &artifactsPerFileTag, Rule::Ptr rule, const QSet<Artifact *> &inputArtifacts)
+{
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[BG] apply rule " << rule->toString() << " " << toStringList(inputArtifacts).join(",\n ");
+
+ QList< QPair<RuleArtifact*, Artifact *> > ruleArtifactArtifactMap;
+ QList<Artifact *> outputArtifacts;
+
+ QSet<Artifact *> usingArtifacts;
+ foreach (BuildProduct *dep, product->usings) {
+ foreach (Artifact *targetArtifact, dep->targetArtifacts) {
+ ArtifactList sbsArtifacts = targetArtifact->sideBySideArtifacts;
+ sbsArtifacts.insert(targetArtifact);
+ foreach (Artifact *artifact, sbsArtifacts) {
+ QString matchingTag;
+ foreach (const QString &tag, rule->usings) {
+ if (artifact->fileTags.contains(tag)) {
+ matchingTag = tag;
+ break;
+ }
+ }
+ if (matchingTag.isEmpty())
+ continue;
+ usingArtifacts.insert(artifact);
+ }
+ }
+ }
+
+ // create the output artifacts from the set of input artifacts
+ QSharedPointer<Transformer> transformer;
+ foreach (RuleArtifact::Ptr ruleArtifact, rule->artifacts) {
+ if (!rule->isMultiplexRule()) {
+ foreach (Artifact *inputArtifact, inputArtifacts) {
+ setupScriptEngineForArtifact(product, inputArtifact);
+ QSet<Artifact *> oneInputArtifact;
+ oneInputArtifact.insert(inputArtifact);
+ createOutputArtifact(product, rule, ruleArtifact, oneInputArtifact,
+ &ruleArtifactArtifactMap, &outputArtifacts, transformer);
+ }
+ } else {
+ createOutputArtifact(product, rule, ruleArtifact, inputArtifacts,
+ &ruleArtifactArtifactMap, &outputArtifacts, transformer);
+ }
+ }
+
+ foreach (Artifact *outputArtifact, outputArtifacts) {
+ // insert the output artifacts into the pool of artifacts
+ foreach (const QString &fileTag, outputArtifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(outputArtifact);
+
+ // connect artifacts that match the file tags in explicitlyDependsOn
+ foreach (const QString &fileTag, rule->explicitlyDependsOn)
+ foreach (Artifact *dependency, artifactsPerFileTag.value(fileTag))
+ loggedConnect(outputArtifact, dependency);
+
+ // Transformer setup
+ transformer->outputs.insert(outputArtifact);
+ for (QSet<Artifact *>::const_iterator it = usingArtifacts.constBegin(); it != usingArtifacts.constEnd(); ++it) {
+ Artifact *dep = *it;
+ loggedConnect(outputArtifact, dep);
+ transformer->inputs.insert(dep);
+ foreach (Artifact *sideBySideDep, dep->sideBySideArtifacts) {
+ loggedConnect(outputArtifact, sideBySideDep);
+ transformer->inputs.insert(sideBySideDep);
+ }
+ }
+
+ m_artifactsThatMustGetNewTransformers -= outputArtifact;
+ }
+
+ // setup side-by-side artifacts
+ if (outputArtifacts.count() > 1)
+ foreach (Artifact *sbs1, outputArtifacts)
+ foreach (Artifact *sbs2, outputArtifacts)
+ if (sbs1 != sbs2)
+ sbs1->sideBySideArtifacts.insert(sbs2);
+
+ transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject());
+
+ // change the transformer outputs according to the bindings in Artifact
+ QScriptValue scriptValue;
+ for (int i=ruleArtifactArtifactMap.count(); --i >= 0;) {
+ RuleArtifact *ra = ruleArtifactArtifactMap.at(i).first;
+ if (ra->bindings.isEmpty())
+ continue;
+
+ // expose attributes of this artifact
+ Artifact *outputArtifact = ruleArtifactArtifactMap.at(i).second;
+ outputArtifact->configuration = Configuration::Ptr(new Configuration(*outputArtifact->configuration));
+
+ // ### clean m_scriptEngine first?
+ m_scriptEngine.globalObject().setProperty("fileName", m_scriptEngine.toScriptValue(outputArtifact->fileName), QScriptValue::ReadOnly);
+ m_scriptEngine.globalObject().setProperty("fileTags", toScriptValue(&m_scriptEngine, outputArtifact->fileTags), QScriptValue::ReadOnly);
+
+ QVariantMap artifactModulesCfg = outputArtifact->configuration->value().value("modules").toMap();
+ for (int i=0; i < ra->bindings.count(); ++i) {
+ const QStringList &name = ra->bindings.at(i).first;
+ const QString &code = ra->bindings.at(i).second;
+ scriptValue = m_scriptEngine.evaluate(code);
+ if (scriptValue.isError())
+ throw Error(QLatin1String("evaluating rule bindings: ") + scriptValue.toString());
+ setConfigProperty(artifactModulesCfg, name, scriptValue.toVariant());
+ }
+ QVariantMap outputArtifactConfiguration = outputArtifact->configuration->value();
+ outputArtifactConfiguration.insert("modules", artifactModulesCfg);
+ outputArtifact->configuration->setValue(outputArtifactConfiguration);
+ }
+
+ transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject());
+
+ // setup transform properties
+ {
+ const QVariantMap overriddenTransformProperties = product->rProduct->configuration->value().value("modules").toMap().value(rule->module->name).toMap().value(rule->objectId).toMap();
+ /*
+ overriddenTransformProperties contains the rule's transform properties that have been overridden in the project file.
+ For example, if you set cpp.compiler.defines in your project file, that property appears here.
+ */
+
+ QMap<QString, QScriptProgram>::const_iterator it = rule->transformProperties.begin();
+ for (; it != rule->transformProperties.end(); ++it)
+ {
+ const QString &propertyName = it.key();
+ QScriptValue sv;
+ if (overriddenTransformProperties.contains(propertyName)) {
+ sv = m_scriptEngine.toScriptValue(overriddenTransformProperties.value(propertyName));
+ } else {
+ const QScriptProgram &myProgram = it.value();
+ sv = m_scriptEngine.evaluate(myProgram);
+ if (m_scriptEngine.hasUncaughtException()) {
+ CodeLocation errorLocation;
+ errorLocation.fileName = m_scriptEngine.uncaughtExceptionBacktrace().join("\n");
+ errorLocation.line = m_scriptEngine.uncaughtExceptionLineNumber();
+ throw Error(QLatin1String("transform property evaluation: ") + m_scriptEngine.uncaughtException().toString(), errorLocation);
+ } else if (sv.isError()) {
+ CodeLocation errorLocation(myProgram.fileName(), myProgram.firstLineNumber());
+ throw Error(QLatin1String("transform property evaluation: ") + sv.toString(), errorLocation);
+ }
+ }
+ m_scriptEngine.globalObject().setProperty(propertyName, sv);
+ }
+ }
+
+ createTransformerCommands(rule->script, transformer.data());
+ if (transformer->commands.isEmpty())
+ throw Error(QString("There's a rule without commands: %1.").arg(rule->toString()), rule->script->location);
+}
+
+void BuildGraph::createTransformerCommands(RuleScript::Ptr script, Transformer *transformer)
+{
+ QScriptProgram &scriptProgram = m_scriptProgramCache[script->script];
+ if (scriptProgram.isNull())
+ scriptProgram = QScriptProgram(script->script);
+
+ QScriptValue scriptValue = m_scriptEngine.evaluate(scriptProgram);
+ if (m_scriptEngine.hasUncaughtException())
+ throw Error("evaluating prepare script: " + m_scriptEngine.uncaughtException().toString(),
+ script->location);
+
+ QList<AbstractCommand*> commands;
+ if (scriptValue.isArray()) {
+ const int count = scriptValue.property("length").toInt32();
+ for (qint32 i=0; i < count; ++i) {
+ QScriptValue item = scriptValue.property(i);
+ if (item.isValid() && !item.isUndefined()) {
+ AbstractCommand *cmd = createCommandFromScriptValue(item);
+ if (cmd)
+ commands += cmd;
+ }
+ }
+ } else {
+ AbstractCommand *cmd = createCommandFromScriptValue(scriptValue);
+ if (cmd)
+ commands += cmd;
+ }
+
+ transformer->commands = commands;
+}
+
+QString BuildGraph::buildDirectoryRoot() const
+{
+ Q_ASSERT(!m_outputDirectoryRoot.isEmpty());
+ QString path = FileInfo::resolvePath(m_outputDirectoryRoot, QLatin1String("build"));
+ if (!path.endsWith('/'))
+ path.append(QLatin1Char('/'));
+ return path;
+}
+
+/*
+ * c must be built before p
+ * p ----> c
+ * p.children = c
+ * c.parents = p
+ *
+ * also: children means i depend on or i am produced by
+ * parent means "produced by me" or "depends on me"
+ */
+void BuildGraph::connect(Artifact *p, Artifact *c)
+{
+ Q_ASSERT(p != c);
+ p->children.insert(c);
+ c->parents.insert(p);
+ p->project->markDirty();
+}
+
+void BuildGraph::loggedConnect(Artifact *u, Artifact *v)
+{
+ Q_ASSERT(u != v);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] connect '%s' -> '%s'",
+ qPrintable(fileName(u)),
+ qPrintable(fileName(v)));
+ connect(u, v);
+}
+
+static bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path)
+{
+ if (u == v) {
+ path.append(v);
+ return true;
+ }
+
+ for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) {
+ if (findPath(*it, v, path)) {
+ path.prepend(u);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool existsPath(Artifact *u, Artifact *v)
+{
+ if (u == v)
+ return true;
+
+ for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it)
+ if (existsPath(*it, v))
+ return true;
+
+ return false;
+}
+
+bool BuildGraph::safeConnect(Artifact *u, Artifact *v)
+{
+ Q_ASSERT(u != v);
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace("[BG] safeConnect: '%s' '%s'",
+ qPrintable(fileName(u)),
+ qPrintable(fileName(v)));
+
+ if (existsPath(v, u)) {
+ QList<Artifact *> circle;
+ findPath(v, u, circle);
+ qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle);
+ return false;
+ }
+
+ connect(u, v);
+ return true;
+}
+
+void BuildGraph::disconnect(Artifact *u, Artifact *v)
+{
+ u->children.remove(v);
+ v->parents.remove(u);
+}
+
+QSet<Artifact *> BuildGraph::disconnect(Artifact *n) const
+{
+ QSet<Artifact *> r;
+ if (n->children.count() == 1) {
+ Artifact * c = *(n->children.begin());
+ c->parents.remove(n);
+ n->children.clear();
+ r += n;
+ foreach (Artifact * p, n->parents) {
+ r += disconnect(p);
+ p->children.remove(n);
+ if (p->transformer)
+ p->transformer->inputs.remove(n);
+ }
+ }
+ return r;
+}
+
+void BuildGraph::remove(Artifact *artifact) const
+{
+ if (qbsLogLevel(LoggerTrace))
+ qbsTrace() << "[BG] remove artifact " << fileName(artifact);
+
+ if (artifact->artifactType == Artifact::Generated)
+ QFile::remove(artifact->fileName);
+ artifact->product->artifacts.remove(artifact->fileName);
+ artifact->product->targetArtifacts.remove(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ parent->children.remove(artifact);
+ if (parent->transformer) {
+ parent->transformer->inputs.remove(artifact);
+ m_artifactsThatMustGetNewTransformers += parent;
+ }
+ }
+ foreach (Artifact *child, artifact->children) {
+ child->parents.remove(artifact);
+ }
+ artifact->children.clear();
+ artifact->parents.clear();
+ artifact->project->markDirty();
+}
+
+/**
+ * Removes the artifact and all the artifacts that depend exclusively on it.
+ * Example: if you remove a cpp artifact then the obj artifact is removed but
+ * not the resulting application (if there's more then one cpp artifact).
+ */
+void BuildGraph::removeArtifactAndExclusiveDependents(Artifact *artifact, QList<Artifact*> *removedArtifacts)
+{
+ if (removedArtifacts)
+ removedArtifacts->append(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ if (parent->children.count() == 1)
+ removeArtifactAndExclusiveDependents(parent, removedArtifacts);
+ }
+ remove(artifact);
+}
+
+BuildProject::Ptr BuildGraph::resolveProject(ResolvedProject::Ptr rProject, QFutureInterface<bool> &futureInterface)
+{
+ BuildProject::Ptr project = BuildProject::Ptr(new BuildProject(this));
+ project->setResolvedProject(rProject);
+ foreach (ResolvedProduct::Ptr rProduct, rProject->products) {
+ resolveProduct(project.data(), rProduct, futureInterface);
+ }
+ detectCycle(project.data());
+ return project;
+}
+
+BuildProduct::Ptr BuildGraph::resolveProduct(BuildProject *project, ResolvedProduct::Ptr rProduct, QFutureInterface<bool> &futureInterface)
+{
+ BuildProduct::Ptr product = m_productCache.value(rProduct);
+ if (product)
+ return product;
+
+ futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ product = BuildProduct::Ptr(new BuildProduct);
+ m_productCache.insert(rProduct, product);
+ product->project = project;
+ product->rProduct = rProduct;
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+
+ foreach (ResolvedProduct::Ptr t2, rProduct->uses) {
+ if (t2 == rProduct) {
+ throw Error(tr("circular using"));
+ }
+ BuildProduct::Ptr referencedProduct = resolveProduct(project, t2, futureInterface);
+ product->usings.append(referencedProduct.data());
+ }
+
+ //add qbsFile artifact
+ Artifact *qbsFileArtifact = product->artifacts.value(rProduct->qbsFile);
+ if (!qbsFileArtifact) {
+ qbsFileArtifact = new Artifact(project);
+ qbsFileArtifact->artifactType = Artifact::SourceFile;
+ qbsFileArtifact->fileName = rProduct->qbsFile;
+ qbsFileArtifact->configuration = rProduct->configuration;
+ insert(product, qbsFileArtifact);
+ }
+ qbsFileArtifact->fileTags.insert("qbs");
+ artifactsPerFileTag["qbs"].insert(qbsFileArtifact);
+
+ // read sources
+ foreach (SourceArtifact::Ptr sourceArtifact, rProduct->sources) {
+ QString filePath = sourceArtifact->absoluteFilePath;
+ if (product->artifacts.contains(filePath)) {
+ // ignore duplicate artifacts
+ continue;
+ }
+
+ Artifact *artifact = createArtifact(product, sourceArtifact);
+
+ foreach (const QString &fileTag, artifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(artifact);
+ }
+
+ // read manually added transformers
+ QList<Artifact *> transformerOutputs;
+ foreach (const ResolvedTransformer::Ptr rtrafo, rProduct->transformers) {
+ QList<Artifact *> inputArtifacts;
+ foreach (const QString &inputFileName, rtrafo->inputs) {
+ Artifact *artifact = product->artifacts.value(inputFileName);
+ if (!artifact)
+ throw Error(QString("Can't find artifact '%0' in the list of source files.").arg(inputFileName));
+ if (artifact->fileTags.isEmpty())
+ artifact->fileTags += "unknown";
+ inputArtifacts += artifact;
+ }
+ QSharedPointer<Transformer> transformer(new Transformer);
+ transformer->inputs = inputArtifacts.toSet();
+ transformer->rule = Rule::Ptr(new Rule);
+ transformer->rule->inputs = rtrafo->inputs;
+ transformer->rule->jsImports = rtrafo->jsImports;
+ transformer->rule->module = ResolvedModule::Ptr(new ResolvedModule);
+ transformer->rule->module->name = rtrafo->module->name;
+ transformer->rule->script = rtrafo->transform;
+ foreach (SourceArtifact::Ptr sourceArtifact, rtrafo->outputs) {
+ Artifact *outputArtifact = createArtifact(product, sourceArtifact);
+ outputArtifact->artifactType = Artifact::Generated;
+ outputArtifact->transformer = transformer;
+ transformer->outputs += outputArtifact;
+ transformerOutputs += outputArtifact;
+ foreach (Artifact *inputArtifact, inputArtifacts)
+ safeConnect(outputArtifact, inputArtifact);
+ foreach (const QString &fileTag, outputArtifact->fileTags)
+ artifactsPerFileTag[fileTag].insert(outputArtifact);
+
+ RuleArtifact::Ptr ruleArtifact(new RuleArtifact);
+ ruleArtifact->fileScript = outputArtifact->fileName;
+ ruleArtifact->fileTags = outputArtifact->fileTags.toList();
+ transformer->rule->artifacts += ruleArtifact;
+ }
+ setupScriptEngineForProduct(&m_scriptEngine, rProduct, transformer->rule, this);
+ transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject());
+ transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject());
+ createTransformerCommands(rtrafo->transform, transformer.data());
+ if (transformer->commands.isEmpty())
+ throw Error(QString("There's a transformer without commands."), rtrafo->transform->location);
+ }
+
+ applyRules(product.data(), artifactsPerFileTag);
+
+ QSet<Artifact *> productArtifactCandidates;
+ for (int i=0; i < product->rProduct->fileTags.count(); ++i)
+ foreach (Artifact *artifact, artifactsPerFileTag.value(product->rProduct->fileTags.at(i)))
+ if (artifact->artifactType == Artifact::Generated)
+ productArtifactCandidates += artifact;
+
+ if (productArtifactCandidates.isEmpty()) {
+ // this should already be catched in the rule graph
+ throw Error("The impossible happenend! The rules generate no product.");
+ }
+
+ foreach (Artifact *productArtifact, productArtifactCandidates) {
+ product->targetArtifacts.insert(productArtifact);
+ project->addBuildProduct(product);
+
+ foreach (Artifact *trafoOutputArtifact, transformerOutputs)
+ if (productArtifact != trafoOutputArtifact)
+ loggedConnect(productArtifact, trafoOutputArtifact);
+ }
+
+ return product;
+}
+
+void BuildGraph::onProductChanged(BuildProduct::Ptr product, ResolvedProduct::Ptr changedProduct)
+{
+ qbsDebug() << "[BG] product '" << product->rProduct->name << "' changed.";
+
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+ QList<Artifact *> addedArtifacts, artifactsToRemove;
+ QHash<QString, SourceArtifact::Ptr> oldArtifacts, newArtifacts;
+ foreach (SourceArtifact::Ptr a, product->rProduct->sources)
+ oldArtifacts.insert(a->absoluteFilePath, a);
+ foreach (SourceArtifact::Ptr a, changedProduct->sources) {
+ newArtifacts.insert(a->absoluteFilePath, a);
+ if (!oldArtifacts.contains(a->absoluteFilePath)) {
+ // artifact added
+ qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' added to product " << product->rProduct->name;
+ product->rProduct->sources.insert(a);
+ addedArtifacts += createArtifact(product, a);
+ }
+ }
+ foreach (SourceArtifact::Ptr a, product->rProduct->sources) {
+ SourceArtifact::Ptr changedArtifact = newArtifacts.value(a->absoluteFilePath);
+ if (!changedArtifact) {
+ // artifact removed
+ qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' removed from product " << product->rProduct->name;
+ Artifact *artifact = product->artifacts.value(a->absoluteFilePath);
+ Q_ASSERT(artifact);
+ removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove);
+ continue;
+ }
+ if (changedArtifact->fileTags != a->fileTags) {
+ // artifact's filetags have changed
+ qbsDebug() << "[BG] filetags have changed for artifact '" << a->absoluteFilePath
+ << "' from " << a->fileTags << " to " << changedArtifact->fileTags;
+ Artifact *artifact = product->artifacts.value(a->absoluteFilePath);
+ Q_ASSERT(artifact);
+
+ // handle added filetags
+ foreach (const QString &addedFileTag, changedArtifact->fileTags - a->fileTags)
+ artifactsPerFileTag[addedFileTag] += artifact;
+
+ // handle removed filetags
+ foreach (const QString &removedFileTag, a->fileTags - changedArtifact->fileTags) {
+ artifact->fileTags -= removedFileTag;
+ foreach (Artifact *parent, artifact->parents) {
+ if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) {
+ // this parent has been created because of the removed filetag
+ removeArtifactAndExclusiveDependents(parent, &artifactsToRemove);
+ }
+ }
+ }
+ }
+ if (changedArtifact->configuration->value() != a->configuration->value()) // ### TODO
+ {
+ qWarning("Some properties changed. Consider rebuild or fix QBS-7. File name: %s", qPrintable(changedArtifact->absoluteFilePath));
+ QVariantMap m = a->configuration->value();
+
+ for (QVariantMap::iterator it = m.begin(); it != m.end(); ++it) {
+ if (it.value() != changedArtifact->configuration->value().value(it.key())) {
+ qDebug() << " old:" << it.value();
+ qDebug() << " new:" << changedArtifact->configuration->value().value(it.key());
+ }
+ }
+ }
+ }
+
+ // apply rules for new artifacts
+ foreach (Artifact *artifact, addedArtifacts)
+ foreach (const QString &ft, artifact->fileTags)
+ artifactsPerFileTag[ft] += artifact;
+ applyRules(product.data(), artifactsPerFileTag);
+
+ // parents of removed artifacts must update their transformers
+ foreach (Artifact *removedArtifact, artifactsToRemove)
+ foreach (Artifact *parent, removedArtifact->parents)
+ m_artifactsThatMustGetNewTransformers += parent;
+ updateNodesThatMustGetNewTransformer();
+
+ // delete all removed artifacts physically from the disk
+ foreach (Artifact *artifact, artifactsToRemove) {
+ if (artifact->artifactType == Artifact::Generated) {
+ qbsDebug() << "[BG] deleting stale artifact " << artifact->fileName;
+ QFile::remove(artifact->fileName);
+ }
+ delete artifact;
+ }
+}
+
+void BuildGraph::updateNodesThatMustGetNewTransformer()
+{
+ foreach (Artifact *artifact, m_artifactsThatMustGetNewTransformers)
+ updateNodeThatMustGetNewTransformer(artifact);
+ m_artifactsThatMustGetNewTransformers.clear();
+}
+
+void BuildGraph::updateNodeThatMustGetNewTransformer(Artifact *artifact)
+{
+ Q_CHECK_PTR(artifact->transformer);
+
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[BG] updating transformer for " << fileName(artifact);
+
+ Rule::Ptr rule = artifact->transformer->rule;
+ artifact->product->project->markDirty();
+ artifact->transformer = QSharedPointer<Transformer>();
+
+ QMap<QString, QSet<Artifact *> > artifactsPerFileTag;
+ foreach (Artifact *input, artifact->children)
+ foreach (const QString &fileTag, input->fileTags)
+ artifactsPerFileTag[fileTag] += input;
+
+ applyRule(artifact->product, artifactsPerFileTag, rule);
+}
+
+Artifact *BuildGraph::createArtifact(BuildProduct::Ptr product, SourceArtifact::Ptr sourceArtifact)
+{
+ Artifact *artifact = new Artifact(product->project);
+ artifact->artifactType = Artifact::SourceFile;
+ artifact->fileName = sourceArtifact->absoluteFilePath;
+ artifact->fileTags = sourceArtifact->fileTags;
+ artifact->configuration = sourceArtifact->configuration;
+ insert(product, artifact);
+ return artifact;
+}
+
+QString BuildGraph::resolveOutPath(const QString &path, BuildProduct *product) const
+{
+ QString result;
+ QString buildDir = product->rProduct->buildDirectory;
+ result = FileInfo::resolvePath(buildDir, path);
+
+ Q_ASSERT(result.startsWith(buildDir));
+ result = QDir::cleanPath(result);
+ return result;
+}
+
+void Transformer::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+ rule = pool.idLoadS<Rule>(s);
+ loadContainer(inputs, s, pool);
+ loadContainer(outputs, s, pool);
+ int count, cmdType;
+ s >> count;
+ commands.reserve(count);
+ while (--count >= 0) {
+ s >> cmdType;
+ AbstractCommand *cmd = AbstractCommand::createByType(static_cast<AbstractCommand::CommandType>(cmdType));
+ cmd->load(s);
+ commands += cmd;
+ }
+}
+
+void Transformer::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s << pool.store(rule);
+ storeContainer(inputs, s, pool);
+ storeContainer(outputs, s, pool);
+ s << commands.count();
+ foreach (AbstractCommand *cmd, commands) {
+ s << int(cmd->type());
+ cmd->store(s);
+ }
+}
+
+void BuildProduct::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+ int i, count;
+
+ // artifacts
+ artifacts.clear();
+ s >> count;
+ for (i = count; --i >= 0;) {
+ QString key;
+ s >> key;
+ artifacts.insert(key, pool.idLoad<Artifact>(s));
+ }
+
+ // edges
+ for (i = count; --i >= 0;) {
+ Artifact *artifact = pool.idLoad<Artifact>(s);
+ int count2, j;
+ s >> count2;
+ artifact->parents.clear();
+ artifact->parents.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->parents.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->children.clear();
+ artifact->children.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->children.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->fileDependencies.clear();
+ artifact->fileDependencies.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->fileDependencies.insert(pool.idLoad<Artifact>(s));
+
+ s >> count2;
+ artifact->sideBySideArtifacts.clear();
+ artifact->sideBySideArtifacts.reserve(count2);
+ for (j = count2; --j >= 0;)
+ artifact->sideBySideArtifacts.insert(pool.idLoad<Artifact>(s));
+ }
+
+ // other data
+ rProduct = pool.idLoadS<ResolvedProduct>(s);
+ loadContainer(targetArtifacts, s, pool);
+ loadContainer(usings, s, pool);
+}
+
+void BuildProduct::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s << artifacts.count();
+
+ //artifacts
+ for (QHash<QString, Artifact *>::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) {
+ s << i.key();
+ PersistentObjectId artifactId = pool.store(i.value());
+ s << artifactId;
+ }
+
+ // edges
+ for (QHash<QString, Artifact *>::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) {
+ Artifact * artifact = i.value();
+ s << pool.store(artifact);
+
+ s << artifact->parents.count();
+ foreach (Artifact * n, artifact->parents)
+ s << pool.store(n);
+ s << artifact->children.count();
+ foreach (Artifact * n, artifact->children)
+ s << pool.store(n);
+ s << artifact->fileDependencies.count();
+ foreach (Artifact * n, artifact->fileDependencies)
+ s << pool.store(n);
+ s << artifact->sideBySideArtifacts.count();
+ foreach (Artifact *n, artifact->sideBySideArtifacts)
+ s << pool.store(n);
+ }
+
+ // other data
+ s << pool.store(rProduct);
+ storeContainer(targetArtifacts, s, pool);
+ storeContainer(usings, s, pool);
+}
+
+BuildProject::BuildProject(BuildGraph *bg)
+ : m_buildGraph(bg)
+ , m_dirty(false)
+{
+}
+
+BuildProject::~BuildProject()
+{
+ qDeleteAll(m_dependencyArtifacts);
+}
+
+static bool isConfigCompatible(const QVariantMap &userCfg, const QVariantMap &projectCfg)
+{
+ QVariantMap::const_iterator it = userCfg.begin();
+ for (; it != userCfg.end(); ++it) {
+ if (it.value().type() == QVariant::Map) {
+ if (!isConfigCompatible(it.value().toMap(), projectCfg.value(it.key()).toMap()))
+ return false;
+ } else {
+ QVariant value = projectCfg.value(it.key());
+ if (!value.isNull() && value != it.value()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+BuildProject::Ptr BuildProject::load(BuildGraph *bg, const FileTime &minTimeStamp, Configuration::Ptr cfg, const QStringList &loaderSearchPaths)
+{
+ PersistentPool pool;
+ QString fileName;
+ QStringList bgFiles = storedProjectFiles(bg);
+ foreach (const QString &fn, bgFiles) {
+ if (!pool.load(fn, PersistentPool::LoadHeadData))
+ continue;
+ PersistentPool::HeadData headData = pool.headData();
+ if (isConfigCompatible(cfg->value(), headData.projectConfig)) {
+ fileName = fn;
+ break;
+ }
+ }
+ if (fileName.isNull()) {
+ qbsDebug() << "[BG] No stored build graph found that's compatible to the desired build configuration.";
+ return BuildProject::Ptr();
+ }
+
+ BuildProject::Ptr project;
+ qbsDebug() << "[BG] trying to load: " << fileName;
+ FileInfo bgfi(fileName);
+ if (!bgfi.exists()) {
+ qbsDebug() << "[BG] stored build graph file does not exist";
+ return project;
+ }
+ if (!pool.load(fileName))
+ throw Error("Cannot load stored build graph.");
+ project = BuildProject::Ptr(new BuildProject(bg));
+ PersistentObjectData data = pool.getData(0);
+ project->load(pool, data);
+ project->resolvedProject()->configuration = Configuration::Ptr(new Configuration);
+ project->resolvedProject()->configuration->setValue(pool.headData().projectConfig);
+ qbsDebug() << "[BG] stored project loaded.";
+
+ bool projectFileChanged = false;
+ if (bgfi.lastModified() < minTimeStamp) {
+ projectFileChanged = true;
+ }
+
+ QList<BuildProduct::Ptr> changedProducts;
+ foreach (BuildProduct::Ptr product, project->buildProducts()) {
+ FileInfo pfi(product->rProduct->qbsFile);
+ if (!pfi.exists())
+ throw Error(QString("The product file '%1' is gone.").arg(product->rProduct->qbsFile));
+ if (bgfi.lastModified() < pfi.lastModified())
+ changedProducts += product;
+ }
+
+ if (projectFileChanged || !changedProducts.isEmpty()) {
+
+ Loader ldr;
+ ldr.setSearchPaths(loaderSearchPaths);
+ ldr.loadProject(project->resolvedProject()->qbsFile);
+ QFutureInterface<bool> dummyFutureInterface;
+ ResolvedProject::Ptr changedProject = ldr.resolveProject(bg->buildDirectoryRoot(), cfg, dummyFutureInterface);
+ if (!changedProject) {
+ QString msg("Trying to load '%1' failed.");
+ throw Error(msg.arg(project->resolvedProject()->qbsFile));
+ }
+
+ if (projectFileChanged) {
+ qWarning("[BG] project file changed: %s", qPrintable(project->resolvedProject()->qbsFile));
+ qWarning("[BG] ### HANDLING THAT PROPERLY IS NOT YET IMPLEMENTED");
+ qWarning("[BG] ### CONSIDER DELETING THE STORED BUILD GRAPH");
+ }
+
+ QMap<QString, ResolvedProduct::Ptr> changedProductsMap;
+ foreach (BuildProduct::Ptr product, changedProducts) {
+ if (changedProductsMap.isEmpty())
+ foreach (ResolvedProduct::Ptr cp, changedProject->products)
+ changedProductsMap.insert(cp->name, cp);
+ bg->onProductChanged(product, changedProductsMap.value(product->rProduct->name));
+ }
+
+ BuildGraph::detectCycle(project.data());
+ }
+
+ return project;
+}
+
+void BuildProject::store()
+{
+ if (!dirty()) {
+ qbsDebug() << "[BG] build graph is unchanged in project " << resolvedProject()->id << ".";
+ return;
+ }
+ const QString fileName = storedProjectFilePath(buildGraph(), resolvedProject()->id);
+ qbsDebug() << "[BG] storing: " << fileName;
+ PersistentPool pool;
+ PersistentPool::HeadData headData;
+ headData.projectConfig = resolvedProject()->configuration->value();
+ pool.setHeadData(headData);
+ PersistentObjectData data;
+ store(pool, data);
+ pool.setData(0, data);
+ pool.store(fileName);
+}
+
+QString BuildProject::storedProjectFilePath(BuildGraph *bg, const QString &projectId)
+{
+ return bg->buildDirectoryRoot() + projectId + ".bg";
+}
+
+QStringList BuildProject::storedProjectFiles(BuildGraph *bg)
+{
+ QStringList result;
+ QDirIterator dirit(bg->buildDirectoryRoot(), QStringList() << "*.bg", QDir::Files);
+ while (dirit.hasNext())
+ result += dirit.next();
+ return result;
+}
+
+void BuildProject::load(PersistentPool &pool, PersistentObjectData &data)
+{
+ QDataStream s(data);
+
+ setResolvedProject(pool.idLoadS<ResolvedProject>(s));
+
+ int count, i;
+ s >> count;
+ for (i = count; --i >= 0;) {
+ BuildProduct::Ptr product = pool.idLoadS<BuildProduct>(s);
+ product->project = this;
+ foreach (Artifact *artifact, product->artifacts)
+ artifact->project = this;
+ addBuildProduct(product);
+ }
+
+ s >> count;
+ m_dependencyArtifacts.clear();
+ m_dependencyArtifacts.reserve(count);
+ for (i = count; --i >= 0;) {
+ Artifact *artifact = pool.idLoad<Artifact>(s);
+ artifact->project = this;
+ m_dependencyArtifacts.insert(artifact->fileName, artifact);
+ }
+}
+
+void BuildProject::store(PersistentPool &pool, PersistentObjectData &data) const
+{
+ QDataStream s(&data, QIODevice::WriteOnly);
+
+ s << pool.store(resolvedProject());
+ storeContainer(m_buildProducts, s, pool);
+ storeHashContainer(m_dependencyArtifacts, s, pool);
+}
+
+char **createCFileTags(const QSet<QString> &fileTags)
+{
+ if (fileTags.isEmpty())
+ return 0;
+
+ char **buf = new char*[fileTags.count()];
+ size_t i = 0;
+ foreach (const QString &fileTag, fileTags) {
+ buf[i] = qstrdup(fileTag.toLocal8Bit().data());
+ ++i;
+ }
+ return buf;
+}
+
+void freeCFileTags(char **cFileTags, int numFileTags)
+{
+ if (!cFileTags)
+ return;
+ for (int i = numFileTags; --i >= 0;)
+ delete[] cFileTags[i];
+ delete[] cFileTags;
+}
+
+BuildGraph * BuildProject::buildGraph() const
+{
+ return m_buildGraph;
+}
+
+ResolvedProject::Ptr BuildProject::resolvedProject() const
+{
+ return m_resolvedProject;
+}
+
+QSet<BuildProduct::Ptr> BuildProject::buildProducts() const
+{
+ return m_buildProducts;
+}
+
+QHash<QString, Artifact *> &BuildProject::dependencyArtifacts()
+{
+ return m_dependencyArtifacts;
+}
+
+bool BuildProject::dirty() const
+{
+ return m_dirty;
+}
+
+Artifact *BuildProject::findArtifact(const QString &filePath) const
+{
+ Artifact *artifact = m_dependencyArtifacts.value(filePath);
+ if (!artifact) {
+ foreach (const BuildProduct::Ptr &product, m_buildProducts) {
+ artifact = product->artifacts.value(filePath);
+ if (artifact)
+ break;
+ }
+ }
+ return artifact;
+}
+
+void BuildProject::markDirty()
+{
+ m_dirty = true;
+}
+
+void BuildProject::addBuildProduct(const BuildProduct::Ptr &product)
+{
+ m_buildProducts.insert(product);
+}
+
+void BuildProject::setResolvedProject(const ResolvedProject::Ptr &resolvedProject)
+{
+ m_resolvedProject = resolvedProject;
+}
+
+QString fileName(Artifact *n)
+{
+ class BuildGraph *bg = n->project->buildGraph();
+ QString str = n->fileName;
+ if (str.startsWith(bg->outputDirectoryRoot()))
+ str.remove(0, bg->outputDirectoryRoot().count());
+ if (str.startsWith('/'))
+ str.remove(0, 1);
+ return str;
+}
+
+} // namespace qbs