diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2017-06-28 14:20:45 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2017-06-30 07:49:23 +0000 |
commit | 499893be59218fd4f563f031e270207f594cc4de (patch) | |
tree | faf7f0683d90ff4b1a778f09a558a5e8ea47b405 | |
parent | 7d0d9705ba4fb8e9813bbf425c10596d306f53c2 (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.qdoc | 8 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/rulenode.cpp | 4 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/rulesapplicator.cpp | 4 | ||||
-rw-r--r-- | src/lib/corelib/language/builtindeclarations.cpp | 3 | ||||
-rw-r--r-- | src/lib/corelib/language/language.cpp | 5 | ||||
-rw-r--r-- | src/lib/corelib/language/language.h | 3 | ||||
-rw-r--r-- | src/lib/corelib/language/projectresolver.cpp | 31 | ||||
-rw-r--r-- | src/lib/corelib/language/projectresolver.h | 1 | ||||
-rw-r--r-- | src/lib/qtprofilesetup/templates/qml.qbs | 22 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp | 0 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp | 0 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp | 0 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs | 42 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 19 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.h | 1 |
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(); |