summaryrefslogtreecommitdiff
path: root/src/lib/buildgraph
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2013-06-18 15:24:24 +0200
committerJoerg Bornemann <joerg.bornemann@digia.com>2013-06-19 12:00:30 +0200
commitcc1d95bb7ded0337ab27fd1b6c6206452d6dcd14 (patch)
tree840d41f9ee2599b695e6f3355cb813b0ecd78478 /src/lib/buildgraph
parent39b62dd0bb9691aed813d760ef7af290c0cd38b4 (diff)
downloadqbs-cc1d95bb7ded0337ab27fd1b6c6206452d6dcd14.tar.gz
Move BuildGraphLoader into its own set of files.
Change-Id: Icd11a83337a34d3544bfd59c1ab93c9d2ccb4dea Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
Diffstat (limited to 'src/lib/buildgraph')
-rw-r--r--src/lib/buildgraph/buildgraph.cpp507
-rw-r--r--src/lib/buildgraph/buildgraph.h40
-rw-r--r--src/lib/buildgraph/buildgraph.pri2
-rw-r--r--src/lib/buildgraph/buildgraphloader.cpp550
-rw-r--r--src/lib/buildgraph/buildgraphloader.h85
5 files changed, 637 insertions, 547 deletions
diff --git a/src/lib/buildgraph/buildgraph.cpp b/src/lib/buildgraph/buildgraph.cpp
index 8fa790bf2..95330f962 100644
--- a/src/lib/buildgraph/buildgraph.cpp
+++ b/src/lib/buildgraph/buildgraph.cpp
@@ -31,25 +31,16 @@
#include "artifact.h"
#include "projectbuilddata.h"
#include "productbuilddata.h"
-#include "cycledetector.h"
-#include "rulesevaluationcontext.h"
-#include "transformer.h"
#include <jsextensions/moduleproperties.h>
-#include <language/loader.h>
#include <language/language.h>
#include <language/scriptengine.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
-#include <tools/fileinfo.h>
-#include <tools/propertyfinder.h>
#include <tools/qbsassert.h>
-#include <tools/setupprojectparameters.h>
-#include <QDir>
#include <QFile>
-#include <QFileInfo>
namespace qbs {
namespace Internal {
@@ -436,503 +427,5 @@ void dumpProductBuildData(const ResolvedProductConstPtr &product)
dumpProductBuildDataInternal(product, artifact, QByteArray());
}
-
-BuildGraphLoader::BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger) :
- m_logger(logger), m_environment(env)
-{
-}
-
-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;
-}
-
-void restoreBackPointers(const ResolvedProjectPtr &project)
-{
- foreach (const ResolvedProductPtr &product, project->products) {
- product->project = project;
- if (!product->buildData)
- continue;
- foreach (Artifact * const a, product->buildData->artifacts) {
- a->project = project;
- project->topLevelProject()->buildData->insertIntoArtifactLookupTable(a);
- }
- }
-
- foreach (const ResolvedProjectPtr &subProject, project->subProjects) {
- subProject->parentProject = project;
- restoreBackPointers(subProject);
- }
-}
-
-BuildGraphLoadResult BuildGraphLoader::load(const SetupProjectParameters &parameters,
- const RulesEvaluationContextPtr &evalContext)
-{
- m_result = BuildGraphLoadResult();
- m_evalContext = evalContext;
-
- const QString projectId = TopLevelProject::deriveId(parameters.buildConfigurationTree());
- const QString buildDir
- = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), projectId);
- const QString buildGraphFilePath
- = ProjectBuildData::deriveBuildGraphFilePath(buildDir, projectId);
-
- PersistentPool pool(m_logger);
- m_logger.qbsDebug() << "[BG] trying to load: " << buildGraphFilePath;
- try {
- pool.load(buildGraphFilePath);
- } catch (const ErrorInfo &loadError) {
- if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
- throw loadError;
- m_logger.qbsInfo() << loadError.toString();
- return m_result;
- }
-
- if (!isConfigCompatible(parameters.buildConfigurationTree(), pool.headData().projectConfig)) {
- const QString message = Tr::tr("Cannot use stored build graph at '%1':"
- "Incompatible project configuration.").arg(buildGraphFilePath);
- if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
- throw ErrorInfo(message);
- m_logger.qbsInfo() << message;
- return m_result;
- }
-
- const TopLevelProjectPtr project = TopLevelProject::create();
-
- // TODO: Store some meta data that will enable us to show actual progress (e.g. number of products).
- evalContext->initializeObserver(Tr::tr("Restoring build graph from disk"), 1);
-
- project->load(pool);
- project->buildData->evaluationContext = evalContext;
-
- if (QFileInfo(project->location.fileName()) != QFileInfo(parameters.projectFilePath())) {
- QString errorMessage = Tr::tr("Stored build graph is for project file '%1', but "
- "input file is '%2'. ")
- .arg(QDir::toNativeSeparators(project->location.fileName()),
- QDir::toNativeSeparators(parameters.projectFilePath()));
- if (!parameters.ignoreDifferentProjectFilePath()) {
- errorMessage += Tr::tr("Aborting.");
- throw ErrorInfo(errorMessage);
- }
-
- // Okay, let's assume it's the same project anyway (the source dir might have moved).
- errorMessage += Tr::tr("Ignoring.");
- m_logger.qbsWarning() << errorMessage;
- }
-
- restoreBackPointers(project);
- foreach (Artifact * const a, project->buildData->dependencyArtifacts)
- a->project = project;
-
- project->location = CodeLocation(parameters.projectFilePath(), project->location.line(),
- project->location.column());
- project->setBuildConfiguration(pool.headData().projectConfig);
- project->buildDirectory = buildDir;
- m_result.loadedProject = project;
- evalContext->incrementProgressValue();
-
- if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
- return m_result;
- QBS_CHECK(parameters.restoreBehavior() == SetupProjectParameters::RestoreAndTrackChanges);
-
- trackProjectChanges(parameters, buildGraphFilePath, project);
- return m_result;
-}
-
-static void exchangeDependencies(const ResolvedProductPtr &newProduct,
- const QList<ResolvedProductPtr> &allRestoredProducts,
- const QList<ResolvedProductPtr> &addedProducts)
-{
- QSet<ResolvedProductPtr> newDependencies;
- foreach (const ResolvedProductPtr &dependency, newProduct->dependencies) {
- if (addedProducts.contains(dependency)) {
- newDependencies << dependency;
- exchangeDependencies(dependency, allRestoredProducts, addedProducts);
- continue;
- }
- bool counterPartFound = false;
- foreach (const ResolvedProductPtr &restoredProduct, allRestoredProducts) {
- if (restoredProduct->name == dependency->name) {
- newDependencies << restoredProduct;
- counterPartFound = true;
- break;
- }
- }
- QBS_CHECK(counterPartFound);
- }
- newProduct->dependencies = newDependencies;
-}
-
-static void manipulateAddedProducts(const QList<ResolvedProjectPtr> &allRestoredProjects,
- const QList<ResolvedProductPtr> &allRestoredProducts,
- const QList<ResolvedProductPtr> &addedProducts)
-{
- foreach (const ResolvedProductPtr &product, addedProducts) {
- // Find the right existing (sub-)project to put the new product into.
- bool projectFound = false;
- foreach (const ResolvedProjectPtr &project, allRestoredProjects) {
- if (project->location != product->project->location)
- continue;
- product->project = project;
- project->products.append(product);
- projectFound = true;
- break;
- }
- QBS_CHECK(projectFound);
-
- // Exchange dependencies that are not new with their counterparts from the restored project.
- exchangeDependencies(product, allRestoredProducts, addedProducts);
- }
-}
-
-// TODO: Pay more attention to project metadata such as name and location. If these change,
-// we must transfer them over to the old build graph.
-void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters &parameters,
- const QString &buildGraphFilePath, const TopLevelProjectPtr &restoredProject)
-{
- const FileInfo bgfi(buildGraphFilePath);
- const QList<ResolvedProjectPtr> allRestoredProjects
- = restoredProject->allSubProjects() << restoredProject;
- bool projectFileChanged = false;
- bool subProjectRemoved = false;
- foreach (const ResolvedProjectConstPtr &p, allRestoredProjects) {
- FileInfo fi(p->location.fileName());
- if (!fi.exists()) {
- subProjectRemoved = true;
- break;
- }
- if (bgfi.lastModified() < fi.lastModified()) {
- projectFileChanged = true;
- break;
- }
- }
- if (subProjectRemoved)
- m_logger.qbsTrace() << "A sub-project was removed, must re-resolve project";
- if (projectFileChanged)
- m_logger.qbsTrace() << "A project file changed, must re-resolve project.";
-
- bool environmentChanged = false;
- for (QHash<QString, QString>::ConstIterator it = restoredProject->usedEnvironment.constBegin();
- !environmentChanged && it != restoredProject->usedEnvironment.constEnd(); ++it) {
- environmentChanged = m_environment.value(it.key()) != it.value();
- }
- if (environmentChanged)
- m_logger.qbsTrace() << "A relevant environment variable changed, must re-resolve project.";
-
- bool productRemoved = false;
- const QList<ResolvedProductPtr> allRestoredProducts = restoredProject->allProducts();
- QList<ResolvedProductPtr> changedProducts;
- foreach (const ResolvedProductPtr &product, allRestoredProducts) {
- const FileInfo pfi(product->location.fileName());
- if (!pfi.exists()) {
- productRemoved = true;
- } else if (bgfi.lastModified() < pfi.lastModified()) {
- changedProducts += product;
- } else {
- foreach (const GroupPtr &group, product->groups) {
- if (!group->wildcards)
- continue;
- const QSet<QString> files
- = group->wildcards->expandPatterns(group, product->sourceDirectory);
- QSet<QString> wcFiles;
- foreach (const SourceArtifactConstPtr &sourceArtifact, group->wildcards->files)
- wcFiles += sourceArtifact->absoluteFilePath;
- if (files == wcFiles)
- continue;
- changedProducts += product;
- break;
- }
- }
- }
-
- if (!environmentChanged && !projectFileChanged && !subProjectRemoved && !productRemoved
- && changedProducts.isEmpty()) {
- return;
- }
-
- Loader ldr(m_evalContext->engine(), m_logger);
- ldr.setSearchPaths(parameters.searchPaths());
- m_result.newlyResolvedProject = ldr.loadProject(parameters);
-
- QMap<QString, ResolvedProductPtr> freshProductsByName;
- const QList<ResolvedProductPtr> allNewlyResolvedProducts
- = m_result.newlyResolvedProject->allProducts();
- foreach (const ResolvedProductPtr &cp, allNewlyResolvedProducts)
- freshProductsByName.insert(cp->name, cp);
-
- QSet<TransformerPtr> seenTransformers;
- foreach (const ResolvedProductPtr &product, allRestoredProducts) {
- if (!product->buildData)
- continue;
- foreach (Artifact *artifact, product->buildData->artifacts) {
- if (!artifact->transformer || seenTransformers.contains(artifact->transformer))
- continue;
- seenTransformers.insert(artifact->transformer);
- ResolvedProductPtr freshProduct = freshProductsByName.value(product->name);
- if (freshProduct && checkForPropertyChanges(artifact->transformer, freshProduct)) {
- m_result.discardLoadedProject = true;
- return;
- }
- }
- }
-
- foreach (const ResolvedProductPtr &product, changedProducts) {
- ResolvedProductPtr freshProduct = freshProductsByName.value(product->name);
- if (!freshProduct)
- continue;
- onProductChanged(product, freshProduct);
- if (m_result.discardLoadedProject)
- return;
- }
-
- QSet<QString> oldProductNames, newProductNames;
- foreach (const ResolvedProductConstPtr &product, allRestoredProducts)
- oldProductNames += product->name;
- foreach (const ResolvedProductConstPtr &product, allNewlyResolvedProducts)
- newProductNames += product->name;
-
- const QSet<QString> removedProductsNames = oldProductNames - newProductNames;
- if (!removedProductsNames.isEmpty()) {
- foreach (const ResolvedProductPtr &product, allRestoredProducts) {
- if (removedProductsNames.contains(product->name))
- onProductRemoved(product);
- }
- }
-
- const QSet<QString> addedProductNames = newProductNames - oldProductNames;
- QList<ResolvedProductPtr> addedProducts;
- foreach (const QString &productName, addedProductNames) {
- const ResolvedProductPtr &freshProduct = freshProductsByName.value(productName);
- QBS_ASSERT(freshProduct, continue);
- addedProducts.append(freshProduct);
- }
- if (!addedProducts.isEmpty()) {
- manipulateAddedProducts(allRestoredProjects, allRestoredProducts, addedProducts);
- BuildDataResolver bpr(m_logger);
- bpr.resolveProductBuildDataForExistingProject(restoredProject, addedProducts);
- }
-
- CycleDetector(m_logger).visitProject(restoredProject);
-}
-
-void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product)
-{
- m_logger.qbsDebug() << "[BG] product '" << product->name << "' removed.";
-
- product->topLevelProject()->buildData->isDirty = true;
- product->project->products.removeOne(product);
-
- // delete all removed artifacts physically from the disk
- if (product->buildData) {
- foreach (Artifact *artifact, product->buildData->artifacts) {
- artifact->disconnectAll(m_logger);
- removeGeneratedArtifactFromDisk(artifact, m_logger);
- }
- }
-}
-
-void BuildGraphLoader::onProductChanged(const ResolvedProductPtr &product,
- const ResolvedProductPtr &changedProduct)
-{
- m_logger.qbsDebug() << "[BG] product '" << product->name << "' changed.";
-
- ArtifactsPerFileTagMap artifactsPerFileTag;
- QList<Artifact *> addedArtifacts;
- ArtifactList artifactsToRemove;
- QHash<QString, SourceArtifactConstPtr> oldArtifacts, newArtifacts;
-
- const QList<SourceArtifactPtr> oldProductAllFiles = product->allEnabledFiles();
- foreach (const SourceArtifactConstPtr &a, oldProductAllFiles)
- oldArtifacts.insert(a->absoluteFilePath, a);
- foreach (const SourceArtifactPtr &a, changedProduct->allEnabledFiles()) {
- newArtifacts.insert(a->absoluteFilePath, a);
- if (!oldArtifacts.contains(a->absoluteFilePath)) {
- // artifact added
- m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath
- << "' added to product " << product->name;
- Artifact *newArtifact = lookupArtifact(product, a->absoluteFilePath);
- if (newArtifact) {
- // User added a source file that was a generated artifact in the previous
- // build, e.g. a C++ source file that was generated and now is a non-generated
- // source file.
- newArtifact->artifactType = Artifact::SourceFile;
- } else {
- newArtifact = createArtifact(product, a, m_logger);
- foreach (Artifact *oldArtifact,
- product->topLevelProject()->buildData->lookupArtifacts(newArtifact->filePath())) {
- if (oldArtifact == newArtifact
- || oldArtifact->artifactType != Artifact::FileDependency) {
- // The source file already exists in another product.
- continue;
- }
- // User added a source file that was recognized as file dependency in the
- // previous build, e.g. a C++ header file.
- replaceFileDependencyWithArtifact(oldArtifact, newArtifact);
- }
- }
- addedArtifacts += newArtifact;
- }
- }
-
- foreach (const SourceArtifactPtr &a, oldProductAllFiles) {
- const SourceArtifactConstPtr changedArtifact = newArtifacts.value(a->absoluteFilePath);
- if (!changedArtifact) {
- // artifact removed
- m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath
- << "' removed from product " << product->name;
- Artifact *artifact = lookupArtifact(product, a->absoluteFilePath);
- QBS_CHECK(artifact);
- removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove);
- continue;
- }
- if (changedArtifact->fileTags != a->fileTags) {
- // artifact's filetags have changed
- m_logger.qbsDebug() << "[BG] filetags have changed for artifact '"
- << a->absoluteFilePath << "' from " << a->fileTags << " to "
- << changedArtifact->fileTags;
- Artifact *artifact = lookupArtifact(product, a->absoluteFilePath);
- QBS_CHECK(artifact);
-
- // handle added filetags
- foreach (const FileTag &addedFileTag, changedArtifact->fileTags - a->fileTags)
- artifactsPerFileTag[addedFileTag] += artifact;
-
- // handle removed filetags
- foreach (const FileTag &removedFileTag, a->fileTags - changedArtifact->fileTags) {
- artifact->fileTags -= removedFileTag;
- foreach (Artifact *parent, artifact->parents) {
- if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) {
- // this parent has been created because of the removed filetag
- removeArtifactAndExclusiveDependents(parent, &artifactsToRemove);
- }
- }
- }
- }
- }
-
- // Discard groups of the old product. Use the groups of the new one.
- product->groups = changedProduct->groups;
- product->properties = changedProduct->properties;
-
- // apply rules for new artifacts
- foreach (Artifact *artifact, addedArtifacts)
- foreach (const FileTag &ft, artifact->fileTags)
- artifactsPerFileTag[ft] += artifact;
- RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules();
-
- addTargetArtifacts(product, artifactsPerFileTag, m_logger);
-
- // parents of removed artifacts must update their transformers
- foreach (Artifact *removedArtifact, artifactsToRemove)
- foreach (Artifact *parent, removedArtifact->parents)
- product->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent;
- product->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger);
-
- // delete all removed artifacts physically from the disk
- foreach (Artifact *artifact, artifactsToRemove) {
- removeGeneratedArtifactFromDisk(artifact, m_logger);
- delete artifact;
- }
-}
-
-/**
- * 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 BuildGraphLoader::removeArtifactAndExclusiveDependents(Artifact *artifact,
- ArtifactList *removedArtifacts)
-{
- if (removedArtifacts)
- removedArtifacts->insert(artifact);
- foreach (Artifact *parent, artifact->parents) {
- bool removeParent = false;
- disconnect(parent, artifact, m_logger);
- if (parent->children.isEmpty()) {
- removeParent = true;
- } else if (parent->transformer) {
- artifact->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent;
- parent->transformer->inputs.remove(artifact);
- removeParent = parent->transformer->inputs.isEmpty();
- }
- if (removeParent)
- removeArtifactAndExclusiveDependents(parent, removedArtifacts);
- }
- artifact->topLevelProject()->buildData->removeArtifact(artifact, m_logger);
-}
-
-bool BuildGraphLoader::checkForPropertyChanges(const TransformerPtr &restoredTrafo,
- const ResolvedProductPtr &freshProduct)
-{
- PropertyFinder finder;
- foreach (const Property &property, restoredTrafo->modulePropertiesUsedInPrepareScript) {
- QVariant v;
- if (property.kind == Property::PropertyInProduct) {
- v = freshProduct->properties->value().value(property.propertyName);
- } else if (property.value.type() == QVariant::List) {
- v = finder.propertyValues(freshProduct->properties->value(), property.moduleName,
- property.propertyName);
- } else {
- v = finder.propertyValue(freshProduct->properties->value(), property.moduleName,
- property.propertyName);
- }
- if (property.value != v)
- return true;
- }
- return false;
-}
-
-void BuildGraphLoader::replaceFileDependencyWithArtifact(Artifact *filedep, Artifact *artifact)
-{
- QBS_ASSERT(filedep->project == artifact->project, return);
- if (m_logger.traceEnabled()) {
- m_logger.qbsTrace()
- << QString::fromLocal8Bit("[BG] replace file dependency '%1' "
- "with artifact of type '%2'")
- .arg(relativeArtifactFileName(filedep)).arg(artifact->artifactType);
- }
- foreach (const ResolvedProductPtr &product, filedep->project->products) {
- if (!product->buildData)
- continue;
- foreach (Artifact *artifactInProduct, product->buildData->artifacts) {
- if (artifactInProduct->children.contains(filedep)) {
- disconnect(artifactInProduct, filedep, m_logger);
- loggedConnect(artifactInProduct, artifact, m_logger);
- }
- artifactInProduct->fileDependencies.remove(filedep);
- }
- }
- filedep->topLevelProject()->buildData->dependencyArtifacts.remove(filedep);
- filedep->topLevelProject()->buildData->removeFromArtifactLookupTable(filedep);
- delete filedep;
-}
-
-void addTargetArtifacts(const ResolvedProductPtr &product,
- ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger)
-{
- foreach (const FileTag &fileTag, product->fileTags) {
- foreach (Artifact * const artifact, artifactsPerFileTag.value(fileTag)) {
- if (artifact->artifactType == Artifact::Generated)
- product->buildData->targetArtifacts += artifact;
- }
- }
- if (product->buildData->targetArtifacts.isEmpty()) {
- const QString msg = QString::fromLocal8Bit("No artifacts generated for product '%1'.");
- logger.qbsDebug() << msg.arg(product->name);
- }
-}
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/buildgraph/buildgraph.h b/src/lib/buildgraph/buildgraph.h
index b5712de7f..14df4e44e 100644
--- a/src/lib/buildgraph/buildgraph.h
+++ b/src/lib/buildgraph/buildgraph.h
@@ -33,14 +33,11 @@
#include "rulesapplicator.h"
#include <language/forward_decls.h>
-#include <logging/logger.h>
-#include <QProcessEnvironment>
#include <QScriptValue>
#include <QStringList>
namespace qbs {
-class SetupProjectParameters;
namespace Internal {
class Logger;
class ScriptEngine;
@@ -80,43 +77,6 @@ QStringList toStringList(const T &artifactContainer)
return l;
}
-class BuildGraphLoadResult
-{
-public:
- BuildGraphLoadResult() : discardLoadedProject(false) {}
-
- TopLevelProjectPtr newlyResolvedProject;
- TopLevelProjectPtr loadedProject;
- bool discardLoadedProject;
-};
-
-
-class BuildGraphLoader
-{
-public:
- BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger);
-
- BuildGraphLoadResult load(const SetupProjectParameters &parameters,
- const RulesEvaluationContextPtr &evalContext);
-
-private:
- void trackProjectChanges(const SetupProjectParameters &parameters,
- const QString &buildGraphFilePath,
- const TopLevelProjectPtr &restoredProject);
- void onProductRemoved(const ResolvedProductPtr &product);
- void onProductChanged(const ResolvedProductPtr &product,
- const ResolvedProductPtr &changedProduct);
- void removeArtifactAndExclusiveDependents(Artifact *artifact,
- ArtifactList *removedArtifacts = 0);
- bool checkForPropertyChanges(const TransformerPtr &restoredTrafo, const ResolvedProductPtr &freshProduct);
- void replaceFileDependencyWithArtifact(Artifact *filedep, Artifact *artifact);
-
- RulesEvaluationContextPtr m_evalContext;
- BuildGraphLoadResult m_result;
- Logger m_logger;
- QProcessEnvironment m_environment;
-};
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/buildgraph/buildgraph.pri b/src/lib/buildgraph/buildgraph.pri
index d33e53861..ad4aa0349 100644
--- a/src/lib/buildgraph/buildgraph.pri
+++ b/src/lib/buildgraph/buildgraph.pri
@@ -1,6 +1,7 @@
SOURCES += \
$$PWD/automoc.cpp\
$$PWD/buildgraph.cpp\
+ $$PWD/buildgraphloader.cpp \
$$PWD/executor.cpp\
$$PWD/executorjob.cpp\
$$PWD/rulegraph.cpp\
@@ -26,6 +27,7 @@ SOURCES += \
HEADERS += \
$$PWD/automoc.h\
$$PWD/buildgraph.h\
+ $$PWD/buildgraphloader.h \
$$PWD/executor.h\
$$PWD/executorjob.h\
$$PWD/rulegraph.h\
diff --git a/src/lib/buildgraph/buildgraphloader.cpp b/src/lib/buildgraph/buildgraphloader.cpp
new file mode 100644
index 000000000..eea9bbd73
--- /dev/null
+++ b/src/lib/buildgraph/buildgraphloader.cpp
@@ -0,0 +1,550 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#include "buildgraphloader.h"
+
+#include "artifact.h"
+#include "artifactlist.h"
+#include "buildgraph.h"
+#include "cycledetector.h"
+#include "productbuilddata.h"
+#include "projectbuilddata.h"
+#include "rulesevaluationcontext.h"
+#include "transformer.h"
+#include <language/language.h>
+#include <language/loader.h>
+#include <logging/translator.h>
+#include <tools/propertyfinder.h>
+#include <tools/qbsassert.h>
+#include <tools/setupprojectparameters.h>
+
+#include <QDir>
+#include <QFileInfo>
+
+namespace qbs {
+namespace Internal {
+
+BuildGraphLoader::BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger) :
+ m_logger(logger), m_environment(env)
+{
+}
+
+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;
+}
+
+static void restoreBackPointers(const ResolvedProjectPtr &project)
+{
+ foreach (const ResolvedProductPtr &product, project->products) {
+ product->project = project;
+ if (!product->buildData)
+ continue;
+ foreach (Artifact * const a, product->buildData->artifacts) {
+ a->project = project;
+ project->topLevelProject()->buildData->insertIntoArtifactLookupTable(a);
+ }
+ }
+
+ foreach (const ResolvedProjectPtr &subProject, project->subProjects) {
+ subProject->parentProject = project;
+ restoreBackPointers(subProject);
+ }
+}
+
+BuildGraphLoadResult BuildGraphLoader::load(const SetupProjectParameters &parameters,
+ const RulesEvaluationContextPtr &evalContext)
+{
+ m_result = BuildGraphLoadResult();
+ m_evalContext = evalContext;
+
+ const QString projectId = TopLevelProject::deriveId(parameters.buildConfigurationTree());
+ const QString buildDir
+ = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), projectId);
+ const QString buildGraphFilePath
+ = ProjectBuildData::deriveBuildGraphFilePath(buildDir, projectId);
+
+ PersistentPool pool(m_logger);
+ m_logger.qbsDebug() << "[BG] trying to load: " << buildGraphFilePath;
+ try {
+ pool.load(buildGraphFilePath);
+ } catch (const ErrorInfo &loadError) {
+ if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
+ throw loadError;
+ m_logger.qbsInfo() << loadError.toString();
+ return m_result;
+ }
+
+ if (!isConfigCompatible(parameters.buildConfigurationTree(), pool.headData().projectConfig)) {
+ const QString message = Tr::tr("Cannot use stored build graph at '%1':"
+ "Incompatible project configuration.").arg(buildGraphFilePath);
+ if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
+ throw ErrorInfo(message);
+ m_logger.qbsInfo() << message;
+ return m_result;
+ }
+
+ const TopLevelProjectPtr project = TopLevelProject::create();
+
+ // TODO: Store some meta data that will enable us to show actual progress (e.g. number of products).
+ evalContext->initializeObserver(Tr::tr("Restoring build graph from disk"), 1);
+
+ project->load(pool);
+ project->buildData->evaluationContext = evalContext;
+
+ if (QFileInfo(project->location.fileName()) != QFileInfo(parameters.projectFilePath())) {
+ QString errorMessage = Tr::tr("Stored build graph is for project file '%1', but "
+ "input file is '%2'. ")
+ .arg(QDir::toNativeSeparators(project->location.fileName()),
+ QDir::toNativeSeparators(parameters.projectFilePath()));
+ if (!parameters.ignoreDifferentProjectFilePath()) {
+ errorMessage += Tr::tr("Aborting.");
+ throw ErrorInfo(errorMessage);
+ }
+
+ // Okay, let's assume it's the same project anyway (the source dir might have moved).
+ errorMessage += Tr::tr("Ignoring.");
+ m_logger.qbsWarning() << errorMessage;
+ }
+
+ restoreBackPointers(project);
+ foreach (Artifact * const a, project->buildData->dependencyArtifacts)
+ a->project = project;
+
+ project->location = CodeLocation(parameters.projectFilePath(), project->location.line(),
+ project->location.column());
+ project->setBuildConfiguration(pool.headData().projectConfig);
+ project->buildDirectory = buildDir;
+ m_result.loadedProject = project;
+ evalContext->incrementProgressValue();
+
+ if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly)
+ return m_result;
+ QBS_CHECK(parameters.restoreBehavior() == SetupProjectParameters::RestoreAndTrackChanges);
+
+ trackProjectChanges(parameters, buildGraphFilePath, project);
+ return m_result;
+}
+
+static void exchangeDependencies(const ResolvedProductPtr &newProduct,
+ const QList<ResolvedProductPtr> &allRestoredProducts,
+ const QList<ResolvedProductPtr> &addedProducts)
+{
+ QSet<ResolvedProductPtr> newDependencies;
+ foreach (const ResolvedProductPtr &dependency, newProduct->dependencies) {
+ if (addedProducts.contains(dependency)) {
+ newDependencies << dependency;
+ exchangeDependencies(dependency, allRestoredProducts, addedProducts);
+ continue;
+ }
+ bool counterPartFound = false;
+ foreach (const ResolvedProductPtr &restoredProduct, allRestoredProducts) {
+ if (restoredProduct->name == dependency->name) {
+ newDependencies << restoredProduct;
+ counterPartFound = true;
+ break;
+ }
+ }
+ QBS_CHECK(counterPartFound);
+ }
+ newProduct->dependencies = newDependencies;
+}
+
+static void manipulateAddedProducts(const QList<ResolvedProjectPtr> &allRestoredProjects,
+ const QList<ResolvedProductPtr> &allRestoredProducts,
+ const QList<ResolvedProductPtr> &addedProducts)
+{
+ foreach (const ResolvedProductPtr &product, addedProducts) {
+ // Find the right existing (sub-)project to put the new product into.
+ bool projectFound = false;
+ foreach (const ResolvedProjectPtr &project, allRestoredProjects) {
+ if (project->location != product->project->location)
+ continue;
+ product->project = project;
+ project->products.append(product);
+ projectFound = true;
+ break;
+ }
+ QBS_CHECK(projectFound);
+
+ // Exchange dependencies that are not new with their counterparts from the restored project.
+ exchangeDependencies(product, allRestoredProducts, addedProducts);
+ }
+}
+
+// TODO: Pay more attention to project metadata such as name and location. If these change,
+// we must transfer them over to the old build graph.
+void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters &parameters,
+ const QString &buildGraphFilePath, const TopLevelProjectPtr &restoredProject)
+{
+ const FileInfo bgfi(buildGraphFilePath);
+ const QList<ResolvedProjectPtr> allRestoredProjects
+ = restoredProject->allSubProjects() << restoredProject;
+ bool projectFileChanged = false;
+ bool subProjectRemoved = false;
+ foreach (const ResolvedProjectConstPtr &p, allRestoredProjects) {
+ FileInfo fi(p->location.fileName());
+ if (!fi.exists()) {
+ subProjectRemoved = true;
+ break;
+ }
+ if (bgfi.lastModified() < fi.lastModified()) {
+ projectFileChanged = true;
+ break;
+ }
+ }
+ if (subProjectRemoved)
+ m_logger.qbsTrace() << "A sub-project was removed, must re-resolve project";
+ if (projectFileChanged)
+ m_logger.qbsTrace() << "A project file changed, must re-resolve project.";
+
+ bool environmentChanged = false;
+ for (QHash<QString, QString>::ConstIterator it = restoredProject->usedEnvironment.constBegin();
+ !environmentChanged && it != restoredProject->usedEnvironment.constEnd(); ++it) {
+ environmentChanged = m_environment.value(it.key()) != it.value();
+ }
+ if (environmentChanged)
+ m_logger.qbsTrace() << "A relevant environment variable changed, must re-resolve project.";
+
+ bool productRemoved = false;
+ const QList<ResolvedProductPtr> allRestoredProducts = restoredProject->allProducts();
+ QList<ResolvedProductPtr> changedProducts;
+ foreach (const ResolvedProductPtr &product, allRestoredProducts) {
+ const FileInfo pfi(product->location.fileName());
+ if (!pfi.exists()) {
+ productRemoved = true;
+ } else if (bgfi.lastModified() < pfi.lastModified()) {
+ changedProducts += product;
+ } else {
+ foreach (const GroupPtr &group, product->groups) {
+ if (!group->wildcards)
+ continue;
+ const QSet<QString> files
+ = group->wildcards->expandPatterns(group, product->sourceDirectory);
+ QSet<QString> wcFiles;
+ foreach (const SourceArtifactConstPtr &sourceArtifact, group->wildcards->files)
+ wcFiles += sourceArtifact->absoluteFilePath;
+ if (files == wcFiles)
+ continue;
+ changedProducts += product;
+ break;
+ }
+ }
+ }
+
+ if (!environmentChanged && !projectFileChanged && !subProjectRemoved && !productRemoved
+ && changedProducts.isEmpty()) {
+ return;
+ }
+
+ Loader ldr(m_evalContext->engine(), m_logger);
+ ldr.setSearchPaths(parameters.searchPaths());
+ m_result.newlyResolvedProject = ldr.loadProject(parameters);
+
+ QMap<QString, ResolvedProductPtr> freshProductsByName;
+ const QList<ResolvedProductPtr> allNewlyResolvedProducts
+ = m_result.newlyResolvedProject->allProducts();
+ foreach (const ResolvedProductPtr &cp, allNewlyResolvedProducts)
+ freshProductsByName.insert(cp->name, cp);
+
+ QSet<TransformerPtr> seenTransformers;
+ foreach (const ResolvedProductPtr &product, allRestoredProducts) {
+ if (!product->buildData)
+ continue;
+ foreach (Artifact *artifact, product->buildData->artifacts) {
+ if (!artifact->transformer || seenTransformers.contains(artifact->transformer))
+ continue;
+ seenTransformers.insert(artifact->transformer);
+ ResolvedProductPtr freshProduct = freshProductsByName.value(product->name);
+ if (freshProduct && checkForPropertyChanges(artifact->transformer, freshProduct)) {
+ m_result.discardLoadedProject = true;
+ return;
+ }
+ }
+ }
+
+ foreach (const ResolvedProductPtr &product, changedProducts) {
+ ResolvedProductPtr freshProduct = freshProductsByName.value(product->name);
+ if (!freshProduct)
+ continue;
+ onProductChanged(product, freshProduct);
+ if (m_result.discardLoadedProject)
+ return;
+ }
+
+ QSet<QString> oldProductNames, newProductNames;
+ foreach (const ResolvedProductConstPtr &product, allRestoredProducts)
+ oldProductNames += product->name;
+ foreach (const ResolvedProductConstPtr &product, allNewlyResolvedProducts)
+ newProductNames += product->name;
+
+ const QSet<QString> removedProductsNames = oldProductNames - newProductNames;
+ if (!removedProductsNames.isEmpty()) {
+ foreach (const ResolvedProductPtr &product, allRestoredProducts) {
+ if (removedProductsNames.contains(product->name))
+ onProductRemoved(product);
+ }
+ }
+
+ const QSet<QString> addedProductNames = newProductNames - oldProductNames;
+ QList<ResolvedProductPtr> addedProducts;
+ foreach (const QString &productName, addedProductNames) {
+ const ResolvedProductPtr &freshProduct = freshProductsByName.value(productName);
+ QBS_ASSERT(freshProduct, continue);
+ addedProducts.append(freshProduct);
+ }
+ if (!addedProducts.isEmpty()) {
+ manipulateAddedProducts(allRestoredProjects, allRestoredProducts, addedProducts);
+ BuildDataResolver bpr(m_logger);
+ bpr.resolveProductBuildDataForExistingProject(restoredProject, addedProducts);
+ }
+
+ CycleDetector(m_logger).visitProject(restoredProject);
+}
+
+void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product)
+{
+ m_logger.qbsDebug() << "[BG] product '" << product->name << "' removed.";
+
+ product->topLevelProject()->buildData->isDirty = true;
+ product->project->products.removeOne(product);
+
+ // delete all removed artifacts physically from the disk
+ if (product->buildData) {
+ foreach (Artifact *artifact, product->buildData->artifacts) {
+ artifact->disconnectAll(m_logger);
+ removeGeneratedArtifactFromDisk(artifact, m_logger);
+ }
+ }
+}
+
+void BuildGraphLoader::onProductChanged(const ResolvedProductPtr &product,
+ const ResolvedProductPtr &changedProduct)
+{
+ m_logger.qbsDebug() << "[BG] product '" << product->name << "' changed.";
+
+ ArtifactsPerFileTagMap artifactsPerFileTag;
+ QList<Artifact *> addedArtifacts;
+ ArtifactList artifactsToRemove;
+ QHash<QString, SourceArtifactConstPtr> oldArtifacts, newArtifacts;
+
+ const QList<SourceArtifactPtr> oldProductAllFiles = product->allEnabledFiles();
+ foreach (const SourceArtifactConstPtr &a, oldProductAllFiles)
+ oldArtifacts.insert(a->absoluteFilePath, a);
+ foreach (const SourceArtifactPtr &a, changedProduct->allEnabledFiles()) {
+ newArtifacts.insert(a->absoluteFilePath, a);
+ if (!oldArtifacts.contains(a->absoluteFilePath)) {
+ // artifact added
+ m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath
+ << "' added to product " << product->name;
+ Artifact *newArtifact = lookupArtifact(product, a->absoluteFilePath);
+ if (newArtifact) {
+ // User added a source file that was a generated artifact in the previous
+ // build, e.g. a C++ source file that was generated and now is a non-generated
+ // source file.
+ newArtifact->artifactType = Artifact::SourceFile;
+ } else {
+ newArtifact = createArtifact(product, a, m_logger);
+ foreach (Artifact *oldArtifact,
+ product->topLevelProject()->buildData->lookupArtifacts(newArtifact->filePath())) {
+ if (oldArtifact == newArtifact
+ || oldArtifact->artifactType != Artifact::FileDependency) {
+ // The source file already exists in another product.
+ continue;
+ }
+ // User added a source file that was recognized as file dependency in the
+ // previous build, e.g. a C++ header file.
+ replaceFileDependencyWithArtifact(oldArtifact, newArtifact);
+ }
+ }
+ addedArtifacts += newArtifact;
+ }
+ }
+
+ foreach (const SourceArtifactPtr &a, oldProductAllFiles) {
+ const SourceArtifactConstPtr changedArtifact = newArtifacts.value(a->absoluteFilePath);
+ if (!changedArtifact) {
+ // artifact removed
+ m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath
+ << "' removed from product " << product->name;
+ Artifact *artifact = lookupArtifact(product, a->absoluteFilePath);
+ QBS_CHECK(artifact);
+ removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove);
+ continue;
+ }
+ if (changedArtifact->fileTags != a->fileTags) {
+ // artifact's filetags have changed
+ m_logger.qbsDebug() << "[BG] filetags have changed for artifact '"
+ << a->absoluteFilePath << "' from " << a->fileTags << " to "
+ << changedArtifact->fileTags;
+ Artifact *artifact = lookupArtifact(product, a->absoluteFilePath);
+ QBS_CHECK(artifact);
+
+ // handle added filetags
+ foreach (const FileTag &addedFileTag, changedArtifact->fileTags - a->fileTags)
+ artifactsPerFileTag[addedFileTag] += artifact;
+
+ // handle removed filetags
+ foreach (const FileTag &removedFileTag, a->fileTags - changedArtifact->fileTags) {
+ artifact->fileTags -= removedFileTag;
+ foreach (Artifact *parent, artifact->parents) {
+ if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) {
+ // this parent has been created because of the removed filetag
+ removeArtifactAndExclusiveDependents(parent, &artifactsToRemove);
+ }
+ }
+ }
+ }
+ }
+
+ // Discard groups of the old product. Use the groups of the new one.
+ product->groups = changedProduct->groups;
+ product->properties = changedProduct->properties;
+
+ // apply rules for new artifacts
+ foreach (Artifact *artifact, addedArtifacts)
+ foreach (const FileTag &ft, artifact->fileTags)
+ artifactsPerFileTag[ft] += artifact;
+ RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules();
+
+ addTargetArtifacts(product, artifactsPerFileTag, m_logger);
+
+ // parents of removed artifacts must update their transformers
+ foreach (Artifact *removedArtifact, artifactsToRemove)
+ foreach (Artifact *parent, removedArtifact->parents)
+ product->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent;
+ product->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger);
+
+ // delete all removed artifacts physically from the disk
+ foreach (Artifact *artifact, artifactsToRemove) {
+ removeGeneratedArtifactFromDisk(artifact, m_logger);
+ delete artifact;
+ }
+}
+
+/**
+ * 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 BuildGraphLoader::removeArtifactAndExclusiveDependents(Artifact *artifact,
+ ArtifactList *removedArtifacts)
+{
+ if (removedArtifacts)
+ removedArtifacts->insert(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ bool removeParent = false;
+ disconnect(parent, artifact, m_logger);
+ if (parent->children.isEmpty()) {
+ removeParent = true;
+ } else if (parent->transformer) {
+ artifact->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent;
+ parent->transformer->inputs.remove(artifact);
+ removeParent = parent->transformer->inputs.isEmpty();
+ }
+ if (removeParent)
+ removeArtifactAndExclusiveDependents(parent, removedArtifacts);
+ }
+ artifact->topLevelProject()->buildData->removeArtifact(artifact, m_logger);
+}
+
+bool BuildGraphLoader::checkForPropertyChanges(const TransformerPtr &restoredTrafo,
+ const ResolvedProductPtr &freshProduct)
+{
+ PropertyFinder finder;
+ foreach (const Property &property, restoredTrafo->modulePropertiesUsedInPrepareScript) {
+ QVariant v;
+ if (property.kind == Property::PropertyInProduct) {
+ v = freshProduct->properties->value().value(property.propertyName);
+ } else if (property.value.type() == QVariant::List) {
+ v = finder.propertyValues(freshProduct->properties->value(), property.moduleName,
+ property.propertyName);
+ } else {
+ v = finder.propertyValue(freshProduct->properties->value(), property.moduleName,
+ property.propertyName);
+ }
+ if (property.value != v)
+ return true;
+ }
+ return false;
+}
+
+void BuildGraphLoader::replaceFileDependencyWithArtifact(Artifact *filedep, Artifact *artifact)
+{
+ QBS_ASSERT(filedep->project == artifact->project, return);
+ if (m_logger.traceEnabled()) {
+ m_logger.qbsTrace()
+ << QString::fromLocal8Bit("[BG] replace file dependency '%1' "
+ "with artifact of type '%2'")
+ .arg(relativeArtifactFileName(filedep)).arg(artifact->artifactType);
+ }
+ foreach (const ResolvedProductPtr &product, filedep->project->products) {
+ if (!product->buildData)
+ continue;
+ foreach (Artifact *artifactInProduct, product->buildData->artifacts) {
+ if (artifactInProduct->children.contains(filedep)) {
+ disconnect(artifactInProduct, filedep, m_logger);
+ loggedConnect(artifactInProduct, artifact, m_logger);
+ }
+ artifactInProduct->fileDependencies.remove(filedep);
+ }
+ }
+ filedep->topLevelProject()->buildData->dependencyArtifacts.remove(filedep);
+ filedep->topLevelProject()->buildData->removeFromArtifactLookupTable(filedep);
+ delete filedep;
+}
+
+void addTargetArtifacts(const ResolvedProductPtr &product,
+ ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger)
+{
+ foreach (const FileTag &fileTag, product->fileTags) {
+ foreach (Artifact * const artifact, artifactsPerFileTag.value(fileTag)) {
+ if (artifact->artifactType == Artifact::Generated)
+ product->buildData->targetArtifacts += artifact;
+ }
+ }
+ if (product->buildData->targetArtifacts.isEmpty()) {
+ const QString msg = QString::fromLocal8Bit("No artifacts generated for product '%1'.");
+ logger.qbsDebug() << msg.arg(product->name);
+ }
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/buildgraph/buildgraphloader.h b/src/lib/buildgraph/buildgraphloader.h
new file mode 100644
index 000000000..6bc0b625b
--- /dev/null
+++ b/src/lib/buildgraph/buildgraphloader.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#ifndef QBS_BUILDGRAPHLOADER_H
+#define QBS_BUILDGRAPHLOADER_H
+
+#include "forward_decls.h"
+
+#include <language/forward_decls.h>
+#include <logging/logger.h>
+
+#include <QProcessEnvironment>
+
+namespace qbs {
+class SetupProjectParameters;
+
+namespace Internal {
+class ArtifactList;
+
+class BuildGraphLoadResult
+{
+public:
+ BuildGraphLoadResult() : discardLoadedProject(false) {}
+
+ TopLevelProjectPtr newlyResolvedProject;
+ TopLevelProjectPtr loadedProject;
+ bool discardLoadedProject;
+};
+
+
+class BuildGraphLoader
+{
+public:
+ BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger);
+
+ BuildGraphLoadResult load(const SetupProjectParameters &parameters,
+ const RulesEvaluationContextPtr &evalContext);
+
+private:
+ void trackProjectChanges(const SetupProjectParameters &parameters,
+ const QString &buildGraphFilePath,
+ const TopLevelProjectPtr &restoredProject);
+ void onProductRemoved(const ResolvedProductPtr &product);
+ void onProductChanged(const ResolvedProductPtr &product,
+ const ResolvedProductPtr &changedProduct);
+ void removeArtifactAndExclusiveDependents(Artifact *artifact,
+ ArtifactList *removedArtifacts = 0);
+ bool checkForPropertyChanges(const TransformerPtr &restoredTrafo, const ResolvedProductPtr &freshProduct);
+ void replaceFileDependencyWithArtifact(Artifact *filedep, Artifact *artifact);
+
+ RulesEvaluationContextPtr m_evalContext;
+ BuildGraphLoadResult m_result;
+ Logger m_logger;
+ QProcessEnvironment m_environment;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard.