aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2017-06-28 14:20:45 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2017-06-30 07:49:23 +0000
commit499893be59218fd4f563f031e270207f594cc4de (patch)
treefaf7f0683d90ff4b1a778f09a558a5e8ea47b405
parent7d0d9705ba4fb8e9813bbf425c10596d306f53c2 (diff)
Introduce Rule.requiresInputs
This supports the use case of a rule that wants to create its output even if no artifacts match the declared inputs. We have encountered it several times in practice. [ChangeLog] Introduced new property Rule.requiresInputs Change-Id: If42ab347fb8e47b9117ac48fe3ce1b228566e0d8 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--doc/reference/items/language/rule.qdoc8
-rw-r--r--src/lib/corelib/buildgraph/rulenode.cpp4
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp4
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp3
-rw-r--r--src/lib/corelib/language/language.cpp5
-rw-r--r--src/lib/corelib/language/language.h3
-rw-r--r--src/lib/corelib/language/projectresolver.cpp31
-rw-r--r--src/lib/corelib/language/projectresolver.h1
-rw-r--r--src/lib/qtprofilesetup/templates/qml.qbs22
-rw-r--r--tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp0
-rw-r--r--tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp0
-rw-r--r--tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp0
-rw-r--r--tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs42
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp19
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
15 files changed, 111 insertions, 32 deletions
diff --git a/doc/reference/items/language/rule.qdoc b/doc/reference/items/language/rule.qdoc
index eff9a95ff..c089f19d3 100644
--- a/doc/reference/items/language/rule.qdoc
+++ b/doc/reference/items/language/rule.qdoc
@@ -282,6 +282,14 @@
The argument \c{input} is \c{undefined} if there's more than one input artifact for this
rule. Similarly, \c{output} is only defined if there's exactly one output artifact.
\row
+ \li requiresInputs
+ \li bool
+ \li \c true if the rule declares any inputs, \c false otherwise
+ \li Specifies whether a rule's commands should be created even if no inputs are available.
+ Enabling this property can be useful in cases where you potentially have input files,
+ but it is possible that there are none and you want to create the output file in
+ any case.
+ \row
\li alwaysRun
\li bool
\li false
diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp
index 7b7ac7899..d4558787d 100644
--- a/src/lib/corelib/buildgraph/rulenode.cpp
+++ b/src/lib/corelib/buildgraph/rulenode.cpp
@@ -83,7 +83,7 @@ void RuleNode::apply(const Logger &logger, const ArtifactSet &changedInputs,
const ArtifactSet addedInputs = allCompatibleInputs - m_oldInputArtifacts;
const ArtifactSet removedInputs = m_oldInputArtifacts - allCompatibleInputs;
result->upToDate = changedInputs.isEmpty() && addedInputs.isEmpty() && removedInputs.isEmpty()
- && m_rule->requiresInputs();
+ && m_rule->declaresInputs() && m_rule->requiresInputs;
if (logger.traceEnabled()) {
logger.qbsTrace()
@@ -133,7 +133,7 @@ void RuleNode::apply(const Logger &logger, const ArtifactSet &changedInputs,
}
RulesApplicator::handleRemovedRuleOutputs(inputs, outputArtifactsToRemove, logger);
}
- if (!inputs.isEmpty() || !m_rule->requiresInputs()) {
+ if (!inputs.isEmpty() || !m_rule->declaresInputs() || !m_rule->requiresInputs) {
RulesApplicator applicator(product.lock(), logger);
applicator.applyRule(m_rule, inputs);
result->createdNodes = applicator.createdArtifacts();
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp
index 08a778850..b10ae67b9 100644
--- a/src/lib/corelib/buildgraph/rulesapplicator.cpp
+++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp
@@ -84,7 +84,7 @@ RulesApplicator::~RulesApplicator()
void RulesApplicator::applyRule(const RuleConstPtr &rule, const ArtifactSet &inputArtifacts)
{
- if (inputArtifacts.isEmpty() && rule->requiresInputs())
+ if (inputArtifacts.isEmpty() && rule->declaresInputs() && rule->requiresInputs)
return;
m_createdArtifacts.clear();
@@ -339,7 +339,7 @@ Artifact *RulesApplicator::createOutputArtifact(const QString &filePath, const F
(*inputArtifacts.cbegin())->filePath()));
throw error;
}
- if (m_rule->requiresInputs())
+ if (m_rule->declaresInputs())
outputArtifact->clearTimestamp();
m_invalidatedArtifacts += outputArtifact;
} else {
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index 009e80ed1..bb9b79ada 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -421,6 +421,9 @@ void BuiltinDeclarations::addRuleItem()
PropertyDeclaration decl(QLatin1String("multiplex"), PropertyDeclaration::Boolean);
decl.setInitialValueSource(QLatin1String("false"));
item << decl;
+ PropertyDeclaration requiresInputsDecl(QLatin1String("requiresInputs"),
+ PropertyDeclaration::Boolean);
+ item << requiresInputsDecl;
item << PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String);
item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::StringList);
item << PropertyDeclaration(QLatin1String("outputFileTags"), PropertyDeclaration::StringList);
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index f9c469032..c5aa34ae1 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -403,7 +403,7 @@ bool Rule::isDynamic() const
return outputArtifactsScript->isValid();
}
-bool Rule::requiresInputs() const
+bool Rule::declaresInputs() const
{
return !inputs.isEmpty() || !inputsFromDependencies.isEmpty();
}
@@ -421,6 +421,7 @@ void Rule::load(PersistentPool &pool)
pool.load(inputsFromDependencies);
pool.load(explicitlyDependsOn);
pool.load(multiplex);
+ pool.load(requiresInputs);
pool.load(alwaysRun);
pool.load(artifacts);
}
@@ -438,6 +439,7 @@ void Rule::store(PersistentPool &pool) const
pool.store(inputsFromDependencies);
pool.store(explicitlyDependsOn);
pool.store(multiplex);
+ pool.store(requiresInputs);
pool.store(alwaysRun);
pool.store(artifacts);
}
@@ -1224,6 +1226,7 @@ bool operator==(const Rule &r1, const Rule &r2)
&& r1.inputsFromDependencies == r2.inputsFromDependencies
&& r1.explicitlyDependsOn == r2.explicitlyDependsOn
&& r1.multiplex == r2.multiplex
+ && r1.requiresInputs == r2.requiresInputs
&& r1.alwaysRun == r2.alwaysRun;
}
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index 583b585e9..d65a12536 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -349,6 +349,7 @@ public:
FileTags inputsFromDependencies;
FileTags explicitlyDependsOn;
bool multiplex;
+ bool requiresInputs;
QList<RuleArtifactPtr> artifacts; // unused, if outputFileTags/outputArtifactsScript is non-empty
bool alwaysRun;
@@ -360,7 +361,7 @@ public:
FileTags staticOutputFileTags() const;
FileTags collectedOutputFileTags() const;
bool isDynamic() const;
- bool requiresInputs() const;
+ bool declaresInputs() const;
private:
Rule() : multiplex(false), alwaysRun(false), ruleGraphId(-1) {}
diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp
index f4b20a412..0b81f6f4e 100644
--- a/src/lib/corelib/language/projectresolver.cpp
+++ b/src/lib/corelib/language/projectresolver.cpp
@@ -836,6 +836,11 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
rule->inputs = m_evaluator->fileTagsValue(item, QLatin1String("inputs"));
rule->inputsFromDependencies
= m_evaluator->fileTagsValue(item, QLatin1String("inputsFromDependencies"));
+ bool requiresInputsSet = false;
+ rule->requiresInputs = m_evaluator->boolValue(item, QLatin1String("requiresInputs"), true,
+ &requiresInputsSet);
+ if (!requiresInputsSet)
+ rule->requiresInputs = rule->declaresInputs();
rule->auxiliaryInputs
= m_evaluator->fileTagsValue(item, QLatin1String("auxiliaryInputs"));
rule->excludedAuxiliaryInputs
@@ -843,12 +848,19 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
rule->explicitlyDependsOn
= m_evaluator->fileTagsValue(item, QLatin1String("explicitlyDependsOn"));
rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule;
- if (!rule->multiplex && !rule->requiresInputs()) {
- const QString message = Tr::tr("Rule has no inputs, but is not a multiplex rule.");
- ErrorInfo error(message, item->location());
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw error;
- m_logger.printWarning(error);
+ if (!rule->multiplex && !rule->declaresInputs()) {
+ handleError(ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."),
+ item->location()));
+ return;
+ }
+ if (!rule->multiplex && !rule->requiresInputs) {
+ handleError(ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule.")
+ , item->location()));
+ return;
+ }
+ if (!rule->declaresInputs() && rule->requiresInputs) {
+ handleError(ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule "
+ "does not declare any input tags."), item->location()));
return;
}
if (m_productContext)
@@ -1059,6 +1071,13 @@ void ProjectResolver::printProfilingInfo()
.arg(elapsedTimeString(m_elapsedTimeGroups));
}
+void ProjectResolver::handleError(const ErrorInfo &error)
+{
+ if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
+ throw error;
+ m_logger.printWarning(error);
+}
+
static bool hasDependencyCycle(Set<ResolvedProduct *> *checked,
Set<ResolvedProduct *> *branch,
const ResolvedProductPtr &product,
diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h
index 2b46c2093..f25db0651 100644
--- a/src/lib/corelib/language/projectresolver.h
+++ b/src/lib/corelib/language/projectresolver.h
@@ -146,6 +146,7 @@ private:
static void matchArtifactProperties(const ResolvedProductPtr &product,
const QList<SourceArtifactPtr> &artifacts);
void printProfilingInfo();
+ void handleError(const ErrorInfo &error);
Evaluator *m_evaluator;
Logger &m_logger;
diff --git a/src/lib/qtprofilesetup/templates/qml.qbs b/src/lib/qtprofilesetup/templates/qml.qbs
index 8a5a1b9f8..0f3a9cb77 100644
--- a/src/lib/qtprofilesetup/templates/qml.qbs
+++ b/src/lib/qtprofilesetup/templates/qml.qbs
@@ -40,29 +40,11 @@ QtModule {
fileTags: ["qt.qml.qml"]
}
- // This is needed for the case that there are no QML files in the project.
Rule {
condition: isStaticLibrary
multiplex: true
- Artifact {
- filePath: "qml_plugin_import.dummy"
- fileTags: ["qt.qml.import_dummy"]
- }
- prepare: {
- var cmd = new JavaScriptCommand();
- cmd.silent = true;
- cmd.sourceCode = function() {
- var f = new TextFile(output.filePath, TextFile.WriteOnly);
- f.close();
- }
- return [cmd];
- }
- }
-
- Rule {
- condition: isStaticLibrary
- multiplex: true
- inputs: ["qt.qml.qml", "qt.qml.import_dummy"]
+ requiresInputs: false
+ inputs: ["qt.qml.qml"]
outputFileTags: ["cpp", "qt.qml.pluginlist"]
outputArtifacts: {
var list = [];
diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp
diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp
diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp
diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs
new file mode 100644
index 000000000..6719bc69d
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs
@@ -0,0 +1,42 @@
+import qbs
+import qbs.TextFile
+
+Product {
+ name: "p"
+ type: ["p.out"]
+
+ property bool enableTagger
+
+ FileTagger {
+ condition: enableTagger
+ patterns: ["*.inp"]
+ fileTags: ["p.in"]
+ }
+
+ Rule {
+ multiplex: true
+ requiresInputs: false
+ inputs: ["p.in"]
+ Artifact {
+ filePath: "output.txt"
+ fileTags: ["p.out"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "Generating " + output.fileName;
+ cmd.sourceCode = function() {
+ var f = new TextFile(output.filePath, TextFile.WriteOnly);
+ f.write('(');
+ var inputsList = inputs["p.in"];
+ if (inputsList) {
+ for (var i = 0; i < inputsList.length; ++i)
+ f.write(inputsList[i].fileName + ',');
+ }
+ f.write(')');
+ };
+ return [cmd];
+ }
+ }
+
+ files: ["a.inp", "b.inp", "c.inp"]
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index 0bdb9c81f..a7371c8a6 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -1598,6 +1598,25 @@ void TestBlackbox::ruleWithNoInputs()
QVERIFY2(m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
}
+void TestBlackbox::ruleWithNonRequiredInputs()
+{
+ QDir::setCurrent(testDataDir + "/rule-with-non-required-inputs");
+ QbsRunParameters params("build", {"products.p.enableTagger:false"});
+ QCOMPARE(runQbs(params), 0);
+ QFile outFile(relativeProductBuildDir("p") + "/output.txt");
+ QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString()));
+ QByteArray output = outFile.readAll();
+ QCOMPARE(output, QByteArray("()"));
+ outFile.close();
+ params.command = "resolve";
+ params.arguments = QStringList({"products.p.enableTagger:true"});
+ QCOMPARE(runQbs(params), 0);
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString()));
+ output = outFile.readAll();
+ QCOMPARE(output, QByteArray("(a.inp,b.inp,c.inp,)"));
+}
+
void TestBlackbox::smartRelinking()
{
QDir::setCurrent(testDataDir + "/smart-relinking");
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 83d8420e4..77f5762fb 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -156,6 +156,7 @@ private slots:
void ruleConditions();
void ruleCycle();
void ruleWithNoInputs();
+ void ruleWithNonRequiredInputs();
void smartRelinking();
void smartRelinking_data();
void soVersion();