aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/corelib/language/moduleloader.cpp90
-rw-r--r--src/lib/corelib/language/moduleloader.h8
-rw-r--r--src/lib/corelib/tools/stringconstants.h3
-rw-r--r--tests/auto/language/testdata/invalid-overrides.qbs16
-rw-r--r--tests/auto/language/tst_language.cpp57
-rw-r--r--tests/auto/language/tst_language.h2
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 &parameters)
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();