diff options
-rw-r--r-- | src/lib/corelib/language/moduleloader.cpp | 90 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleloader.h | 8 | ||||
-rw-r--r-- | src/lib/corelib/tools/stringconstants.h | 3 | ||||
-rw-r--r-- | tests/auto/language/testdata/invalid-overrides.qbs | 16 | ||||
-rw-r--r-- | tests/auto/language/tst_language.cpp | 57 | ||||
-rw-r--r-- | tests/auto/language/tst_language.h | 2 |
6 files changed, 166 insertions, 10 deletions
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index 27c5f5dc8..f81e8d44f 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -298,8 +298,10 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) break; } } - if (ok) + if (ok) { + collectNameFromOverride(key); continue; + } ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(key)); e.append(Tr::tr("Please use one of the following:")); e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value")); @@ -523,7 +525,10 @@ void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *p TopLevelProjectContext tlp; tlp.buildDirectory = buildDirectory; handleProject(loadResult, &tlp, projectItem, referencedFilePaths); + checkProjectNamesInOverrides(tlp); collectProductsByName(tlp); + checkProductNamesInOverrides(); + adjustDependenciesForMultiplexing(tlp); for (ProjectContext * const projectContext : qAsConst(tlp.projects)) { @@ -574,12 +579,15 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem)); overrideItemProperties(projectItem, StringConstants::projectPrefix(), m_parameters.overriddenValuesTree()); - const QString projectName = m_evaluator->stringValue(projectItem, - StringConstants::nameProperty()); - if (!projectName.isEmpty()) - overrideItemProperties(projectItem, QStringLiteral("projects.") + projectName, - m_parameters.overriddenValuesTree()); + projectContext.name = m_evaluator->stringValue(projectItem, + StringConstants::nameProperty()); + if (projectContext.name.isEmpty()) + projectContext.name = FileInfo::baseName(projectItem->location().filePath()); + overrideItemProperties(projectItem, + StringConstants::projectsOverridePrefix() + projectContext.name, + m_parameters.overriddenValuesTree()); if (!checkItemCondition(projectItem)) { + m_disabledProjects.insert(projectContext.name); delete p; return; } @@ -786,7 +794,7 @@ QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, I productName = FileInfo::completeBaseName(productItem->file()->filePath()); productItem->setProperty(nameKey, VariantValue::create(productName)); } - overrideItemProperties(productItem, QStringLiteral("products.") + productName, + overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName, m_parameters.overriddenValuesTree()); const MultiplexInfo &multiplexInfo = extractMultiplexInfo(productItem, qbsModule.item); @@ -1753,6 +1761,70 @@ void ModuleLoader::handleProfile(Item *profileItem) m_localProfiles.insert(profileName, values); } +void ModuleLoader::collectNameFromOverride(const QString &overrideString) +{ + static const auto extract = [](const QString &prefix, const QString &overrideString) { + if (!overrideString.startsWith(prefix)) + return QString(); + const int startPos = prefix.length(); + const int endPos = overrideString.lastIndexOf(StringConstants::dot()); + if (endPos == -1) + return QString(); + return overrideString.mid(startPos, endPos - startPos); + }; + const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString); + if (!projectName.isEmpty()) { + m_projectNamesUsedInOverrides.insert(projectName); + return; + } + const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString); + if (!productName.isEmpty()) { + m_productNamesUsedInOverrides.insert(productName.left( + productName.indexOf(StringConstants::dot()))); + return; + } +} + +void ModuleLoader::checkProjectNamesInOverrides(const ModuleLoader::TopLevelProjectContext &tlp) +{ + for (const QString &projectNameInOverride : m_projectNamesUsedInOverrides) { + if (m_disabledProjects.contains(projectNameInOverride)) + continue; + bool found = false; + for (const ProjectContext * const p : tlp.projects) { + if (p->name == projectNameInOverride) { + found = true; + break; + } + } + if (!found) { + handlePropertyError(Tr::tr("Unknown project '%1' in property override.") + .arg(projectNameInOverride), m_parameters, m_logger); + } + } +} + +void ModuleLoader::checkProductNamesInOverrides() +{ + for (const QString &productNameInOverride : m_productNamesUsedInOverrides) { + bool found = false; + for (auto it = m_productsByName.cbegin(); it != m_productsByName.cend(); ++it) { + // In an override string such as "a.b.c:d, we cannot tell whether we have a product + // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take + // care not to emit false positives here. + if (it->first == productNameInOverride + || it->first.startsWith(productNameInOverride + StringConstants::dot())) { + found = true; + break; + } + } + if (!found) { + handlePropertyError(Tr::tr("Unknown product '%1' in property override.") + .arg(productNameInOverride), m_parameters, m_logger); + } + } +} + void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject) { for (ProjectContext * const project : topLevelProject.projects) { @@ -2827,8 +2899,8 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *expor deepestModuleInstance->setPrototype(modulePrototype); const QString fullName = moduleName.toString(); const QString generalOverrideKey = QStringLiteral("modules.") + fullName; - const QString perProductOverrideKey = QStringLiteral("products.") + productContext->name - + QLatin1Char('.') + fullName; + const QString perProductOverrideKey = StringConstants::productsOverridePrefix() + + productContext->name + QLatin1Char('.') + fullName; for (Item *instance = moduleInstance; instance; instance = instance->prototype()) { overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree()); if (fullName == QStringLiteral("qbs")) diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h index 3dafd42de..8eafb54da 100644 --- a/src/lib/corelib/language/moduleloader.h +++ b/src/lib/corelib/language/moduleloader.h @@ -152,6 +152,7 @@ private: Item *item; Item *scope; + QString name; }; class ProjectContext; @@ -163,7 +164,6 @@ private: public: ProjectContext *project; ModuleLoaderResult::ProductInfo info; - QString name; QString profileName; QString multiplexConfigurationId; QString multiplexConfigIdForModulePrototypes; @@ -340,6 +340,9 @@ private: void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, QVariantMap &values); void handleProfile(Item *profileItem); + void collectNameFromOverride(const QString &overrideString); + void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp); + void checkProductNamesInOverrides(); ItemPool *m_pool; Logger &m_logger; @@ -378,6 +381,9 @@ private: Version m_qbsVersion; Item *m_tempScopeItem = nullptr; qint64 m_elapsedTimeProbes; + Set<QString> m_projectNamesUsedInOverrides; + Set<QString> m_productNamesUsedInOverrides; + Set<QString> m_disabledProjects; }; } // namespace Internal diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h index fca451b7f..004effb97 100644 --- a/src/lib/corelib/tools/stringconstants.h +++ b/src/lib/corelib/tools/stringconstants.h @@ -146,6 +146,9 @@ public: static const QString &projectPrefix() { return project(); } static const QString &productValue() { return product(); } + QBS_STRING_CONSTANT(projectsOverridePrefix, "projects.") + QBS_STRING_CONSTANT(productsOverridePrefix, "products.") + QBS_STRING_CONSTANT(baseVar, "base") static const QString &explicitlyDependsOnVar() { return explicitlyDependsOn(); } QBS_STRING_CONSTANT(inputVar, "input") diff --git a/tests/auto/language/testdata/invalid-overrides.qbs b/tests/auto/language/testdata/invalid-overrides.qbs new file mode 100644 index 000000000..93e6c34c8 --- /dev/null +++ b/tests/auto/language/testdata/invalid-overrides.qbs @@ -0,0 +1,16 @@ +import qbs + +Project { + name: "My.Project" + property bool x + + Product { + name: "MyProduct" + property bool x + } + + Product { + name: "MyOtherProduct" + Depends { name: "cpp" } + } +} diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp index 0eb466188..9020c4874 100644 --- a/tests/auto/language/tst_language.cpp +++ b/tests/auto/language/tst_language.cpp @@ -1307,6 +1307,63 @@ void TestLanguage::invalidBindingInDisabledItem() QVERIFY(!exceptionCaught); } +void TestLanguage::invalidOverrides() +{ + QFETCH(QString, key); + QFETCH(QString, expectedErrorMessage); + const bool successExpected = expectedErrorMessage.isEmpty(); + bool exceptionCaught = false; + try { + qbs::SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("invalid-overrides.qbs")); + params.setOverriddenValues(QVariantMap{std::make_pair(key, true)}); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + if (successExpected) + qDebug() << e.toString(); + else + QVERIFY2(e.toString().contains(expectedErrorMessage), qPrintable(e.toString())); + } + QEXPECT_FAIL("no such module in product", "not easily checkable", Continue); + QCOMPARE(!exceptionCaught, successExpected); +} + +void TestLanguage::invalidOverrides_data() +{ + QTest::addColumn<QString>("key"); + QTest::addColumn<QString>("expectedErrorMessage"); + + QTest::newRow("no such project") << "projects.myproject.x" + << QString("Unknown project 'myproject' in property override."); + QTest::newRow("no such project property") << "projects.My.Project.y" + << QString("Unknown property: projects.My.Project.y"); + QTest::newRow("valid project property override") << "projects.My.Project.x" << QString(); + QTest::newRow("no such product") << "products.myproduct.x" + << QString("Unknown product 'myproduct' in property override."); + QTest::newRow("no such product (with module)") << "products.myproduct.cpp.useRPaths" + << QString("Unknown product 'myproduct' in property override."); + QTest::newRow("no such product property") << "products.MyProduct.y" + << QString("Unknown property: products.MyProduct.y"); + QTest::newRow("valid product property override") << "products.MyProduct.x" << QString(); + + // This cannot be an error, because the semantics are "if some product in the project has + // such a module, then set that property", and the code that does the property overrides + // does not have a global view. + QTest::newRow("no such module") << "modules.blubb.x" << QString(); + + QTest::newRow("no such module in product") << "products.MyProduct.cpp.useRPaths" + << QString("Invalid module 'cpp' in property override."); + QTest::newRow("no such module property") << "modules.cpp.blubb" + << QString("Unknown property: modules.cpp.blubb"); + QTest::newRow("no such module property (per product)") << "products.MyOtherProduct.cpp.blubb" + << QString("Unknown property: products.MyOtherProduct.cpp.blubb"); + QTest::newRow("valid per-product module property override") + << "products.MyOtherProduct.cpp.useRPaths" << QString(); +} + class JSSourceValueCreator { FileContextPtr m_fileContext; diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h index d12fc898a..edbebef5e 100644 --- a/tests/auto/language/tst_language.h +++ b/tests/auto/language/tst_language.h @@ -112,6 +112,8 @@ private slots: void idUniqueness(); void importCollection(); void invalidBindingInDisabledItem(); + void invalidOverrides(); + void invalidOverrides_data(); void itemPrototype(); void itemScope(); void jsExtensions(); |