aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2023-10-13 10:19:17 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2023-10-13 10:19:17 +0200
commit7793e84a5b692cfd61af06622db5241f8cd44ebe (patch)
tree986bb3e7acc4a42d0f5c56b7e5c2ae87dbccb721
parent871e4c29b3e1d6611dc7c71c4bb01950ebf9874d (diff)
parent40327c8277b904944dbd9a227f9a819e3d53666d (diff)
Merge 2.2 into master
-rw-r--r--doc/reference/modules/capnprotocpp-module.qdoc4
-rw-r--r--doc/reference/modules/protobufcpp-module.qdoc44
-rw-r--r--share/qbs/module-providers/Qt/setup-qt.js32
-rw-r--r--share/qbs/module-providers/Qt/templates/core.qbs12
-rw-r--r--share/qbs/modules/capnproto/capnprotobase.qbs2
-rw-r--r--share/qbs/modules/protobuf/cpp/protobufcpp.qbs100
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp15
-rw-r--r--src/lib/corelib/language/item.h5
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.cpp43
-rw-r--r--src/lib/corelib/loader/loaderutils.cpp113
-rw-r--r--src/lib/corelib/loader/loaderutils.h14
-rw-r--r--src/lib/corelib/loader/moduleloader.cpp17
-rw-r--r--src/lib/corelib/loader/productresolver.cpp47
-rw-r--r--src/lib/corelib/loader/productscollector.cpp6
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp3
-rw-r--r--tests/auto/language/testdata/module-parameters/module-parameters.qbs35
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs4
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs4
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs7
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs3
-rw-r--r--tests/auto/language/tst_language.cpp152
-rw-r--r--tests/auto/language/tst_language.h2
22 files changed, 450 insertions, 214 deletions
diff --git a/doc/reference/modules/capnprotocpp-module.qdoc b/doc/reference/modules/capnprotocpp-module.qdoc
index c3c8660c9..7635edd9e 100644
--- a/doc/reference/modules/capnprotocpp-module.qdoc
+++ b/doc/reference/modules/capnprotocpp-module.qdoc
@@ -104,12 +104,10 @@
/*!
\qmlproperty string capnproto.cpp::outputDir
- \readonly
The directory where the \c capnpc compiler generated files are placed.
- The value of this property is automatically set by \QBS and cannot be
- changed by the user.
+ \defaultvalue \c product.buildDirectory + "/capnp"
*/
/*!
diff --git a/doc/reference/modules/protobufcpp-module.qdoc b/doc/reference/modules/protobufcpp-module.qdoc
index 85851c4ae..eef189d0b 100644
--- a/doc/reference/modules/protobufcpp-module.qdoc
+++ b/doc/reference/modules/protobufcpp-module.qdoc
@@ -78,6 +78,14 @@
\li 1.18.0
\li This tag is attached to the header files generated by \c protoc compiler.
\endtable
+
+ \section2 Dependencies
+
+ The \l protobuf.cpp module requires runtime libraries to be operational. It depends on the
+ \c "protobuflib" module which can be created by the \l qbspkgconfig module provider (the
+ corresponding packages are \c protobuf or \c protobuf-lite). If \l useGrpc is set to true,
+ the \l protobuf.cpp module also depends on the \c "grpcpp" module (corresponding package is
+ \c gprc++).
*/
/*!
@@ -101,24 +109,6 @@
*/
/*!
- \qmlproperty string protobuf.cpp::grpcIncludePath
-
- The path where grpc++ headers are located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
- \qmlproperty string protobuf.cpp::grpcLibraryPath
-
- The path where the grpc++ library is located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
\qmlproperty pathList protobuf.cpp::importPaths
The list of imports that are passed to the \c protoc tool via the \c --proto_path option.
@@ -131,24 +121,6 @@
*/
/*!
- \qmlproperty string protobuf.cpp::includePath
-
- The path where protobuf C++ headers are located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
- \qmlproperty string protobuf.cpp::libraryPath
-
- The path where the protobuf C++ library is located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
\qmlproperty string protobuf.cpp::outputDir
\readonly
diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js
index b33b30e3a..f65c80d77 100644
--- a/share/qbs/module-providers/Qt/setup-qt.js
+++ b/share/qbs/module-providers/Qt/setup-qt.js
@@ -1416,7 +1416,7 @@ function findVariable(content, start) {
}
function minVersionJsString(minVersion) {
- return !minVersion ? "original" : ModUtils.toJSLiteral(minVersion);
+ return !minVersion ? "" : ModUtils.toJSLiteral(minVersion);
}
function replaceSpecialValues(content, module, qtProps, abi) {
@@ -1470,12 +1470,12 @@ function replaceSpecialValues(content, module, qtProps, abi) {
libNameForLinkerRelease: ModUtils.toJSLiteral(libNameForLinker(module, qtProps, false)),
entryPointLibsDebug: ModUtils.toJSLiteral(qtProps.entryPointLibsDebug),
entryPointLibsRelease: ModUtils.toJSLiteral(qtProps.entryPointLibsRelease),
- minWinVersion: minVersionJsString(qtProps.windowsVersion),
- minMacVersion: minVersionJsString(qtProps.macosVersion),
- minIosVersion: minVersionJsString(qtProps.iosVersion),
- minTvosVersion: minVersionJsString(qtProps.tvosVersion),
- minWatchosVersion: minVersionJsString(qtProps.watchosVersion),
- minAndroidVersion: minVersionJsString(qtProps.androidVersion),
+ minWinVersion_optional: minVersionJsString(qtProps.windowsVersion),
+ minMacVersion_optional: minVersionJsString(qtProps.macosVersion),
+ minIosVersion_optional: minVersionJsString(qtProps.iosVersion),
+ minTvosVersion_optional: minVersionJsString(qtProps.tvosVersion),
+ minWatchosVersion_optional: minVersionJsString(qtProps.watchosVersion),
+ minAndroidVersion_optional: minVersionJsString(qtProps.androidVersion),
};
var additionalContent = "";
@@ -1546,9 +1546,21 @@ function replaceSpecialValues(content, module, qtProps, abi) {
for (var pos = findVariable(content, 0); pos[0] !== -1;
pos = findVariable(content, pos[0])) {
- var replacement = dict[content.slice(pos[0] + 1, pos[1])] || "";
- content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1);
- pos[0] += replacement.length;
+ var varName = content.slice(pos[0] + 1, pos[1]);
+ var replacement = dict[varName] || "";
+ if (!replacement && varName.endsWith("_optional")) {
+ var prevNewline = content.lastIndexOf('\n', pos[0]);
+ if (prevNewline === -1)
+ prevNewline = 0;
+ var nextNewline = content.indexOf('\n', pos[0]);
+ if (nextNewline === -1)
+ prevNewline = content.length;
+ content = content.slice(0, prevNewline) + content.slice(nextNewline);
+ pos[0] = prevNewline;
+ } else {
+ content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1);
+ pos[0] += replacement.length;
+ }
}
return content;
}
diff --git a/share/qbs/module-providers/Qt/templates/core.qbs b/share/qbs/module-providers/Qt/templates/core.qbs
index 214bd65c2..485402716 100644
--- a/share/qbs/module-providers/Qt/templates/core.qbs
+++ b/share/qbs/module-providers/Qt/templates/core.qbs
@@ -222,12 +222,12 @@ Module {
return "libc++";
return original;
}
- cpp.minimumWindowsVersion: @minWinVersion@
- cpp.minimumMacosVersion: @minMacVersion@
- cpp.minimumIosVersion: @minIosVersion@
- cpp.minimumTvosVersion: @minTvosVersion@
- cpp.minimumWatchosVersion: @minWatchosVersion@
- cpp.minimumAndroidVersion: @minAndroidVersion@
+ cpp.minimumWindowsVersion: @minWinVersion_optional@
+ cpp.minimumMacosVersion: @minMacVersion_optional@
+ cpp.minimumIosVersion: @minIosVersion_optional@
+ cpp.minimumTvosVersion: @minTvosVersion_optional@
+ cpp.minimumWatchosVersion: @minWatchosVersion_optional@
+ cpp.minimumAndroidVersion: @minAndroidVersion_optional@
// Universal Windows Platform support
cpp.windowsApiFamily: mkspecName.startsWith("winrt-") ? "pc" : undefined
diff --git a/share/qbs/modules/capnproto/capnprotobase.qbs b/share/qbs/modules/capnproto/capnprotobase.qbs
index e84d39433..56d542770 100644
--- a/share/qbs/modules/capnproto/capnprotobase.qbs
+++ b/share/qbs/modules/capnproto/capnprotobase.qbs
@@ -40,7 +40,7 @@ Module {
property pathList importPaths: []
- readonly property string outputDir: product.buildDirectory + "/capnp"
+ property string outputDir: product.buildDirectory + "/capnp"
Probes.BinaryProbe {
id: compilerProbe
diff --git a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
index e9505dfc4..9a8c55524 100644
--- a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
+++ b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
@@ -6,29 +6,14 @@ import "../protobufbase.qbs" as ProtobufBase
import "../protobuf.js" as HelperFunctions
ProtobufBase {
- property string includePath: includeProbe.found ? includeProbe.path : undefined
- property string libraryPath: libraryProbe.found ? libraryProbe.path : undefined
-
property bool useGrpc: false
property bool _linkLibraries: true
- property stringList _extraGrpcLibs: []
readonly property bool _hasModules: protobuflib.present && (!useGrpc || grpcpp.present)
- property string grpcIncludePath: grpcIncludeProbe.found ? grpcIncludeProbe.path : undefined
- property string grpcLibraryPath: grpcLibraryProbe.found ? grpcLibraryProbe.path : undefined
-
property string _cxxLanguageVersion: "c++17"
- readonly property string _libraryName: {
- var libraryName;
- if (libraryProbe.found) {
- libraryName = FileInfo.baseName(libraryProbe.fileName);
- if (libraryName.startsWith("lib"))
- libraryName = libraryName.substring(3);
- }
- return libraryName;
- }
+ cpp.includePaths: outputDir
Depends { name: "cpp" }
Depends {
@@ -52,43 +37,6 @@ ProtobufBase {
names: "grpc_cpp_plugin"
}
- cpp.libraryPaths: {
- if (!_linkLibraries || _hasModules)
- return [];
-
- var result = [];
- if (libraryProbe.found)
- result.push(libraryProbe.path);
- if (useGrpc && grpcLibraryProbe.found)
- result.push(grpcLibraryPath);
- return result;
- }
- cpp.dynamicLibraries: {
- if (!_linkLibraries || _hasModules)
- return [];
-
- var result = [];
- if (_libraryName)
- result.push(_libraryName)
- if (qbs.targetOS.includes("unix"))
- result.push("pthread");
- if (useGrpc) {
- result = result.concat(_extraGrpcLibs);
- result.push("grpc++");
- }
- return result;
- }
- cpp.includePaths: {
- if (!_linkLibraries || _hasModules)
- return [outputDir];
-
- var result = [outputDir];
- if (includeProbe.found)
- result.push(includePath);
- if (useGrpc && grpcIncludeProbe.found)
- result.push(grpcIncludePath);
- return result;
- }
cpp.cxxLanguageVersion: _cxxLanguageVersion
Rule {
@@ -123,56 +71,16 @@ ProtobufBase {
}
}
- Probes.IncludeProbe {
- id: includeProbe
- names: "google/protobuf/message.h"
- platformSearchPaths: includePath ? [] : base
- searchPaths: includePath ? [includePath] : []
- }
-
- Probes.LibraryProbe {
- id: libraryProbe
- names: [
- "protobuf",
- "protobufd",
- ]
- platformSearchPaths: libraryPath ? [] : base
- searchPaths: libraryPath ? [libraryPath] : []
- }
-
- Probes.IncludeProbe {
- id: grpcIncludeProbe
- pathSuffixes: "grpc++"
- names: "grpc++.h"
- platformSearchPaths: grpcIncludePath ? [] : base
- searchPaths: grpcIncludePath ? [grpcIncludePath] : []
- }
-
- Probes.LibraryProbe {
- id: grpcLibraryProbe
- names: "grpc++"
- platformSearchPaths: grpcLibraryPath ? [] : base
- searchPaths: grpcLibraryPath ? [grpcLibraryPath] : []
- }
-
validate: {
HelperFunctions.validateCompiler(compilerName, compilerPath);
- if (_hasModules)
- return;
-
- if (_linkLibraries && !includeProbe.found)
- throw "Can't find cpp protobuf include files. Please set the includePath property.";
- if (_linkLibraries && !libraryProbe.found)
- throw "Can't find cpp protobuf library. Please set the libraryPath property.";
+ if (_linkLibraries && ! _hasModules) {
+ throw "Can't find cpp protobuf runtime. Make sure .pc files are present";
+ }
if (useGrpc) {
if (!File.exists(grpcPluginPath))
throw "Can't find grpc_cpp_plugin plugin. Please set the grpcPluginPath property.";
- if (_linkLibraries && !grpcIncludeProbe.found)
- throw "Can't find grpc++ include files. Please set the grpcIncludePath property.";
- if (_linkLibraries && !grpcLibraryProbe.found)
- throw "Can't find grpc++ library. Please set the grpcLibraryPath property.";
}
}
}
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index a2502685e..2ad0e3ef4 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -258,7 +258,6 @@ void BuiltinDeclarations::addExportItem()
item << PropertyDeclaration(StringConstants::prefixMappingProperty(),
PropertyDeclaration::Variant);
auto allowedChildTypes = item.allowedChildTypes();
- allowedChildTypes.insert(ItemType::Parameters);
allowedChildTypes.insert(ItemType::Properties);
item.setAllowedChildTypes(allowedChildTypes);
insert(item);
@@ -339,16 +338,10 @@ void BuiltinDeclarations::addModuleProviderItem()
ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type)
{
ItemDeclaration item(type);
- item.setAllowedChildTypes(ItemDeclaration::TypeNames()
- << ItemType::Group
- << ItemType::Depends
- << ItemType::FileTagger
- << ItemType::JobLimit
- << ItemType::Rule
- << ItemType::Parameter
- << ItemType::Probe
- << ItemType::PropertyOptions
- << ItemType::Scanner);
+ item.setAllowedChildTypes({ItemType::Depends, ItemType::FileTagger, ItemType::Group,
+ ItemType::JobLimit, ItemType::Parameter, ItemType::Parameters,
+ ItemType::Probe, ItemType::PropertyOptions,
+ ItemType::Rule, ItemType::Scanner});
PropertyDeclaration nameDecl = nameProperty();
PropertyDeclaration::Flags nameFlags = nameDecl.flags();
nameFlags |= PropertyDeclaration::ReadOnlyFlag;
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index 7f81d53b5..aadf2516b 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -55,6 +55,7 @@
#include <atomic>
#include <mutex>
+#include <utility>
#include <vector>
namespace qbs {
@@ -86,7 +87,9 @@ public:
// All items that declared an explicit dependency on this module. Can contain any
// number of module instances and at most one product.
- std::vector<Item *> loadingItems;
+ using ParametersWithPriority = std::pair<QVariantMap, int>;
+ using LoadingItemInfo = std::pair<Item *, ParametersWithPriority>;
+ std::vector<LoadingItemInfo> loadingItems;
QVariantMap parameters;
VersionRange versionRange;
diff --git a/src/lib/corelib/loader/dependenciesresolver.cpp b/src/lib/corelib/loader/dependenciesresolver.cpp
index 8c7a69e08..431abc9fe 100644
--- a/src/lib/corelib/loader/dependenciesresolver.cpp
+++ b/src/lib/corelib/loader/dependenciesresolver.cpp
@@ -170,6 +170,7 @@ private:
std::pair<Item::Module *, Item *> findExistingModule(const FullyResolvedDependsItem &dependency,
Item *item);
void updateModule(Item::Module &module, const FullyResolvedDependsItem &dependency);
+ int dependsChainLength();
ProductContext *findMatchingProduct(const FullyResolvedDependsItem &dependency);
Item *findMatchingModule(const FullyResolvedDependsItem &dependency);
std::pair<bool, HandleDependency> checkProductDependency(
@@ -409,6 +410,12 @@ LoadModuleResult DependenciesResolver::loadModule(
ProductContext *productDep = nullptr;
Item *moduleItem = nullptr;
+ const auto addLoadingItem = [&](Item::Module &module, Item &loadingItem) {
+ module.loadingItems.emplace_back(&loadingItem,
+ std::make_pair(dependency.parameters,
+ INT_MAX - dependsChainLength()));
+ };
+
// The module might already have been loaded for this product (directly or indirectly).
const auto &[existingModule, moduleWithSameName]
= findExistingModule(dependency, m_product.item);
@@ -420,8 +427,15 @@ LoadModuleResult DependenciesResolver::loadModule(
QBS_CHECK(existingModule->item);
moduleItem = existingModule->item;
- if (!contains(existingModule->loadingItems, loadingItem))
- existingModule->loadingItems.push_back(loadingItem);
+ const auto matcher = [loadingItem](const Item::Module::LoadingItemInfo &info) {
+ return info.first == loadingItem;
+ };
+ const auto it = std::find_if(existingModule->loadingItems.begin(),
+ existingModule->loadingItems.end(), matcher);
+ if (it == existingModule->loadingItems.end())
+ addLoadingItem(*existingModule, *loadingItem);
+ else
+ it->second.first = mergeDependencyParameters(it->second.first, dependency.parameters);
} else if (dependency.product) {
productDep = dependency.product; // We have already done the look-up.
} else if (!(productDep = findMatchingProduct(dependency))) {
@@ -497,17 +511,9 @@ LoadModuleResult DependenciesResolver::loadModule(
qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
if (m_product.item) {
Item::Module module = createModule(dependency, moduleItem, productDep);
-
- if (module.name.toString() != StringConstants::qbsModule()) {
- // TODO: Why do we have default parameters only for Export items and
- // property declarations only for modules? Does that make any sense?
- if (productDep)
- module.parameters = productDep->defaultParameters;
- mergeParameters(module.parameters, dependency.parameters);
- }
module.required = dependency.requiredGlobally;
- module.loadingItems.push_back(loadingItem);
- module.maxDependsChainLength = m_product.dependenciesContext ? stateStack().size() : 1;
+ addLoadingItem(module, *loadingItem);
+ module.maxDependsChainLength = dependsChainLength();
m_product.item->addModule(module);
addLocalModule();
}
@@ -543,14 +549,15 @@ void DependenciesResolver::updateModule(
Item::Module &module, const FullyResolvedDependsItem &dependency)
{
forwardParameterDeclarations(dependency.item, m_product.item->modules());
-
- // TODO: Use priorities like for property values. See QBS-1300.
- mergeParameters(module.parameters, dependency.parameters);
-
module.versionRange.narrowDown(dependency.versionRange);
module.required |= dependency.requiredGlobally;
- if (int(stateStack().size()) > module.maxDependsChainLength)
- module.maxDependsChainLength = stateStack().size();
+ if (dependsChainLength() > module.maxDependsChainLength)
+ module.maxDependsChainLength = dependsChainLength();
+}
+
+int DependenciesResolver::dependsChainLength()
+{
+ return m_product.dependenciesContext ? stateStack().size() : 1;
}
ProductContext *DependenciesResolver::findMatchingProduct(
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp
index a0949df91..d00317afb 100644
--- a/src/lib/corelib/loader/loaderutils.cpp
+++ b/src/lib/corelib/loader/loaderutils.cpp
@@ -68,20 +68,6 @@ QString fullProductDisplayName(const QString &name, const QString &multiplexId)
return result;
}
-void mergeParameters(QVariantMap &dst, const QVariantMap &src)
-{
- for (auto it = src.begin(); it != src.end(); ++it) {
- if (it.value().userType() == QMetaType::QVariantMap) {
- QVariant &vdst = dst[it.key()];
- QVariantMap mdst = vdst.toMap();
- mergeParameters(mdst, it.value().toMap());
- vdst = mdst;
- } else {
- dst[it.key()] = it.value();
- }
- }
-}
-
void adjustParametersScopes(Item *item, Item *scope)
{
if (item->type() == ItemType::ModuleParameters) {
@@ -219,7 +205,7 @@ void TopLevelProjectContext::addDisabledItem(Item *item)
m_disabledItems.data << item;
}
-bool TopLevelProjectContext::isDisabledItem(Item *item) const
+bool TopLevelProjectContext::isDisabledItem(const Item *item) const
{
std::shared_lock lock(m_disabledItems.mutex);
return m_disabledItems.data.contains(item);
@@ -402,6 +388,21 @@ Item::PropertyDeclarationMap TopLevelProjectContext::parameterDeclarations(Item
return {};
}
+void TopLevelProjectContext::setParameters(const Item *moduleProto, const QVariantMap &parameters)
+{
+ std::unique_lock lock(m_parameters.mutex);
+ m_parameters.data.insert({moduleProto, parameters});
+}
+
+QVariantMap TopLevelProjectContext::parameters(Item *moduleProto) const
+{
+ std::shared_lock lock(m_parameters.mutex);
+ if (const auto it = m_parameters.data.find(moduleProto); it != m_parameters.data.end()) {
+ return it->second;
+ }
+ return {};
+}
+
QString TopLevelProjectContext::findModuleDirectory(
const QualifiedId &module, const QString &searchPath,
const std::function<QString()> &findOnDisk)
@@ -868,4 +869,86 @@ void ItemReaderCache::AstCacheEntry::removeProcessingThread()
m_processingThreads.data.remove(std::this_thread::get_id());
}
+class DependencyParametersMerger
+{
+public:
+ DependencyParametersMerger(std::vector<Item::Module::ParametersWithPriority> &&candidates)
+ : m_candidates(std::move(candidates)) { }
+ QVariantMap merge();
+
+private:
+ void merge(QVariantMap &current, const QVariantMap &next, int nextPrio);
+
+ const std::vector<Item::Module::ParametersWithPriority> m_candidates;
+
+ struct Conflict {
+ Conflict(QStringList path, QVariant val1, QVariant val2, int prio)
+ : path(std::move(path)), val1(std::move(val1)), val2(std::move(val2)), priority(prio) {}
+ QStringList path;
+ QVariant val1;
+ QVariant val2;
+ int priority;
+ };
+ std::vector<Conflict> m_conflicts;
+ QVariantMap m_currentValue;
+ int m_currentPrio = INT_MIN;
+ QStringList m_path;
+};
+
+QVariantMap mergeDependencyParameters(std::vector<Item::Module::ParametersWithPriority> &&candidates)
+{
+ return DependencyParametersMerger(std::move(candidates)).merge();
+}
+
+QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2)
+{
+ return mergeDependencyParameters({std::make_pair(m1, 0), std::make_pair(m2, 0)});
+}
+
+QVariantMap DependencyParametersMerger::merge()
+{
+ for (const auto &next : m_candidates) {
+ merge(m_currentValue, next.first, next.second);
+ m_currentPrio = next.second;
+ }
+
+ if (!m_conflicts.empty()) {
+ ErrorInfo error(Tr::tr("Conflicting parameter values encountered:"));
+ for (const Conflict &conflict : m_conflicts) {
+ // TODO: Location would be nice ...
+ error.append(Tr::tr(" Parameter '%1' cannot be both '%2' and '%3'.")
+ .arg(conflict.path.join(QLatin1Char('.')),
+ conflict.val1.toString(), conflict.val2.toString()));
+ }
+ throw error;
+ }
+
+ return m_currentValue;
+}
+
+void DependencyParametersMerger::merge(QVariantMap &current, const QVariantMap &next, int nextPrio)
+{
+ for (auto it = next.begin(); it != next.end(); ++it) {
+ m_path << it.key();
+ const QVariant &newValue = it.value();
+ QVariant &currentValue = current[it.key()];
+ if (newValue.userType() == QMetaType::QVariantMap) {
+ QVariantMap mdst = currentValue.toMap();
+ merge(mdst, it.value().toMap(), nextPrio);
+ currentValue = mdst;
+ } else {
+ if (m_currentPrio == nextPrio) {
+ if (currentValue.isValid() && currentValue != newValue)
+ m_conflicts.emplace_back(m_path, currentValue, newValue, m_currentPrio);
+ } else {
+ removeIf(m_conflicts, [this](const Conflict &conflict) {
+ return m_path == conflict.path;
+ });
+ }
+ currentValue = newValue;
+ }
+ m_path.removeLast();
+ }
+}
+
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h
index 679f60e27..e69451419 100644
--- a/src/lib/corelib/loader/loaderutils.h
+++ b/src/lib/corelib/loader/loaderutils.h
@@ -217,7 +217,7 @@ public:
int productsToHandleCount() const { return m_productsToHandle.data.size(); }
void addDisabledItem(Item *item);
- bool isDisabledItem(Item *item) const;
+ bool isDisabledItem(const Item *item) const;
void setProgressObserver(ProgressObserver *observer);
ProgressObserver *progressObserver() const;
@@ -273,6 +273,9 @@ public:
const Item::PropertyDeclarationMap &decls);
Item::PropertyDeclarationMap parameterDeclarations(Item *moduleProto) const;
+ void setParameters(const Item *moduleProto, const QVariantMap &parameters);
+ QVariantMap parameters(Item *moduleProto) const;
+
// An empty string means no matching module directory was found.
QString findModuleDirectory(const QualifiedId &module, const QString &searchPath,
const std::function<QString()> &findOnDisk);
@@ -335,7 +338,7 @@ private:
std::unordered_map<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap;
Set<QString> m_projectNamesUsedInOverrides;
Set<QString> m_productNamesUsedInOverrides;
- GuardedData<Set<Item *>> m_disabledItems;
+ GuardedData<Set<const Item *>> m_disabledItems;
GuardedData<std::vector<ErrorInfo>, std::mutex> m_queuedErrors;
QString m_buildDirectory;
QVariantMap m_profileConfigs;
@@ -354,6 +357,7 @@ private:
// The keys are module prototypes.
GuardedData<std::unordered_map<const Item *,
Item::PropertyDeclarationMap>> m_parameterDeclarations;
+ GuardedData<std::unordered_map<const Item *, QVariantMap>> m_parameters;
GuardedData<std::unordered_map<const Item *,
std::vector<ErrorInfo>>> m_unknownProfilePropertyErrors;
@@ -433,8 +437,12 @@ private:
Pimpl<Private> d;
};
+// List must be sorted by priority in ascending order.
+[[nodiscard]] QVariantMap mergeDependencyParameters(
+ std::vector<Item::Module::ParametersWithPriority> &&candidates);
+[[nodiscard]] QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2);
+
QString fullProductDisplayName(const QString &name, const QString &multiplexId);
-void mergeParameters(QVariantMap &dst, const QVariantMap &src);
void adjustParametersScopes(Item *item, Item *scope);
void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext,
ProductContext *productContext, ModuleContext *moduleContext);
diff --git a/src/lib/corelib/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp
index c937e8b22..80aa3f17c 100644
--- a/src/lib/corelib/loader/moduleloader.cpp
+++ b/src/lib/corelib/loader/moduleloader.cpp
@@ -45,6 +45,7 @@
#include <api/languageinfo.h>
#include <language/evaluator.h>
+#include <language/scriptengine.h>
#include <language/value.h>
#include <logging/categories.h>
#include <logging/translator.h>
@@ -274,11 +275,17 @@ Item *ModuleLoader::createAndInitModuleItem(const QString &moduleName, const QSt
Item::PropertyDeclarationMap decls;
const auto &moduleChildren = module->children();
for (Item *param : moduleChildren) {
- if (param->type() != ItemType::Parameter)
- continue;
- const auto &paramDecls = param->propertyDeclarations();
- for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
- decls.insert(it.key(), it.value());
+ if (param->type() == ItemType::Parameter) {
+ const auto &paramDecls = param->propertyDeclarations();
+ for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
+ decls.insert(it.key(), it.value());
+ } else if (param->type() == ItemType::Parameters) {
+ adjustParametersScopes(param, param);
+ Evaluator &evaluator = m_loaderState.evaluator();
+ QVariantMap parameters = getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(param)).toMap();
+ m_loaderState.topLevelProject().setParameters(module, parameters);
+ }
}
m_loaderState.topLevelProject().addParameterDeclarations(module, decls);
}
diff --git a/src/lib/corelib/loader/productresolver.cpp b/src/lib/corelib/loader/productresolver.cpp
index 1ba644329..6d10132ee 100644
--- a/src/lib/corelib/loader/productresolver.cpp
+++ b/src/lib/corelib/loader/productresolver.cpp
@@ -64,6 +64,8 @@
#include <tools/setupprojectparameters.h>
#include <tools/stringconstants.h>
+#include <algorithm>
+
namespace qbs::Internal {
class PropertiesEvaluator
@@ -101,10 +103,10 @@ private:
bool validateModule(const Item::Module &module);
void handleModuleSetupError(const Item::Module &module, const ErrorInfo &error);
void checkPropertyDeclarations();
+ void mergeDependencyParameters();
void checkDependencyParameterDeclarations(const Item *productItem,
const QString &productName) const;
-
ProductContext &m_product;
LoaderState &m_loaderState;
const Deferral m_deferral;
@@ -257,6 +259,8 @@ void ProductResolverStage1::start()
doFinalMerge(m_product, m_loaderState);
const bool enabled = topLevelProject.checkItemCondition(m_product.item, evaluator);
+
+ mergeDependencyParameters();
checkDependencyParameterDeclarations(m_product.item, m_product.name);
setupGroups(m_product, m_loaderState);
@@ -335,7 +339,8 @@ void ProductResolverStage1::updateModulePresentState(const Item::Module &module)
if (!module.item->isPresentModule())
return;
bool hasPresentLoadingItem = false;
- for (const Item * const loadingItem : module.loadingItems) {
+ for (const auto &loadingItemInfo : module.loadingItems) {
+ const Item * const loadingItem = loadingItemInfo.first;
if (loadingItem == m_product.item) {
hasPresentLoadingItem = true;
break;
@@ -399,6 +404,44 @@ void ProductResolverStage1::checkPropertyDeclarations()
qbs::Internal::checkPropertyDeclarations(m_product.item, m_loaderState);
}
+void ProductResolverStage1::mergeDependencyParameters()
+{
+ if (m_loaderState.topLevelProject().isDisabledItem(m_product.item))
+ return;
+
+ for (Item::Module &module : m_product.item->modules()) {
+ if (module.name.toString() == StringConstants::qbsModule())
+ continue;
+ if (!module.item->isPresentModule())
+ continue;
+
+ using PP = Item::Module::ParametersWithPriority;
+ std::vector<PP> priorityList;
+ const QVariantMap defaultParameters = module.product
+ ? module.product->defaultParameters
+ : m_loaderState.topLevelProject().parameters(module.item->prototype());
+ priorityList.emplace_back(defaultParameters, INT_MIN);
+ for (const Item::Module::LoadingItemInfo &info : module.loadingItems) {
+ const QVariantMap &parameters = info.second.first;
+
+ // Empty parameter maps and inactive loading modules do not contribute to the
+ // final parameter map.
+ if (parameters.isEmpty())
+ continue;
+ if (info.first->type() == ItemType::ModuleInstance && !info.first->isPresentModule())
+ continue;
+
+ // Build a list sorted by priority.
+ static const auto cmp = [](const PP &elem, int prio) { return elem.second < prio; };
+ const auto it = std::lower_bound(priorityList.begin(), priorityList.end(),
+ info.second.second, cmp);
+ priorityList.insert(it, info.second);
+ }
+
+ module.parameters = qbs::Internal::mergeDependencyParameters(std::move(priorityList));
+ }
+}
+
class DependencyParameterDeclarationCheck
{
public:
diff --git a/src/lib/corelib/loader/productscollector.cpp b/src/lib/corelib/loader/productscollector.cpp
index 85d425b8f..042dbd160 100644
--- a/src/lib/corelib/loader/productscollector.cpp
+++ b/src/lib/corelib/loader/productscollector.cpp
@@ -603,9 +603,9 @@ bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext
for (Item * const child : exportItem->children()) {
if (child->type() == ItemType::Parameters) {
adjustParametersScopes(child, child);
- mergeParameters(defaultParameters,
- getJsVariant(evaluator.engine()->context(),
- evaluator.scriptValue(child)).toMap());
+ defaultParameters = mergeDependencyParameters(defaultParameters,
+ getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(child)).toMap());
} else {
Item::addChild(merged, child);
}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index f8a037766..c39cc33f1 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -5956,7 +5956,6 @@ void TestBlackbox::protobuf_data()
QTest::addColumn<QStringList>("properties");
QTest::addColumn<bool>("hasModules");
QTest::addColumn<bool>("successExpected");
- QTest::newRow("cpp") << QString("addressbook_cpp.qbs") << QStringList() << false << true;
QTest::newRow("cpp-pkgconfig")
<< QString("addressbook_cpp.qbs")
<< QStringList("project.qbsModuleProviders:qbspkgconfig")
@@ -8367,8 +8366,6 @@ void TestBlackbox::grpc_data()
QTest::addColumn<QStringList>("arguments");
QTest::addColumn<bool>("hasModules");
- QTest::newRow("cpp") << QString("grpc_cpp.qbs") << QStringList() << false;
-
QStringList pkgConfigArgs("project.qbsModuleProviders:qbspkgconfig");
// on macOS, openSSL is hidden from pkg-config by default
if (qbs::Internal::HostOsInfo::isMacosHost()) {
diff --git a/tests/auto/language/testdata/module-parameters/module-parameters.qbs b/tests/auto/language/testdata/module-parameters/module-parameters.qbs
new file mode 100644
index 000000000..48169d601
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/module-parameters.qbs
@@ -0,0 +1,35 @@
+Project {
+ property bool overrideFromModule
+ property bool overrideFromExport
+ property bool overrideFromProduct
+
+ Product {
+ name: "dep"
+ Export {
+ Depends {
+ name: "higher";
+ condition: project.overrideFromExport
+ lower.param: "fromExportDepends"
+ }
+ Parameters { lower.param: "fromParameters" }
+ }
+ }
+ Product {
+ name: "main"
+
+ Depends {
+ name: "dep"
+ condition: project.overrideFromProduct
+ lower.param: "fromProductDepends"
+ }
+ Depends {
+ name: "higher"
+ condition: project.overrideFromProduct
+ lower.param: "fromProductDepends"
+ }
+ Depends { name: "dep"; condition: !project.overrideFromProduct }
+ Depends { name: "higher"; condition: !project.overrideFromProduct }
+ Depends { name: "highest" }
+ Depends { name: "broken"; required: false }
+ }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs b/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs
new file mode 100644
index 000000000..ae7b4c4ef
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs
@@ -0,0 +1,4 @@
+Module {
+ Depends { name: "higher"; lower.param: "shouldNeverAppear" }
+ validate: { throw "As the name indicates, this module is broken."; }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs b/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs
new file mode 100644
index 000000000..006e05a93
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs
@@ -0,0 +1,4 @@
+Module {
+ Depends { name: "lower" }
+ Parameters { lower.param: "fromParameters" }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs b/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs
new file mode 100644
index 000000000..83b0a0a03
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs
@@ -0,0 +1,7 @@
+Module {
+ Depends {
+ name: "higher"
+ condition: project.overrideFromModule
+ lower.param: "fromModuleDepends"
+ }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs b/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs
new file mode 100644
index 000000000..11436ecd8
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs
@@ -0,0 +1,3 @@
+Module {
+ Parameter { property string param: "origin" }
+}
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index d732b211c..0310205a7 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -1736,6 +1736,157 @@ void TestLanguage::moduleMergingVariantValues()
QCOMPARE(exceptionCaught, false);
}
+void TestLanguage::moduleParameters_data()
+{
+ QTest::addColumn<QVariantMap>("inputProperties");
+ QTest::addColumn<QVariantMap>("expectedModuleParameters");
+ QTest::addColumn<bool>("errorExpected");
+
+ QTest::newRow("no overrides")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from export")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromExportDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from export and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from module")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromModuleDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from module and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from module and export")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromExportDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << true;
+ QTest::newRow("override from module, export and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+}
+
+void TestLanguage::moduleParameters()
+{
+ QFETCH(QVariantMap, inputProperties);
+ QFETCH(QVariantMap, expectedModuleParameters);
+ QFETCH(bool, errorExpected);
+
+ try {
+ defaultParameters.setOverriddenValues(inputProperties);
+ resolveProject("module-parameters/module-parameters.qbs");
+ QVERIFY(!errorExpected);
+ QVERIFY(project);
+ QCOMPARE(int(project->products.size()), 2);
+ const ResolvedProductPtr mainProduct = productsFromProject(project).value("main");
+ QVERIFY(mainProduct);
+ QCOMPARE(int(mainProduct->moduleParameters.size()), 2);
+ for (auto it = expectedModuleParameters.cbegin(); it != expectedModuleParameters.cend();
+ ++it) {
+ const auto findInProduct = [&](const QString &moduleName) {
+ for (auto it = mainProduct->moduleParameters.cbegin();
+ it != mainProduct->moduleParameters.cend(); ++it) {
+ if (it.key()->name == moduleName)
+ return it.value();
+ }
+ return QVariantMap();
+ };
+ const QVariantMap actual = findInProduct(it.key());
+ const QVariantMap expected = it.value().toMap();
+ const bool same = actual == expected;
+ if (!same) {
+ qDebug().noquote() << "---" << expected;
+ qDebug().noquote() << "+++" << actual;
+ }
+ QVERIFY(same);
+ }
+ } catch (const ErrorInfo &e) {
+ QVERIFY2(errorExpected, qPrintable(e.toString()));
+ }
+}
+
void TestLanguage::modulePrioritizationBySearchPath_data()
{
QTest::addColumn<QStringList>("searchPaths");
@@ -3319,4 +3470,3 @@ int main(int argc, char *argv[])
TestLanguage tl(ConsoleLogger::instance().logSink(), s.get());
return QTest::qExec(&tl, argc, argv);
}
-
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index c6695f7c9..5effb05b7 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -117,6 +117,8 @@ private slots:
void jsImportUsedInMultipleScopes();
void localProfileAsTopLevelProfile();
void moduleMergingVariantValues();
+ void moduleParameters_data();
+ void moduleParameters();
void modulePrioritizationBySearchPath_data();
void modulePrioritizationBySearchPath();
void moduleProperties_data();