summaryrefslogtreecommitdiff
path: root/src/lib/buildgraph/rulesapplicator.cpp
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2012-11-29 17:55:50 +0100
committerChristian Kandeler <christian.kandeler@digia.com>2012-12-04 11:49:46 +0100
commitc552cb2ab5c3be9d2c6209b076a71c9e37209c29 (patch)
treeddfb4e866597e4856b52ba0a9c7b10e023418df1 /src/lib/buildgraph/rulesapplicator.cpp
parenta1d3d727f53874451bb07e66f275969d9b0e9b02 (diff)
downloadqbs-c552cb2ab5c3be9d2c6209b076a71c9e37209c29.tar.gz
Split up buildgraph.h and buildgraph.cpp.
Move things to the appropriate places. Change-Id: I2f201ccdf9c465a8c3201e229629b756cc258495 Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
Diffstat (limited to 'src/lib/buildgraph/rulesapplicator.cpp')
-rw-r--r--src/lib/buildgraph/rulesapplicator.cpp310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/lib/buildgraph/rulesapplicator.cpp b/src/lib/buildgraph/rulesapplicator.cpp
new file mode 100644
index 000000000..bd19b1242
--- /dev/null
+++ b/src/lib/buildgraph/rulesapplicator.cpp
@@ -0,0 +1,310 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 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 "rulesapplicator.h"
+
+#include "artifact.h"
+#include "buildgraph.h"
+#include "buildproduct.h"
+#include "buildproject.h"
+#include "rulesevaluationcontext.h"
+#include "transformer.h"
+#include <language/language.h>
+#include <language/scriptengine.h>
+#include <logging/logger.h>
+#include <logging/translator.h>
+#include <tools/error.h>
+#include <tools/scripttools.h>
+
+#include <QDir>
+
+namespace qbs {
+namespace Internal {
+
+RulesApplicator::RulesApplicator(BuildProduct *product, ArtifactsPerFileTagMap &artifactsPerFileTag)
+ : m_buildProduct(product)
+ , m_artifactsPerFileTag(artifactsPerFileTag)
+{
+}
+
+void RulesApplicator::applyAllRules()
+{
+ RulesEvaluationContext::Scope s(m_buildProduct->project->evaluationContext());
+ foreach (const RuleConstPtr &rule, m_buildProduct->topSortedRules())
+ applyRule(rule);
+}
+
+void RulesApplicator::applyRule(const RuleConstPtr &rule)
+{
+ m_rule = rule;
+ BuildGraph::setupScriptEngineForProduct(engine(), m_buildProduct->rProduct, m_rule, scope());
+ Q_ASSERT_X(scope().property("product").strictlyEquals(engine()->evaluate("product")),
+ "BG", "Product object is not in current scope.");
+
+ ArtifactList inputArtifacts;
+ foreach (const QString &fileTag, m_rule->inputs)
+ inputArtifacts.unite(m_artifactsPerFileTag.value(fileTag));
+ if (m_rule->multiplex) { // apply the rule once for a set of inputs
+ if (!inputArtifacts.isEmpty())
+ doApply(inputArtifacts);
+ } else { // apply the rule once for each input
+ ArtifactList lst;
+ foreach (Artifact * const inputArtifact, inputArtifacts) {
+ setupScriptEngineForArtifact(inputArtifact);
+ lst += inputArtifact;
+ doApply(lst);
+ lst.clear();
+ }
+ }
+}
+
+void RulesApplicator::doApply(const ArtifactList &inputArtifacts)
+{
+ evalContext()->checkForCancelation();
+
+ if (qbsLogLevel(LoggerDebug))
+ qbsDebug() << "[BG] apply rule " << m_rule->toString() << " "
+ << BuildGraph::toStringList(inputArtifacts).join(",\n ");
+
+ QList<QPair<const RuleArtifact *, Artifact *> > ruleArtifactArtifactMap;
+ QList<Artifact *> outputArtifacts;
+
+ ArtifactList usingArtifacts;
+ if (!m_rule->usings.isEmpty()) {
+ const QSet<QString> usingsFileTags = m_rule->usings.toSet();
+ foreach (const BuildProductPtr &dep, m_buildProduct->dependencies) {
+ ArtifactList artifactsToCheck;
+ foreach (Artifact *targetArtifact, dep->targetArtifacts)
+ artifactsToCheck.unite(targetArtifact->transformer->outputs);
+ foreach (Artifact *artifact, artifactsToCheck) {
+ QSet<QString> matchingFileTags = artifact->fileTags;
+ matchingFileTags.intersect(usingsFileTags);
+ if (!matchingFileTags.isEmpty())
+ usingArtifacts.insert(artifact);
+ }
+ }
+ }
+
+ m_transformer.clear();
+ // create the output artifacts from the set of input artifacts
+ foreach (const RuleArtifactConstPtr &ruleArtifact, m_rule->artifacts) {
+ Artifact * const outputArtifact = createOutputArtifact(ruleArtifact, inputArtifacts);
+ outputArtifacts << outputArtifact;
+ ruleArtifactArtifactMap << qMakePair(ruleArtifact.data(), outputArtifact);
+ }
+
+ foreach (Artifact *outputArtifact, outputArtifacts) {
+ // insert the output artifacts into the pool of artifacts
+ foreach (const QString &fileTag, outputArtifact->fileTags)
+ m_artifactsPerFileTag[fileTag].insert(outputArtifact);
+
+ // connect artifacts that match the file tags in explicitlyDependsOn
+ foreach (const QString &fileTag, m_rule->explicitlyDependsOn)
+ foreach (Artifact *dependency, m_artifactsPerFileTag.value(fileTag))
+ BuildGraph::loggedConnect(outputArtifact, dependency);
+
+ // Transformer setup
+ for (ArtifactList::const_iterator it = usingArtifacts.constBegin();
+ it != usingArtifacts.constEnd(); ++it)
+ {
+ Artifact *dep = *it;
+ BuildGraph::loggedConnect(outputArtifact, dep);
+ m_transformer->inputs.insert(dep);
+ }
+ m_transformer->outputs.insert(outputArtifact);
+
+ m_buildProduct->project->removeFromArtifactsThatMustGetNewTransformers(outputArtifact);
+ }
+
+ m_transformer->setupInputs(engine(), scope());
+
+ // change the transformer outputs according to the bindings in Artifact
+ QScriptValue scriptValue;
+ for (int i = ruleArtifactArtifactMap.count(); --i >= 0;) {
+ const RuleArtifact *ra = ruleArtifactArtifactMap.at(i).first;
+ if (ra->bindings.isEmpty())
+ continue;
+
+ // expose attributes of this artifact
+ Artifact *outputArtifact = ruleArtifactArtifactMap.at(i).second;
+ outputArtifact->properties = outputArtifact->properties->clone();
+
+ scope().setProperty("fileName", engine()->toScriptValue(outputArtifact->filePath()));
+ scope().setProperty("fileTags", toScriptValue(engine(), outputArtifact->fileTags));
+
+ QVariantMap artifactModulesCfg = outputArtifact->properties->value().value("modules").toMap();
+ for (int i=0; i < ra->bindings.count(); ++i) {
+ const RuleArtifact::Binding &binding = ra->bindings.at(i);
+ scriptValue = engine()->evaluate(binding.code);
+ if (scriptValue.isError()) {
+ QString msg = QLatin1String("evaluating rule binding '%1': %2");
+ throw Error(msg.arg(binding.name.join(QLatin1String(".")), scriptValue.toString()), binding.location);
+ }
+ setConfigProperty(artifactModulesCfg, binding.name, scriptValue.toVariant());
+ }
+ QVariantMap outputArtifactConfig = outputArtifact->properties->value();
+ outputArtifactConfig.insert("modules", artifactModulesCfg);
+ outputArtifact->properties->setValue(outputArtifactConfig);
+ }
+
+ m_transformer->setupOutputs(engine(), scope());
+ m_transformer->createCommands(m_rule->script, engine());
+ if (m_transformer->commands.isEmpty())
+ throw Error(QString("There's a rule without commands: %1.").arg(m_rule->toString()), m_rule->script->location);
+}
+
+void RulesApplicator::setupScriptEngineForArtifact(Artifact *artifact)
+{
+ QString inFileName = artifact->fileName();
+ QString inBaseName = FileInfo::baseName(artifact->filePath());
+ QString inCompleteBaseName = FileInfo::completeBaseName(artifact->filePath());
+
+ QString basedir;
+ if (artifact->artifactType == Artifact::SourceFile) {
+ QDir sourceDir(m_buildProduct->rProduct->sourceDirectory);
+ basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath()));
+ } else {
+ QDir buildDir(m_buildProduct->project->resolvedProject()->buildDirectory);
+ basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath()));
+ }
+
+ QScriptValue modulesScriptValue = artifact->properties->toScriptValue(engine());
+ modulesScriptValue = modulesScriptValue.property("modules");
+
+ // expose per file properties we want to use in an Artifact within a Rule
+ QScriptValue scriptValue = engine()->newObject();
+ scriptValue.setProperty("fileName", inFileName);
+ scriptValue.setProperty("baseName", inBaseName);
+ scriptValue.setProperty("completeBaseName", inCompleteBaseName);
+ scriptValue.setProperty("baseDir", basedir);
+ scriptValue.setProperty("modules", modulesScriptValue);
+
+ scope().setProperty("input", scriptValue);
+ Q_ASSERT_X(scriptValue.strictlyEquals(engine()->evaluate("input")),
+ "BG", "The input object is not in current scope.");
+}
+
+Artifact *RulesApplicator::createOutputArtifact(const RuleArtifactConstPtr &ruleArtifact,
+ const ArtifactList &inputArtifacts)
+{
+ QScriptValue scriptValue = engine()->evaluate(ruleArtifact->fileName);
+ if (scriptValue.isError() || engine()->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);
+
+ Artifact *outputArtifact = m_buildProduct->lookupArtifact(outputPath);
+ if (outputArtifact) {
+ if (outputArtifact->transformer && outputArtifact->transformer != m_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(BuildGraph::fileName(outputArtifact)));
+ m_transformer = outputArtifact->transformer;
+ m_transformer->inputs.unite(inputArtifacts);
+
+ if (m_transformer->inputs.count() > 1 && !m_rule->multiplex) {
+ QString th = "[" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+ QString e = Tr::tr("Conflicting rules for producing %1 %2 \n").arg(outputArtifact->filePath(), th);
+ th = "[" + m_rule->inputs.join(", ")
+ + "] -> [" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]";
+
+ e += QString(" while trying to apply: %1:%2:%3 %4\n")
+ .arg(m_rule->script->location.fileName)
+ .arg(m_rule->script->location.line)
+ .arg(m_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(m_buildProduct->project);
+ outputArtifact->artifactType = Artifact::Generated;
+ outputArtifact->setFilePath(outputPath);
+ outputArtifact->fileTags = ruleArtifact->fileTags.toSet();
+ outputArtifact->alwaysUpdated = ruleArtifact->alwaysUpdated;
+ m_buildProduct->insertArtifact(outputArtifact);
+ }
+
+ if (outputArtifact->fileTags.isEmpty())
+ outputArtifact->fileTags = m_buildProduct->rProduct->fileTagsForFileName(outputArtifact->fileName());
+
+ if (m_rule->multiplex)
+ outputArtifact->properties = m_buildProduct->rProduct->properties;
+ else
+ outputArtifact->properties= (*inputArtifacts.constBegin())->properties;
+
+ foreach (Artifact *inputArtifact, inputArtifacts) {
+ Q_ASSERT(outputArtifact != inputArtifact);
+ BuildGraph::loggedConnect(outputArtifact, inputArtifact);
+ }
+
+ // create transformer if not already done so
+ if (!m_transformer) {
+ m_transformer = Transformer::create();
+ m_transformer->rule = m_rule;
+ m_transformer->inputs = inputArtifacts;
+ }
+ outputArtifact->transformer = m_transformer;
+
+ return outputArtifact;
+}
+
+QString RulesApplicator::resolveOutPath(const QString &path) const
+{
+ QString buildDir = m_buildProduct->project->resolvedProject()->buildDirectory;
+ QString result = FileInfo::resolvePath(buildDir, path);
+ result = QDir::cleanPath(result);
+ return result;
+}
+
+RulesEvaluationContext *RulesApplicator::evalContext() const
+{
+ return m_buildProduct->project->evaluationContext();
+}
+
+ScriptEngine *RulesApplicator::engine() const
+{
+ return evalContext()->engine();
+}
+
+QScriptValue RulesApplicator::scope() const
+{
+ return evalContext()->scope();
+}
+
+} // namespace Internal
+} // namespace qbs