diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2023-10-13 10:19:17 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2023-10-13 10:19:17 +0200 |
commit | 7793e84a5b692cfd61af06622db5241f8cd44ebe (patch) | |
tree | 986bb3e7acc4a42d0f5c56b7e5c2ae87dbccb721 | |
parent | 871e4c29b3e1d6611dc7c71c4bb01950ebf9874d (diff) | |
parent | 40327c8277b904944dbd9a227f9a819e3d53666d (diff) |
Merge 2.2 into master
Change-Id: I1b274180cb0ef09f9487101d17b1545fefa9aa7f
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 ¶meters) +{ + 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 ¤t, 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 ¤t, const QVariantMap &next, int nextPrio) +{ + for (auto it = next.begin(); it != next.end(); ++it) { + m_path << it.key(); + const QVariant &newValue = it.value(); + QVariant ¤tValue = 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 ¶meters); + 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 ¶mDecls = param->propertyDeclarations(); - for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) - decls.insert(it.key(), it.value()); + if (param->type() == ItemType::Parameter) { + const auto ¶mDecls = 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 ¶meters = 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(); |