aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changelogs/changes-1.10.1.md1
-rw-r--r--src/lib/corelib/language/item.h1
-rw-r--r--src/lib/corelib/language/moduleloader.cpp62
-rw-r--r--tests/auto/language/testdata/qbs1275.qbs42
-rw-r--r--tests/auto/language/tst_language.cpp16
-rw-r--r--tests/auto/language/tst_language.h1
6 files changed, 104 insertions, 19 deletions
diff --git a/changelogs/changes-1.10.1.md b/changelogs/changes-1.10.1.md
index 213251f49..45b0cfdb3 100644
--- a/changelogs/changes-1.10.1.md
+++ b/changelogs/changes-1.10.1.md
@@ -1,4 +1,5 @@
# Important bugfixes
+* Fix assertion on project loading (QBS-1275).
* Fix crash when the "original" value is misused (QBS-1255).
* Fix qtquickcompiler support for qml files in subdirectories (QBS-1261).
* Fix GCC support for "bare metal" systems (QBS-1263, QBS-1265).
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index b86c50a3c..97ca3ad91 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -164,7 +164,6 @@ private:
};
inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; }
-inline bool operator==(const Item::Module &m1, const Item::Module &m2) { return m1.item == m2.item; }
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp
index 81d1088b5..35d0636b6 100644
--- a/src/lib/corelib/language/moduleloader.cpp
+++ b/src/lib/corelib/language/moduleloader.cpp
@@ -1000,17 +1000,42 @@ void ModuleLoader::setupProductDependencies(ProductContext *productContext)
productContext->project->result->productInfos.insert(item, productContext->info);
}
-// Non-dependencies first.
+// Leaf modules first.
static void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules)
{
- if (contains(modules, parentModule))
+ if (std::find_if(modules.cbegin(), modules.cend(),
+ [parentModule](const Item::Module &m) { return m.name == parentModule.name;})
+ != modules.cend()) {
return;
+ }
for (const Item::Module &dep : parentModule.item->modules())
createSortedModuleList(dep, modules);
modules.push_back(parentModule);
return;
}
+static Item::Modules modulesSortedByDependency(const Item *productItem)
+{
+ QBS_CHECK(productItem->type() == ItemType::Product);
+ Item::Modules sortedModules;
+ const Item::Modules &unsortedModules = productItem->modules();
+ for (const Item::Module &module : unsortedModules)
+ createSortedModuleList(module, sortedModules);
+ QBS_CHECK(sortedModules.size() == unsortedModules.size());
+
+ // Make sure the top-level items stay the same.
+ for (Item::Module &s : sortedModules) {
+ for (const Item::Module &u : unsortedModules) {
+ if (s.name == u.name) {
+ s.item = u.item;
+ break;
+ }
+ }
+ }
+ return sortedModules;
+}
+
+
template<typename T> bool insertIntoSet(Set<T> &set, const T &value)
{
const auto insertionResult = set.insert(value);
@@ -1045,21 +1070,22 @@ void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext)
Item * const item = productContext->item;
- Item::Modules mergedModules;
- for (const Item::Module &module : Item::Modules(item->modules())) {
- Item::Module mergedModule = module;
- ModuleMerger(m_logger, item, mergedModule).start();
- mergedModules << mergedModule;
- }
- item->setModules(mergedModules);
-
- // Must happen after all modules have been merged, so needs to be a second loop.
- Item::Modules sortedModules;
- for (const Item::Module &module : item->modules())
- createSortedModuleList(module, sortedModules);
- QBS_CHECK(sortedModules.size() == item->modules().size());
-
- for (const Item::Module &module : qAsConst(sortedModules)) {
+ // It is important that dependent modules are merged after their dependency, because
+ // the dependent module's merger potentially needs to replace module items that were
+ // set by the dependency module's merger (namely, scopes of defining items; see
+ // ModuleMerger::replaceItemInScopes()).
+ Item::Modules topSortedModules = modulesSortedByDependency(item);
+ for (Item::Module &module : topSortedModules)
+ ModuleMerger(m_logger, item, module).start();
+
+ // Re-sort the modules by name. This is more stable; see QBS-818.
+ // The list of modules in the product now has the same order as before,
+ // only the items have been replaced by their merged counterparts.
+ Item::Modules lexicographicallySortedModules = topSortedModules;
+ std::sort(lexicographicallySortedModules.begin(), lexicographicallySortedModules.end());
+ item->setModules(lexicographicallySortedModules);
+
+ for (const Item::Module &module : topSortedModules) {
if (!module.item->isPresentModule())
continue;
try {
@@ -1101,7 +1127,7 @@ void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext)
// Module validation must happen in an extra pass, after all Probes have been resolved.
EvalCacheEnabler cacheEnabler(m_evaluator);
- for (const Item::Module &module : qAsConst(sortedModules)) {
+ for (const Item::Module &module : topSortedModules) {
if (!module.item->isPresentModule())
continue;
try {
diff --git a/tests/auto/language/testdata/qbs1275.qbs b/tests/auto/language/testdata/qbs1275.qbs
new file mode 100644
index 000000000..898e1f165
--- /dev/null
+++ b/tests/auto/language/testdata/qbs1275.qbs
@@ -0,0 +1,42 @@
+import qbs
+
+Project {
+ Product {
+ name: "v-bug"
+
+ Export {
+ Depends { name: "cpp"}
+ cpp.defines: ""
+ }
+ }
+
+ Product {
+ name: "e-bug"
+
+ Export { Depends { name: "v-bug" } }
+ }
+
+ Product {
+ name: "u-bug"
+
+ Export { Depends { name: "c-bug" } }
+ }
+
+ Product {
+ name: "c-bug"
+
+ Export { Depends { name: "e-bug" } }
+ }
+
+ Product
+ {
+ name: "H-bug"
+
+ Depends { name: "e-bug" }
+ Depends { name: "u-bug" }
+
+ Group {
+ qbs.install: true
+ }
+ }
+}
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index 8124cc2ac..0eb466188 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -2399,6 +2399,22 @@ void TestLanguage::propertyAssignmentInExportedGroup()
QCOMPARE(exceptionCaught, false);
}
+void TestLanguage::qbs1275()
+{
+ bool exceptionCaught = false;
+ try {
+ defaultParameters.setProjectFilePath(testProject("qbs1275.qbs"));
+ const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ QVERIFY(!!project);
+ const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
+ QCOMPARE(products.count(), 5);
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
+}
+
void TestLanguage::qbsPropertiesInProjectCondition()
{
bool exceptionCaught = false;
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index 2a07e8e42..d12fc898a 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -145,6 +145,7 @@ private slots:
void propertiesBlockInGroup();
void propertiesItemInModule();
void propertyAssignmentInExportedGroup();
+ void qbs1275();
void qbsPropertiesInProjectCondition();
void qbsPropertyConvenienceOverride();
void relaxedErrorMode();