summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2023-02-10 12:27:03 +0100
committerChristian Kandeler <christian.kandeler@qt.io>2023-04-20 08:09:46 +0000
commitfb52fed84a1510a7de0172e643d6fd66a780e2e8 (patch)
tree9fcf258fa8e8d8279a3c98d8e6fa37f3ae5c3dc0
parente178052c763dbe18304a101d6f96e79881081e1a (diff)
downloadqbs-fb52fed84a1510a7de0172e643d6fd66a780e2e8.tar.gz
Rewrite ModuleLoader
=================== Problem description =================== The ModuleLoader class as it exists before this patch has a number of serious problems: - It instantiates modules over and over again everywhere a Depends item appears. The different instances are later merged in a hopelessly obscure and error-prone way. - It seriously pollutes the module scopes so that sloppily written project files appear to work even though they shouldn't. - Dependencies between products are handled twice: Once using the normal module instantiation code (with the Export item acting like a Module item), and also with a parallel mechanism that does strange, seemingly redundant things especially regarding transitive dependencies, which appear to introduce enormous run-time overhead. It is also split up between ModuleLoader and ProjectResolver, adding even more confusion. - The code is messy, seriously under-documented and hardly understood even by its original authors. It presents a huge obstacle to potential contributors. ================= Patch description ================= - Each module is instantiated once per product. Property values are merged on the fly. Special handling for dependencies between products are kept to the absolutely required minimum. - There are no more extra passes for establishing inter-product dependencies. Instead, whenever an unhandled dependency is encountered, processing the current product is paused and resumed once the dependency is ready, with the product state getting saved and restored in between so no work is done twice. - The ModuleLoader class now really only locates and loads modules. The new main class is called ProjectTreeBuilder, and we have split off small, self-contained pieces wherever possible. This process will be continued in follow-up patches (see next section). ======= Outlook ======= The ProjectTreeBuilder ist still too large and should be split up further into small, easily digestible parts with clear responsibilities, until the top-level code becomes tidy and self-documenting. In the end, it can probably be merged with ProjectResolver and Loader. However, this first requires the tight coupling between ProjectTreeBuilder/ModuleProviderLoader/ProbesResolver/ProjectResolver to be broken; otherwise we can't produce clean interfaces. As this would involve touching a lot of otherwise unrelated code, it is out of scope for this patch. ================= Benchmarking data ================= We first present wall-time clock results gathered by running "qbs resolve --log-time" for qbs itself and Qt Creator on macOS and Windows. The numbers are the average of several runs, with outliers removed. Then the same for a simple QML project using a static Qt on Linux (this case is special because our current way of handling plugins causes a huge amount of modules to be loaded). Finally, we show the output of the qbs_benchmarker tool for resolving qbs and Qt Creator on Linux. The data shows a speed-up that is hardly noticeable for simple projects, but increases sharply with project complexity. This suggests that our new way of resolving does not suffer anymore from the non-linear slowdown when the number of dependencies gets large. Resolving qbs on Windows: Before this patch: ModuleLoader 3.6s, ProjectResolver 870ms With this patch: ProjectTreeBuilder 3.6s, ProjectResolver 840ms Resolving Qt Creator on Windows: Before this patch: ModuleLoader 17s, ProjectResolver 6.8s With this patch: ProjectTreeBuilder 10.0s, ProjectResolver 6.5s Resolving qbs on macOS: Before this patch: ModuleLoader 4.0s, ProjectResolver 2.3s With this patch: ProjectTreeBuilder 4.0s, ProjectResolver 2.3s Resolving Qt Creator on macOS: Before this patch: ModuleLoader 32.0s, ProjectResolver 15.6s With this patch: ProjectTreeBuilder 23.0s, ProjectResolver 15.3s Note that the above numbers are for an initial resolve, so they include the time for running Probes. The speed-up for re-resolving (with cached Probes) is even higher, in particular on macOS, where Probes take excessively long. Resolving with static Qt on Linux (QBS-1521): Before this patch: ModuleLoader 36s, ProjectResolver 18s With this patch: ProjectTreeBuilder 1.5s, ProjectResolver 14s Output of qbs_benchmarker for resolving qbs on Linux: Old instruction count: 10029744668 New instruction count: 9079802661 Relative change: -10 % Old peak memory usage: 69881840 Bytes New peak memory usage: 82434624 Bytes Relative change: +17 % Output of qbs_benchmarker for resolving Qt Creator on Linux: Old instruction count: 87364681688 New instruction count: 53634332869 Relative change: -39 % Old peak memory usage: 578458840 Bytes New peak memory usage: 567271960 Bytes Relative change: -2 % I don't know where the increased memory footprint for a small project comes from, but since it goes away for larger projects, it doesn't seem worth investigating. Fixes: QBS-1037 Task-number: QBS-1521 Change-Id: Ieeebce8a7ff68cdffc15d645e2342ece2426fa94 Reviewed-by: Ivan Komissarov <ABBAPOH@gmail.com> Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--doc/reference/items/language/depends.qdoc14
-rw-r--r--share/qbs/module-providers/Qt/templates/QtModule.qbs1
-rw-r--r--share/qbs/module-providers/qbspkgconfig.qbs7
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs2
-rw-r--r--share/qbs/modules/qbs/common.qbs2
-rw-r--r--src/lib/corelib/CMakeLists.txt14
-rw-r--r--src/lib/corelib/api/project.cpp4
-rw-r--r--src/lib/corelib/api/projectdata.cpp3
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp4
-rw-r--r--src/lib/corelib/corelib.qbs14
-rw-r--r--src/lib/corelib/language/astpropertiesitemhandler.cpp2
-rw-r--r--src/lib/corelib/language/evaluator.cpp131
-rw-r--r--src/lib/corelib/language/evaluator.h14
-rw-r--r--src/lib/corelib/language/groupshandler.cpp320
-rw-r--r--src/lib/corelib/language/groupshandler.h (renamed from src/lib/corelib/language/modulemerger.h)63
-rw-r--r--src/lib/corelib/language/item.cpp73
-rw-r--r--src/lib/corelib/language/item.h49
-rw-r--r--src/lib/corelib/language/itemreader.cpp72
-rw-r--r--src/lib/corelib/language/itemreader.h13
-rw-r--r--src/lib/corelib/language/itemreaderastvisitor.cpp8
-rw-r--r--src/lib/corelib/language/itemreaderastvisitor.h2
-rw-r--r--src/lib/corelib/language/itemreadervisitorstate.h3
-rw-r--r--src/lib/corelib/language/itemtype.h1
-rw-r--r--src/lib/corelib/language/language.cpp28
-rw-r--r--src/lib/corelib/language/language.h6
-rw-r--r--src/lib/corelib/language/loader.cpp22
-rw-r--r--src/lib/corelib/language/localprofiles.cpp164
-rw-r--r--src/lib/corelib/language/localprofiles.h68
-rw-r--r--src/lib/corelib/language/moduleinstantiator.cpp337
-rw-r--r--src/lib/corelib/language/moduleinstantiator.h101
-rw-r--r--src/lib/corelib/language/moduleloader.cpp3826
-rw-r--r--src/lib/corelib/language/moduleloader.h423
-rw-r--r--src/lib/corelib/language/modulemerger.cpp267
-rw-r--r--src/lib/corelib/language/modulepropertymerger.cpp304
-rw-r--r--src/lib/corelib/language/modulepropertymerger.h99
-rw-r--r--src/lib/corelib/language/moduleproviderloader.cpp20
-rw-r--r--src/lib/corelib/language/moduleproviderloader.h6
-rw-r--r--src/lib/corelib/language/probesresolver.cpp21
-rw-r--r--src/lib/corelib/language/probesresolver.h10
-rw-r--r--src/lib/corelib/language/productitemmultiplexer.cpp288
-rw-r--r--src/lib/corelib/language/productitemmultiplexer.h84
-rw-r--r--src/lib/corelib/language/projectresolver.cpp332
-rw-r--r--src/lib/corelib/language/projectresolver.h20
-rw-r--r--src/lib/corelib/language/projecttreebuilder.cpp2293
-rw-r--r--src/lib/corelib/language/projecttreebuilder.h227
-rw-r--r--src/lib/corelib/language/propertydeclaration.cpp145
-rw-r--r--src/lib/corelib/language/propertydeclaration.h7
-rw-r--r--src/lib/corelib/language/value.cpp97
-rw-r--r--src/lib/corelib/language/value.h90
-rw-r--r--src/lib/corelib/tools/persistence.cpp2
-rw-r--r--tests/auto/api/tst_api.cpp14
-rw-r--r--tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs41
-rw-r--r--tests/auto/blackbox/testdata/exports-qbs/lib.qbs2
-rw-r--r--tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs4
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp57
-rw-r--r--tests/auto/blackbox/tst_blackbox.h2
-rw-r--r--tests/auto/language/testdata/module-depends-on-product.qbs (renamed from tests/auto/language/testdata/erroneous/module-depends-on-product.qbs)0
-rw-r--r--tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs (renamed from tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs)0
-rw-r--r--tests/auto/language/tst_language.cpp26
-rw-r--r--tests/auto/language/tst_language.h1
60 files changed, 5407 insertions, 4843 deletions
diff --git a/doc/reference/items/language/depends.qdoc b/doc/reference/items/language/depends.qdoc
index 0adba1474..7cc270483 100644
--- a/doc/reference/items/language/depends.qdoc
+++ b/doc/reference/items/language/depends.qdoc
@@ -127,7 +127,19 @@
The \l required and \l profiles properties are ignored if this property is
set.
- Product types attached via Module::additionalProductTypes are not considered.
+ \note This property does not work recursively. Consider this example:
+ \code
+ Product {
+ name: "A"
+ type: "x"
+ Depends { productTypes: "someTag" }
+ }
+ Product {
+ name: "B"
+ Depends { productTypes: "x" }
+ }
+ \endcode
+ It is not guaranteed that \c A will show up in \c B's dependencies.
\nodefaultvalue
*/
diff --git a/share/qbs/module-providers/Qt/templates/QtModule.qbs b/share/qbs/module-providers/Qt/templates/QtModule.qbs
index 62e05327b..35421436f 100644
--- a/share/qbs/module-providers/Qt/templates/QtModule.qbs
+++ b/share/qbs/module-providers/Qt/templates/QtModule.qbs
@@ -23,6 +23,7 @@ Module {
// We have to pull in all plugins here, because dependency resolving happens
// before module merging, and we don't know yet if someone set
// Qt.pluginSupport.pluginsByType in the product.
+ // TODO: We might be able to use Qt.pluginSupport.pluginsByType now.
// The real filtering is done later by the plugin module files themselves.
var list = [];
var allPlugins = Qt.plugin_support.allPluginsByType;
diff --git a/share/qbs/module-providers/qbspkgconfig.qbs b/share/qbs/module-providers/qbspkgconfig.qbs
index 92d5fda71..599f76d33 100644
--- a/share/qbs/module-providers/qbspkgconfig.qbs
+++ b/share/qbs/module-providers/qbspkgconfig.qbs
@@ -52,7 +52,12 @@ ModuleProvider {
property stringList extraPaths
property stringList libDirs
property bool staticMode: false
- property path sysroot: qbs.sysroot
+
+ // We take the sysroot default from qbs.sysroot, except for Xcode toolchains, where
+ // the sysroot points into the Xcode installation and does not contain .pc files.
+ property path sysroot: qbs.toolchain && qbs.toolchain.includes("xcode")
+ ? undefined : qbs.sysroot
+
property bool mergeDependencies: true
relativeSearchPaths: {
diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs
index 22a98ad57..05ceccf5f 100644
--- a/share/qbs/modules/cpp/GenericGCC.qbs
+++ b/share/qbs/modules/cpp/GenericGCC.qbs
@@ -216,7 +216,7 @@ CppModule {
linkerScriptFlag: "-T"
readonly property bool shouldCreateSymlinks: {
- return createSymlinks && internalVersion && ["macho", "elf"].includes(cpp.imageFormat);
+ return createSymlinks && internalVersion && ["macho", "elf"].includes(imageFormat);
}
readonly property bool shouldSignArtifacts: codesign._canSignArtifacts
diff --git a/share/qbs/modules/qbs/common.qbs b/share/qbs/modules/qbs/common.qbs
index 7723c6050..8b31092a9 100644
--- a/share/qbs/modules/qbs/common.qbs
+++ b/share/qbs/modules/qbs/common.qbs
@@ -102,7 +102,7 @@ Module {
property path installSourceBase
property string installRoot: project.buildDirectory + "/install-root"
property string installDir
- property string installPrefix: qbs.targetOS.includes("unix") ? "/usr/local" : ""
+ property string installPrefix: targetOS.includes("unix") ? "/usr/local" : ""
property path sysroot
PropertyOptions {
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index ff3be4071..17d770666 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -200,6 +200,8 @@ set(LANGUAGE_SOURCES
filecontextbase.h
filetags.cpp
filetags.h
+ groupshandler.cpp
+ groupshandler.h
identifiersearch.cpp
identifiersearch.h
item.cpp
@@ -221,10 +223,14 @@ set(LANGUAGE_SOURCES
language.h
loader.cpp
loader.h
+ localprofiles.cpp
+ localprofiles.h
+ moduleinstantiator.cpp
+ moduleinstantiator.h
moduleloader.cpp
moduleloader.h
- modulemerger.cpp
- modulemerger.h
+ modulepropertymerger.cpp
+ modulepropertymerger.h
moduleproviderinfo.h
moduleproviderloader.cpp
moduleproviderloader.h
@@ -232,8 +238,12 @@ set(LANGUAGE_SOURCES
preparescriptobserver.h
probesresolver.cpp
probesresolver.h
+ productitemmultiplexer.cpp
+ productitemmultiplexer.h
projectresolver.cpp
projectresolver.h
+ projecttreebuilder.cpp
+ projecttreebuilder.h
property.cpp
property.h
propertydeclaration.cpp
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 46c73dabd..936a86fa6 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -1006,7 +1006,7 @@ Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath,
const Internal::TopLevelProjectConstPtr project = BuildGraphLoader::loadProject(bgFilePath);
info.bgFilePath = bgFilePath;
info.overriddenProperties = project->overriddenValues;
- info.profileData = project->profileConfigs;
+ info.profileData = project->fullProfileConfigsTree();
std::vector<std::pair<QString, QString>> props;
for (const QString &prop : requestedProperties) {
QStringList components = prop.split(QLatin1Char('.'));
@@ -1045,7 +1045,7 @@ Project::BuildGraphInfo Project::getBuildGraphInfo() const
throw ErrorInfo(Tr::tr("A job is currently in progress."));
info.bgFilePath = d->internalProject->buildGraphFilePath();
info.overriddenProperties = d->internalProject->overriddenValues;
- info.profileData = d->internalProject->profileConfigs;
+ info.profileData = d->internalProject->fullProfileConfigsTree();
} catch (const ErrorInfo &e) {
info.error = e;
}
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 750186539..a97491955 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -41,6 +41,7 @@
#include "projectdata_p.h"
#include "propertymap_p.h"
#include <language/language.h>
+#include <language/productitemmultiplexer.h>
#include <language/propertymapinternal.h>
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
@@ -558,7 +559,7 @@ const QString &ProductData::name() const
*/
QString ProductData::fullDisplayName() const
{
- return ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId());
+ return ProductItemMultiplexer::fullProductDisplayName(name(), multiplexConfigurationId());
}
/*!
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp
index 9d65f42e4..66e4e8578 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp
@@ -900,8 +900,8 @@ bool BuildGraphLoader::checkConfigCompatibility()
if (m_parameters.finalBuildConfigurationTree() != restoredProject->buildConfiguration())
return false;
Settings settings(m_parameters.settingsDirectory());
- for (QVariantMap::ConstIterator it = restoredProject->profileConfigs.constBegin();
- it != restoredProject->profileConfigs.constEnd(); ++it) {
+ const QVariantMap profileConfigsTree = restoredProject->fullProfileConfigsTree();
+ for (auto it = profileConfigsTree.begin(); it != profileConfigsTree.end(); ++it) {
const Profile profile(it.key(), &settings);
const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
profile, m_parameters.configurationName());
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 1012c1baa..f33c483da 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -278,6 +278,8 @@ QbsLibrary {
"filecontextbase.h",
"filetags.cpp",
"filetags.h",
+ "groupshandler.cpp",
+ "groupshandler.h",
"identifiersearch.cpp",
"identifiersearch.h",
"item.cpp",
@@ -299,10 +301,14 @@ QbsLibrary {
"language.h",
"loader.cpp",
"loader.h",
+ "localprofiles.cpp",
+ "localprofiles.h",
+ "moduleinstantiator.cpp",
+ "moduleinstantiator.h",
"moduleloader.cpp",
"moduleloader.h",
- "modulemerger.cpp",
- "modulemerger.h",
+ "modulepropertymerger.cpp",
+ "modulepropertymerger.h",
"moduleproviderinfo.h",
"moduleproviderloader.cpp",
"moduleproviderloader.h",
@@ -310,8 +316,12 @@ QbsLibrary {
"preparescriptobserver.h",
"probesresolver.cpp",
"probesresolver.h",
+ "productitemmultiplexer.cpp",
+ "productitemmultiplexer.h",
"projectresolver.cpp",
"projectresolver.h",
+ "projecttreebuilder.cpp",
+ "projecttreebuilder.h",
"property.cpp",
"property.h",
"propertydeclaration.cpp",
diff --git a/src/lib/corelib/language/astpropertiesitemhandler.cpp b/src/lib/corelib/language/astpropertiesitemhandler.cpp
index b28fe7d76..cc4c02232 100644
--- a/src/lib/corelib/language/astpropertiesitemhandler.cpp
+++ b/src/lib/corelib/language/astpropertiesitemhandler.cpp
@@ -140,7 +140,7 @@ private:
value->setFile(conditionalValue->file());
item->setProperty(propertyName, value);
value->setSourceCode(StringConstants::baseVar());
- value->setSourceUsesBaseFlag();
+ value->setSourceUsesBase();
}
m_alternative.value = conditionalValue;
value->addAlternative(m_alternative);
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp
index 5252f06be..fe3ee9db2 100644
--- a/src/lib/corelib/language/evaluator.cpp
+++ b/src/lib/corelib/language/evaluator.cpp
@@ -70,10 +70,9 @@ public:
mutable QHash<QString, JSValue> valueCache;
};
-static void convertToPropertyType_impl(ScriptEngine *engine,
- const QString &pathPropertiesBaseDir, const Item *item,
- const PropertyDeclaration& decl,
- const CodeLocation &location, JSValue &v);
+static void convertToPropertyType_impl(
+ ScriptEngine *engine, const QString &pathPropertiesBaseDir, const Item *item,
+ const PropertyDeclaration& decl, const Value *value, const CodeLocation &location, JSValue &v);
static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen,
JSValueConst obj);
static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc,
@@ -202,7 +201,7 @@ bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const
void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc,
JSValue &v)
{
- convertToPropertyType_impl(engine(), QString(), nullptr, decl, loc, v);
+ convertToPropertyType_impl(engine(), QString(), nullptr, decl, nullptr, loc, v);
}
JSValue Evaluator::scriptValue(const Item *item)
@@ -223,7 +222,7 @@ JSValue Evaluator::scriptValue(const Item *item)
return scriptValue;
}
-void Evaluator::onItemPropertyChanged(Item *item)
+void Evaluator::clearCache(const Item *item)
{
const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item),
m_scriptEngine->dataWithPtrClass());
@@ -309,13 +308,14 @@ static QString overriddenSourceDirectory(const Item *item, const QString &defaul
static void convertToPropertyType_impl(ScriptEngine *engine,
const QString &pathPropertiesBaseDir, const Item *item,
const PropertyDeclaration& decl,
- const CodeLocation &location, JSValue &v)
+ const Value *value, const CodeLocation &location, JSValue &v)
{
JSContext * const ctx = engine->context();
if (JS_IsUndefined(v) || JS_IsError(ctx, v) || JS_IsException(v))
return;
QString srcDir;
QString actualBaseDir;
+ const Item * const srcDirItem = value && value->scope() ? value->scope() : item;
if (item && !pathPropertiesBaseDir.isEmpty()) {
const VariantValueConstPtr itemSourceDir
= item->variantProperty(QStringLiteral("sourceDirectory"));
@@ -339,8 +339,8 @@ static void convertToPropertyType_impl(ScriptEngine *engine,
makeTypeError(engine, decl, location, v);
break;
}
- const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir)
- : pathPropertiesBaseDir;
+ const QString srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir)
+ : pathPropertiesBaseDir;
if (!srcDir.isEmpty()) {
v = engine->toScriptValue(QDir::cleanPath(FileInfo::resolvePath(srcDir,
getJsString(ctx, v))));
@@ -353,8 +353,8 @@ static void convertToPropertyType_impl(ScriptEngine *engine,
makeTypeError(engine, decl, location, v);
break;
case PropertyDeclaration::PathList:
- srcDir = item ? overriddenSourceDirectory(item, actualBaseDir)
- : pathPropertiesBaseDir;
+ srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir)
+ : pathPropertiesBaseDir;
// Fall-through.
case PropertyDeclaration::StringList:
{
@@ -565,56 +565,52 @@ private:
setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v);
}
if (value->sourceUsesOriginal()) {
- JSValue originalValue = JS_UNDEFINED;
+ JSValue originalJs = JS_UNDEFINED;
ScopedJsValue originalMgr(engine->context(), JS_UNDEFINED);
if (data->item->propertyDeclaration(*propertyName).isScalar()) {
const Item *item = itemOfProperty;
- if (item->type() == ItemType::Module || item->type() == ItemType::Export) {
- const QString errorMessage = Tr::tr("The special value 'original' cannot "
- "be used on the right-hand side of a property declaration.");
+
+ if (item->type() != ItemType::ModuleInstance) {
+ const QString errorMessage = Tr::tr("The special value 'original' can only "
+ "be used with module properties.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- // TODO: Provide a dedicated item type for not-yet-instantiated things that
- // look like module instances in the AST visitor.
- if (item->type() == ItemType::ModuleInstance
- && !item->hasProperty(StringConstants::presentProperty())) {
- const QString errorMessage = Tr::tr("Trying to assign property '%1' "
- "on something that is not a module.").arg(*propertyName);
+ if (!value->scope()) {
+ const QString errorMessage = Tr::tr("The special value 'original' cannot "
+ "be used on the right-hand side of a property declaration.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- while (item->type() == ItemType::ModuleInstance)
- item = item->prototype();
- if (item->type() != ItemType::Module && item->type() != ItemType::Export) {
- const QString errorMessage = Tr::tr("The special value 'original' can only "
- "be used with module properties.");
- extraScope = throwError(engine->context(), errorMessage);
- result.second = false;
- return result;
+ ValuePtr original;
+ for (const ValuePtr &v : value->candidates()) {
+ if (!v->scope()) {
+ original = v;
+ break;
+ }
}
- const ValuePtr v = item->property(*propertyName);
// This can happen when resolving shadow products. The error will be ignored
// in that case.
- if (!v) {
+ if (!original) {
const QString errorMessage = Tr::tr("Error setting up 'original'.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- SVConverter converter(engine, object, v, item, propertyName, data, &originalValue);
+ SVConverter converter(engine, object, original, item, propertyName, data,
+ &originalJs);
converter.start();
} else {
- originalValue = engine->newArray(0, JsValueOwner::Caller);
- originalMgr.setValue(originalValue);
+ originalJs = engine->newArray(0, JsValueOwner::Caller);
+ originalMgr.setValue(originalJs);
}
- setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue);
+ setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalJs);
}
return result;
}
@@ -627,6 +623,13 @@ private:
}
}
+ void pushScopeRecursively(const Item *scope)
+ {
+ if (scope) {
+ pushScopeRecursively(scope->scope());
+ pushScope(data->evaluator->scriptValue(scope));
+ }
+ }
void pushItemScopes(const Item *item)
{
const Item *scope = item->scope();
@@ -698,12 +701,11 @@ private:
}
pushScope(fileCtxScopes.fileScope);
pushItemScopes(data->item);
- if (itemOfProperty->type() != ItemType::ModuleInstance) {
- // Own properties of module instances must not have the instance itself in the scope.
+ if ((itemOfProperty->type() != ItemType::ModuleInstance
+ && itemOfProperty->type() != ItemType::ModuleInstancePlaceholder) || !value->scope()) {
pushScope(*object);
}
- if (value->definingItem())
- pushItemScopes(value->definingItem());
+ pushScopeRecursively(value->scope());
pushScope(maybeExtraScope.first);
pushScope(fileCtxScopes.importScope);
if (alternative) {
@@ -756,6 +758,16 @@ private:
}
};
+static void convertToPropertyType(ScriptEngine *engine, const Item *item,
+ const PropertyDeclaration& decl, const Value *value, JSValue &v)
+{
+ if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) {
+ v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237
+ return;
+ }
+ convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl,
+ value, value->location(), v);
+}
static QString resultToString(JSContext *ctx, const JSValue &scriptValue)
{
@@ -770,19 +782,15 @@ static QString resultToString(JSContext *ctx, const JSValue &scriptValue)
return getJsVariant(ctx, scriptValue).toString();
}
-static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationData *data,
- JSValue *result, const QString &propertyName,
- const ValuePtr &value)
+static void collectValuesFromNextChain(
+ ScriptEngine *engine, JSValue obj, const ValuePtr &value, const Item *itemOfProperty, const QString &name,
+ const EvaluationData *data, JSValue *result)
{
JSValueList lst;
- Set<Value *> &currentNextChain = data->evaluator->currentNextChain();
- Set<Value *> oldNextChain = currentNextChain;
- for (ValuePtr next = value; next; next = next->next())
- currentNextChain.insert(next.get());
-
for (ValuePtr next = value; next; next = next->next()) {
- ScopedJsValue v(engine->context(),
- data->evaluator->property(next->definingItem(), propertyName));
+ JSValue v = JS_UNDEFINED;
+ SVConverter svc(engine, &obj, next, itemOfProperty, &name, data, &v);
+ svc.start();
if (JsException ex = engine->checkAndClearException({})) {
const ScopedJsValueList l(engine->context(), lst);
*result = engine->throwError(ex.toErrorInfo().toString());
@@ -790,7 +798,9 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
}
if (JS_IsUndefined(v))
continue;
- lst.push_back(v.release());
+ const PropertyDeclaration decl = data->item->propertyDeclaration(name);
+ convertToPropertyType(engine, data->item, decl, next.get(), v);
+ lst.push_back(JS_DupValue(engine->context(), v));
if (next->type() == Value::JSSourceValueType
&& std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) {
// TODO: Why on earth do we keep the last _2_ elements?
@@ -803,7 +813,6 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
break;
}
}
- currentNextChain = oldNextChain;
*result = engine->newArray(int(lst.size()), JsValueOwner::ScriptEngine);
quint32 k = 0;
@@ -822,23 +831,17 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
setJsProperty(ctx, *result, StringConstants::lengthProperty(), JS_NewInt32(ctx, k));
}
-static void convertToPropertyType(ScriptEngine *engine, const Item *item,
- const PropertyDeclaration& decl, const Value *value, JSValue &v)
-{
- if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) {
- v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237
- return;
- }
- convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl,
- value->location(), v);
-}
-
struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; };
static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item,
const QString &name, const EvaluationData *data)
{
Evaluator * const evaluator = data->evaluator;
+ const bool isModuleInstance = item->type() == ItemType::ModuleInstance;
for (; item; item = item->prototype()) {
+ if (isModuleInstance
+ && (item->type() == ItemType::Module || item->type() == ItemType::Export)) {
+ break; // There's no property in a prototype that's not also in the instance.
+ }
const ValuePtr value = item->ownProperty(name);
if (!value)
continue;
@@ -857,8 +860,8 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
}
}
- if (value->next() && !evaluator->currentNextChain().contains(value.get())) {
- collectValuesFromNextChain(engine, data, &result, name, value);
+ if (value->next()) {
+ collectValuesFromNextChain(engine, obj, value, itemOfProperty, name, data, &result);
} else {
SVConverter converter(engine, &obj, value, itemOfProperty, &name, data, &result);
converter.start();
diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h
index f7aee8e46..d791a4c5d 100644
--- a/src/lib/corelib/language/evaluator.h
+++ b/src/lib/corelib/language/evaluator.h
@@ -98,12 +98,12 @@ public:
void setCachingEnabled(bool enabled) { m_valueCacheEnabled = enabled; }
bool cachingEnabled() const { return m_valueCacheEnabled; }
+ void clearCache(const Item *item);
PropertyDependencies &propertyDependencies() { return m_propertyDependencies; }
void clearPropertyDependencies() { m_propertyDependencies.clear(); }
std::stack<QualifiedId> &requestedProperties() { return m_requestedProperties; }
- Set<Value *> &currentNextChain() { return m_currentNextChain; }
void handleEvaluationError(const Item *item, const QString &name);
@@ -113,7 +113,7 @@ public:
bool isNonDefaultValue(const Item *item, const QString &name) const;
private:
- void onItemPropertyChanged(Item *item) override;
+ void onItemPropertyChanged(Item *item) override { clearCache(item); }
bool evaluateProperty(JSValue *result, const Item *item, const QString &name,
bool *propertyWasSet);
@@ -124,7 +124,6 @@ private:
QString m_pathPropertiesBaseDir;
PropertyDependencies m_propertyDependencies;
std::stack<QualifiedId> m_requestedProperties;
- Set<Value *> m_currentNextChain;
bool m_valueCacheEnabled = false;
};
@@ -134,12 +133,17 @@ void throwOnEvaluationError(ScriptEngine *engine,
class EvalCacheEnabler
{
public:
- EvalCacheEnabler(Evaluator *evaluator) : m_evaluator(evaluator)
+ EvalCacheEnabler(Evaluator *evaluator, const QString &baseDir) : m_evaluator(evaluator)
{
m_evaluator->setCachingEnabled(true);
+ m_evaluator->setPathPropertiesBaseDir(baseDir);
}
- ~EvalCacheEnabler() { m_evaluator->setCachingEnabled(false); }
+ ~EvalCacheEnabler()
+ {
+ m_evaluator->setCachingEnabled(false);
+ m_evaluator->clearPathPropertiesBaseDir();
+ }
private:
Evaluator * const m_evaluator;
diff --git a/src/lib/corelib/language/groupshandler.cpp b/src/lib/corelib/language/groupshandler.cpp
new file mode 100644
index 000000000..b9acfc553
--- /dev/null
+++ b/src/lib/corelib/language/groupshandler.cpp
@@ -0,0 +1,320 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "groupshandler.h"
+
+#include "evaluator.h"
+#include "item.h"
+#include "moduleinstantiator.h"
+#include "value.h"
+
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+class GroupsHandler::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, ModuleInstantiator &instantiator,
+ Evaluator &evaluator, Logger &logger)
+ : parameters(parameters), moduleInstantiator(instantiator), evaluator(evaluator),
+ logger(logger) {}
+
+ void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
+ QualifiedIdSet &properties);
+ void markModuleTargetGroups(Item *group, const Item::Module &module);
+ void moveGroupsFromModuleToProduct(Item *product, Item *productScope,
+ const Item::Module &module);
+ void moveGroupsFromModulesToProduct(Item *product, Item *productScope);
+ void propagateModulesFromParent(Item *group);
+ void handleGroup(Item *product, Item *group);
+ void adjustScopesInGroupModuleInstances(Item *groupItem, const Item::Module &module);
+ QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group);
+
+ const SetupProjectParameters &parameters;
+ ModuleInstantiator &moduleInstantiator;
+ Evaluator &evaluator;
+ Logger &logger;
+ std::unordered_map<const Item *, QualifiedIdSet> modulePropsSetInGroups;
+ Set<Item *> disabledGroups;
+ qint64 elapsedTime = 0;
+};
+
+GroupsHandler::GroupsHandler(const SetupProjectParameters &parameters,
+ ModuleInstantiator &instantiator, Evaluator &evaluator, Logger &logger)
+ : d(new Private(parameters, instantiator, evaluator, logger)) {}
+GroupsHandler::~GroupsHandler() { delete d; }
+
+void GroupsHandler::setupGroups(Item *product, Item *productScope)
+{
+ AccumulatingTimer timer(d->parameters.logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ d->modulePropsSetInGroups.clear();
+ d->disabledGroups.clear();
+ d->moveGroupsFromModulesToProduct(product, productScope);
+ for (Item * const child : product->children()) {
+ if (child->type() == ItemType::Group)
+ d->handleGroup(product, child);
+ }
+}
+
+std::unordered_map<const Item *, QualifiedIdSet> GroupsHandler::modulePropertiesSetInGroups() const
+{
+ return d->modulePropsSetInGroups;
+}
+
+Set<Item *> GroupsHandler::disabledGroups() const
+{
+ return d->disabledGroups;
+}
+
+void GroupsHandler::printProfilingInfo(int indent)
+{
+ if (!d->parameters.logElapsedTime())
+ return;
+ d->logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ')
+ << Tr::tr("Setting up Groups took %1.")
+ .arg(elapsedTimeString(d->elapsedTime));
+}
+
+void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
+ QualifiedIdSet &properties)
+{
+ const Item::PropertyMap &props = iv->item()->properties();
+ for (auto it = props.cbegin(); it != props.cend(); ++it) {
+ switch (it.value()->type()) {
+ case Value::JSSourceValueType:
+ properties << (QualifiedId(prefix) << it.key());
+ break;
+ case Value::ItemValueType:
+ if (iv->item()->type() == ItemType::ModulePrefix) {
+ gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
+ QualifiedId(prefix) << it.key(), properties);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Module &module)
+{
+ QBS_CHECK(group->type() == ItemType::Group);
+ if (evaluator.boolValue(group, StringConstants::filesAreTargetsProperty())) {
+ group->setProperty(StringConstants::modulePropertyInternal(),
+ VariantValue::create(module.name.toString()));
+ }
+ for (Item * const child : group->children())
+ markModuleTargetGroups(child, module);
+}
+
+void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *productScope,
+ const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+ for (auto it = module.item->children().begin(); it != module.item->children().end();) {
+ Item * const child = *it;
+ if (child->type() != ItemType::Group) {
+ ++it;
+ continue;
+ }
+ child->setScope(productScope);
+ setScopeForDescendants(child, productScope);
+ Item::addChild(product, child);
+ markModuleTargetGroups(child, module);
+ it = module.item->children().erase(it);
+ }
+}
+
+void GroupsHandler::Private::moveGroupsFromModulesToProduct(Item *product, Item *productScope)
+{
+ for (const Item::Module &module : product->modules())
+ moveGroupsFromModuleToProduct(product, productScope, module);
+}
+
+// TODO: I don't completely understand this function, and I suspect we do both too much
+// and too little here. In particular, I'm not sure why we should even have to do anything
+// with groups that don't attach properties.
+// Set aside a day or two at some point to fully grasp all the details and rewrite accordingly.
+void GroupsHandler::Private::propagateModulesFromParent(Item *group)
+{
+ QBS_CHECK(group->type() == ItemType::Group);
+ QHash<QualifiedId, Item *> moduleInstancesForGroup;
+
+ // Step 1: "Instantiate" the product's modules for the group.
+ for (Item::Module m : group->parent()->modules()) {
+ Item * const targetItem = moduleInstantiator.retrieveModuleInstanceItem(group, m.name);
+ QBS_CHECK(targetItem->type() == ItemType::ModuleInstancePlaceholder);
+ targetItem->setPrototype(m.item);
+
+ Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
+ moduleScope->setFile(group->file());
+ moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids
+ moduleScope->setScope(group);
+ targetItem->setScope(moduleScope);
+
+ targetItem->setFile(m.item->file());
+
+ // "parent" should point to the group/artifact parent
+ targetItem->setParent(group->parent());
+
+ targetItem->setOuterItem(m.item);
+
+ m.item = targetItem;
+ group->addModule(m);
+ moduleInstancesForGroup.insert(m.name, targetItem);
+ }
+
+ // Step 2: Make the inter-module references point to the instances created in step 1.
+ for (const Item::Module &module : group->modules()) {
+ Item::Modules adaptedModules;
+ const Item::Modules &oldModules = module.item->prototype()->modules();
+ for (Item::Module depMod : oldModules) {
+ depMod.item = moduleInstancesForGroup.value(depMod.name);
+ adaptedModules << depMod;
+ if (depMod.name.front() == module.name.front())
+ continue;
+ const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front());
+ QBS_CHECK(modulePrefix);
+ module.item->setProperty(depMod.name.front(), modulePrefix);
+ }
+ module.item->setModules(adaptedModules);
+ }
+
+ const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(group);
+ if (propsSetInGroup.empty())
+ return;
+ modulePropsSetInGroups.insert(std::make_pair(group, propsSetInGroup));
+
+ // Step 3: Adapt scopes in values. This is potentially necessary if module properties
+ // get assigned on the group level.
+ for (const Item::Module &module : group->modules())
+ adjustScopesInGroupModuleInstances(group, module);
+}
+
+void GroupsHandler::Private::handleGroup(Item *product, Item *group)
+{
+ propagateModulesFromParent(group);
+ if (!evaluator.boolValue(group, StringConstants::conditionProperty()))
+ disabledGroups << group;
+ for (Item * const child : group->children()) {
+ if (child->type() == ItemType::Group)
+ handleGroup(product, child);
+ }
+}
+
+void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
+ const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+
+ Item *modulePrototype = module.item->rootPrototype();
+ QBS_CHECK(modulePrototype->type() == ItemType::Module
+ || modulePrototype->type() == ItemType::Export);
+
+ const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations();
+ for (const auto &decl : propDecls) {
+ const QString &propName = decl.name();
+
+ // Nothing gets inherited for module properties assigned directly in the group.
+ // In particular, setting a list property overwrites the value from the product's
+ // (or parent group's) instance completely, rather than appending to it
+ // (concatenation happens via outer.concat()).
+ ValuePtr propValue = module.item->ownProperty(propName);
+ if (propValue) {
+ propValue->setScope(module.item->scope(), {});
+ continue;
+ }
+
+ // Find the nearest prototype instance that has the value assigned.
+ // The result is either an instance of a parent group (or the parent group's
+ // parent group and so on) or the instance of the product or the module prototype.
+ // In the latter case, we don't have to do anything.
+ const Item *instanceWithProperty = module.item;
+ do {
+ instanceWithProperty = instanceWithProperty->prototype();
+ QBS_CHECK(instanceWithProperty);
+ propValue = instanceWithProperty->ownProperty(propName);
+ } while (!propValue);
+ QBS_CHECK(propValue);
+
+ if (propValue->type() != Value::JSSourceValueType)
+ continue;
+
+ if (propValue->scope())
+ module.item->setProperty(propName, propValue->clone());
+ }
+
+ for (const ValuePtr &prop : module.item->properties()) {
+ if (prop->type() != Value::JSSourceValueType) {
+ QBS_CHECK(!prop->next());
+ continue;
+ }
+ for (ValuePtr v = prop; v; v = v->next()) {
+ if (!v->scope())
+ continue;
+ for (const Item::Module &module : groupItem->modules()) {
+ if (v->scope() == module.item->prototype()) {
+ v->setScope(module.item, {});
+ break;
+ }
+ }
+ }
+ }
+}
+
+QualifiedIdSet GroupsHandler::Private::gatherModulePropertiesSetInGroup(const Item *group)
+{
+ QualifiedIdSet propsSetInGroup;
+ const Item::PropertyMap &props = group->properties();
+ for (auto it = props.cbegin(); it != props.cend(); ++it) {
+ if (it.value()->type() == Value::ItemValueType) {
+ gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
+ QualifiedId(it.key()), propsSetInGroup);
+ }
+ }
+ return propsSetInGroup;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/modulemerger.h b/src/lib/corelib/language/groupshandler.h
index 469dc86c4..d3948cbef 100644
--- a/src/lib/corelib/language/modulemerger.h
+++ b/src/lib/corelib/language/groupshandler.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
@@ -37,53 +37,48 @@
**
****************************************************************************/
-#ifndef QBS_MODULEMERGER_H
-#define QBS_MODULEMERGER_H
+#pragma once
-#include "item.h"
#include "qualifiedid.h"
-#include <logging/logger.h>
#include <tools/set.h>
-#include <tools/version.h>
-#include <QtCore/qhash.h>
+#include <unordered_map>
+#include <utility>
namespace qbs {
+class SetupProjectParameters;
namespace Internal {
+class Evaluator;
+class Item;
+class Logger;
+class ModuleInstantiator;
-class ModuleMerger {
+// Sets up Group items for the actual resolving stage. Responsibilities:
+// - Moving Group items located in modules over to the product.
+// - Identifying groups declaring target artifacts and marking them accordingly.
+// - Setting up group-level module instances to ensure proper resolving of per-group module
+// properties.
+// - As a side effect of the above point, collecting all properties set on the Group level
+// to help the ProjectResolver decide which properties need to be re-evaluated at all,
+// which is an important optimization (see commit 9cd8653eef).
+class GroupsHandler
+{
public:
- static void merge(Logger &logger, Item *productItem, const QString &productName,
- Item::Modules *topSortedModules);
+ GroupsHandler(const SetupProjectParameters &parameters, ModuleInstantiator &instantiator,
+ Evaluator &evaluator, Logger &logger);
+ ~GroupsHandler();
-private:
- ModuleMerger(Logger &logger, Item *productItem, const QString &productName,
- const Item::Modules::iterator &modulesBegin,
- const Item::Modules::iterator &modulesEnd);
-
- void appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName,
- const ValuePtr &sv);
- void mergeModule(Item::PropertyMap *props, const Item::Module &m);
- void replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace);
- void start();
+ void setupGroups(Item *product, Item *productScope);
+ std::unordered_map<const Item *, QualifiedIdSet> modulePropertiesSetInGroups() const;
+ Set<Item *> disabledGroups() const;
- static ValuePtr lastInNextChain(const ValuePtr &v);
- static const Item::Module *findModule(const Item *item, const QualifiedId &name);
+ void printProfilingInfo(int indent);
- Logger &m_logger;
- Item * const m_productItem;
- Item::Module &m_mergedModule;
- Item *m_clonedModulePrototype = nullptr;
- Set<const Item *> m_seenInstances;
- Set<Item *> m_moduleInstanceContainers;
- const bool m_isBaseModule;
- const bool m_isShadowProduct;
- const Item::Modules::iterator m_modulesBegin;
- const Item::Modules::iterator m_modulesEnd;
+private:
+ class Private;
+ Private * const d;
};
} // namespace Internal
} // namespace qbs
-
-#endif // QBS_MODULEMERGER_H
diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp
index c58d2058d..aa4f2ce00 100644
--- a/src/lib/corelib/language/item.cpp
+++ b/src/lib/corelib/language/item.cpp
@@ -47,6 +47,7 @@
#include "value.h"
#include <api/languageinfo.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
@@ -103,11 +104,20 @@ Item *Item::clone() const
return dup;
}
+Item *Item::rootPrototype()
+{
+ Item *proto = this;
+ while (proto->prototype())
+ proto = proto->prototype();
+ return proto;
+}
+
QString Item::typeName() const
{
switch (type()) {
case ItemType::IdScope: return QStringLiteral("[IdScope]");
case ItemType::ModuleInstance: return QStringLiteral("[ModuleInstance]");
+ case ItemType::ModuleInstancePlaceholder: return QStringLiteral("[ModuleInstancePlaceholder]");
case ItemType::ModuleParameters: return QStringLiteral("[ModuleParametersInstance]");
case ItemType::ModulePrefix: return QStringLiteral("[ModulePrefix]");
case ItemType::Outer: return QStringLiteral("[Outer]");
@@ -216,9 +226,23 @@ PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExp
void Item::addModule(const Item::Module &module)
{
- const auto it = std::lower_bound(m_modules.begin(), m_modules.end(), module);
- QBS_CHECK(it == m_modules.end() || (module.name != it->name && module.item != it->item));
- m_modules.insert(it, module);
+ if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) {
+ QBS_CHECK(none_of(m_modules, [&](const Module &m) {
+ if (m.name != module.name)
+ return false;
+ if (!!module.productInfo != !!m.productInfo)
+ return true;
+ if (!module.productInfo)
+ return true;
+ if (module.productInfo->multiplexId == m.productInfo->multiplexId
+ && module.productInfo->profile == m.productInfo->profile) {
+ return true;
+ }
+ return false;
+ }));
+ }
+
+ m_modules.push_back(module);
}
void Item::setObserver(ItemObserver *observer) const
@@ -275,6 +299,15 @@ void Item::copyProperty(const QString &propertyName, Item *target) const
target->setProperty(propertyName, property(propertyName));
}
+void Item::overrideProperties(const QVariantMap &config, const QString &key,
+ const SetupProjectParameters &parameters, Logger &logger)
+{
+ const QVariant configMap = config.value(key);
+ if (configMap.isNull())
+ return;
+ overrideProperties(configMap.toMap(), QualifiedId(key), parameters, logger);
+}
+
static const char *valueType(const Value *v)
{
switch (v->type()) {
@@ -390,9 +423,37 @@ void Item::overrideProperties(
handlePropertyError(error, parameters, logger);
continue;
}
- setProperty(it.key(),
- VariantValue::create(PropertyDeclaration::convertToPropertyType(
- it.value(), decl.type(), namePrefix, it.key())));
+ const auto overridenValue = VariantValue::create(PropertyDeclaration::convertToPropertyType(
+ it.value(), decl.type(), namePrefix, it.key()));
+ overridenValue->markAsSetByCommandLine();
+ setProperty(it.key(), overridenValue);
+ }
+}
+
+void Item::setModules(const Modules &modules)
+{
+ m_modules = modules;
+}
+
+Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, Item *module)
+{
+ qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")."
+ << "Creating dummy module for presence check.";
+ if (!module) {
+ module = Item::create(&pool, ItemType::ModuleInstance);
+ module->setFile(FileContext::create());
+ module->setProperty(StringConstants::nameProperty(), VariantValue::create(name));
+ }
+ module->setType(ItemType::ModuleInstance);
+ module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue());
+ return module;
+}
+
+void setScopeForDescendants(Item *item, Item *scope)
+{
+ for (Item * const child : item->children()) {
+ child->setScope(scope);
+ setScopeForDescendants(child, scope);
}
}
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index 60d74a3f4..337cad78a 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -53,6 +53,7 @@
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
+#include <optional>
#include <vector>
namespace qbs {
@@ -75,18 +76,29 @@ class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed
public:
struct Module
{
- Module()
- : item(nullptr), isProduct(false), required(true)
- {}
-
QualifiedId name;
- Item *item;
- bool isProduct;
- bool requiredValue = true; // base value of the required prop
- bool required;
- bool fallbackEnabled = true;
+ Item *item = nullptr;
+ struct ProductInfo {
+ ProductInfo(Item *i, const QString &m, const QString &p)
+ : item(i), multiplexId(m), profile(p) {}
+ Item *item = nullptr;
+ QString multiplexId;
+ QString profile;
+ };
+ std::optional<ProductInfo> productInfo; // Set if and only if the dep is a product.
+
+ // 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;
+
QVariantMap parameters;
VersionRange versionRange;
+
+ // The shorter this value, the "closer to the product" we consider the module,
+ // and the higher its precedence is when merging property values.
+ int maxDependsChainLength = 0;
+
+ bool required = true;
};
using Modules = std::vector<Module>;
using PropertyDeclarationMap = QMap<QString, PropertyDeclaration>;
@@ -99,19 +111,27 @@ public:
const QString &id() const { return m_id; }
const CodeLocation &location() const { return m_location; }
Item *prototype() const { return m_prototype; }
+ Item *rootPrototype();
Item *scope() const { return m_scope; }
Item *outerItem() const { return m_outerItem; }
Item *parent() const { return m_parent; }
const FileContextPtr &file() const { return m_file; }
const QList<Item *> &children() const { return m_children; }
+ QList<Item *> &children() { return m_children; }
Item *child(ItemType type, bool checkForMultiple = true) const;
const PropertyMap &properties() const { return m_properties; }
+ PropertyMap &properties() { return m_properties; }
const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; }
PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const;
+
+ // The list of modules is ordered such that dependencies appear before the modules
+ // depending on them.
const Modules &modules() const { return m_modules; }
+ Modules &modules() { return m_modules; }
+
void addModule(const Module &module);
void removeModules() { m_modules.clear(); }
- void setModules(const Modules &modules) { m_modules = modules; }
+ void setModules(const Modules &modules);
ItemType type() const { return m_type; }
void setType(ItemType type) { m_type = type; }
@@ -149,6 +169,11 @@ public:
void copyProperty(const QString &propertyName, Item *target) const;
void overrideProperties(
const QVariantMap &config,
+ const QString &key,
+ const SetupProjectParameters &parameters,
+ Logger &logger);
+ void overrideProperties(
+ const QVariantMap &config,
const QualifiedId &namePrefix,
const SetupProjectParameters &parameters,
Logger &logger);
@@ -178,6 +203,10 @@ private:
inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; }
+Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason,
+ Item *module);
+void setScopeForDescendants(Item *item, Item *scope);
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp
index a29b36320..8fcda8f24 100644
--- a/src/lib/corelib/language/itemreader.cpp
+++ b/src/lib/corelib/language/itemreader.cpp
@@ -39,9 +39,14 @@
#include "itemreader.h"
+#include "deprecationinfo.h"
+#include "evaluator.h"
+#include "item.h"
#include "itemreadervisitorstate.h"
+#include "value.h"
#include <tools/profiling.h>
+#include <tools/stringconstants.h>
#include <tools/stlutils.h>
#include <QtCore/qfileinfo.h>
@@ -148,5 +153,72 @@ void ItemReader::setDeprecationWarningMode(DeprecationWarningMode mode)
m_visitorState->setDeprecationWarningMode(mode);
}
+void ItemReader::handlePropertyOptions(Item *optionsItem, Evaluator &evaluator)
+{
+ const QString name = evaluator.stringValue(optionsItem, StringConstants::nameProperty());
+ if (name.isEmpty()) {
+ throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"),
+ optionsItem->location());
+ }
+ const QString description = evaluator.stringValue(
+ optionsItem, StringConstants::descriptionProperty());
+ const auto removalVersion = Version::fromString(
+ evaluator.stringValue(optionsItem, StringConstants::removalVersionProperty()));
+ const auto allowedValues = evaluator.stringListValue(
+ optionsItem, StringConstants::allowedValuesProperty());
+
+ PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name);
+ if (!decl.isValid()) {
+ decl.setName(name);
+ decl.setType(PropertyDeclaration::Variant);
+ }
+ decl.setDescription(description);
+ if (removalVersion.isValid()) {
+ DeprecationInfo di(removalVersion, description);
+ decl.setDeprecationInfo(di);
+ }
+ decl.setAllowedValues(allowedValues);
+ const ValuePtr property = optionsItem->parent()->property(name);
+ if (!property && !decl.isExpired()) {
+ throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'")
+ .arg(name), optionsItem->location());
+ }
+ if (property && decl.isExpired()) {
+ ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
+ "is still present.")
+ .arg(name, removalVersion.toString()),
+ property->location());
+ e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
+ optionsItem->location());
+ m_visitorState->logger().printWarning(e);
+ }
+ optionsItem->parent()->setPropertyDeclaration(name, decl);
+}
+
+void ItemReader::handleAllPropertyOptionsItems(Item *item, Evaluator &evaluator)
+{
+ QList<Item *> childItems = item->children();
+ auto childIt = childItems.begin();
+ while (childIt != childItems.end()) {
+ Item * const child = *childIt;
+ if (child->type() == ItemType::PropertyOptions) {
+ handlePropertyOptions(child, evaluator);
+ childIt = childItems.erase(childIt);
+ } else {
+ handleAllPropertyOptionsItems(child, evaluator);
+ ++childIt;
+ }
+ }
+ item->setChildren(childItems);
+}
+
+Item *ItemReader::setupItemFromFile(
+ const QString &filePath, const CodeLocation &referencingLocation, Evaluator &evaluator)
+{
+ Item *item = readFile(filePath, referencingLocation);
+ handleAllPropertyOptionsItems(item, evaluator);
+ return item;
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h
index 6b2531cf2..0ca845a00 100644
--- a/src/lib/corelib/language/itemreader.h
+++ b/src/lib/corelib/language/itemreader.h
@@ -50,7 +50,7 @@
namespace qbs {
namespace Internal {
-
+class Evaluator;
class Item;
class ItemPool;
class ItemReaderVisitorState;
@@ -79,8 +79,10 @@ public:
void clearExtraSearchPathsStack();
const QStringList &allSearchPaths() const;
- Item *readFile(const QString &filePath);
- Item *readFile(const QString &filePath, const CodeLocation &referencingLocation);
+ // Parses a file, creates an item for it, generates PropertyDeclarations from
+ // PropertyOptions items and removes said items from the item tree.
+ Item *setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation,
+ Evaluator &evaluator);
Set<QString> filesRead() const;
@@ -90,6 +92,11 @@ public:
void setDeprecationWarningMode(DeprecationWarningMode mode);
private:
+ Item *readFile(const QString &filePath);
+ Item *readFile(const QString &filePath, const CodeLocation &referencingLocation);
+ void handlePropertyOptions(Item *optionsItem, Evaluator &evaluator);
+ void handleAllPropertyOptionsItems(Item *item, Evaluator &evaluator);
+
ItemPool *m_pool = nullptr;
QStringList m_searchPaths;
std::vector<QStringList> m_extraSearchPaths;
diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp
index 721f24079..f43104836 100644
--- a/src/lib/corelib/language/itemreaderastvisitor.cpp
+++ b/src/lib/corelib/language/itemreaderastvisitor.cpp
@@ -268,7 +268,7 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement,
QBS_CHECK(value);
if (AST::cast<AST::Block *>(statement))
- value->m_flags |= JSSourceValue::HasFunctionForm;
+ value->setHasFunctionForm();
value->setFile(m_file);
value->setSourceCode(textViewOf(m_file->content(), statement));
@@ -282,11 +282,11 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement,
idsearch.add(StringConstants::originalVar(), &usesOriginal);
idsearch.start(statement);
if (usesBase)
- value->m_flags |= JSSourceValue::SourceUsesBase;
+ value->setSourceUsesBase();
if (usesOuter)
- value->m_flags |= JSSourceValue::SourceUsesOuter;
+ value->setSourceUsesOuter();
if (usesOriginal)
- value->m_flags |= JSSourceValue::SourceUsesOriginal;
+ value->setSourceUsesOriginal();
return false;
}
diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h
index 963b78471..e8522efd6 100644
--- a/src/lib/corelib/language/itemreaderastvisitor.h
+++ b/src/lib/corelib/language/itemreaderastvisitor.h
@@ -88,7 +88,7 @@ private:
Logger &m_logger;
QHash<QStringList, QString> m_typeNameToFile;
Item *m_item = nullptr;
- ItemType m_instanceItemType = ItemType::ModuleInstance;
+ ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder;
};
} // namespace Internal
diff --git a/src/lib/corelib/language/itemreadervisitorstate.h b/src/lib/corelib/language/itemreadervisitorstate.h
index 90f88cd5e..dc22cfb42 100644
--- a/src/lib/corelib/language/itemreadervisitorstate.h
+++ b/src/lib/corelib/language/itemreadervisitorstate.h
@@ -39,7 +39,6 @@
#ifndef QBS_ITEMREADERVISITORSTATE_H
#define QBS_ITEMREADERVISITORSTATE_H
-#include <logging/logger.h>
#include <tools/deprecationwarningmode.h>
#include <tools/set.h>
@@ -51,6 +50,7 @@ namespace qbs {
namespace Internal {
class Item;
class ItemPool;
+class Logger;
class ItemReaderVisitorState
{
@@ -58,6 +58,7 @@ public:
ItemReaderVisitorState(Logger &logger);
~ItemReaderVisitorState();
+ Logger &logger() { return m_logger; }
Set<QString> filesRead() const { return m_filesRead; }
Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool);
diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h
index 465396f45..7e9900a5b 100644
--- a/src/lib/corelib/language/itemtype.h
+++ b/src/lib/corelib/language/itemtype.h
@@ -74,6 +74,7 @@ enum class ItemType {
// Internal items created mainly by the module loader.
IdScope,
ModuleInstance,
+ ModuleInstancePlaceholder,
ModuleParameters,
ModulePrefix,
Outer,
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index ecfe09d71..fe92caba9 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -41,6 +41,7 @@
#include "artifactproperties.h"
#include "builtindeclarations.h"
+#include "productitemmultiplexer.h"
#include "propertymapinternal.h"
#include "scriptengine.h"
@@ -60,6 +61,7 @@
#include <tools/qbsassert.h>
#include <tools/qttools.h>
#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
@@ -423,18 +425,9 @@ QString ResolvedProduct::uniqueName() const
return uniqueName(name, multiplexConfigurationId);
}
-QString ResolvedProduct::fullDisplayName(const QString &name,
- const QString &multiplexConfigurationId)
-{
- QString result = name;
- if (!multiplexConfigurationId.isEmpty())
- result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId));
- return result;
-}
-
QString ResolvedProduct::fullDisplayName() const
{
- return fullDisplayName(name, multiplexConfigurationId);
+ return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
}
QString ResolvedProduct::profile() const
@@ -617,6 +610,16 @@ void TopLevelProject::makeModuleProvidersNonTransient()
m.transientOutput = false;
}
+QVariantMap TopLevelProject::fullProfileConfigsTree() const
+{
+ QVariantMap tree;
+ for (auto it = profileConfigs.cbegin(); it != profileConfigs.cend(); ++it) {
+ tree.insert(it.key(), SetupProjectParameters::finalBuildConfigurationTree(
+ it.value().toMap(), overriddenValues));
+ }
+ return tree;
+}
+
QString TopLevelProject::buildGraphFilePath() const
{
return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id());
@@ -913,11 +916,6 @@ bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1,
return listsAreEqual(l1, l2);
}
-QString multiplexIdToString(const QString &id)
-{
- return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
-}
-
bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b)
{
return equals(a.m_sharedData.get(), b.m_sharedData.get());
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index a1115519d..1dae572a1 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -361,7 +361,7 @@ public:
static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); }
QString name;
- QStringList moduleDependencies;
+ QStringList moduleDependencies; // TODO: Still needed?
PrivateScriptFunction setupBuildEnvironmentScript;
PrivateScriptFunction setupRunEnvironmentScript;
ResolvedProduct *product = nullptr;
@@ -597,7 +597,6 @@ public:
static QString uniqueName(const QString &name,
const QString &multiplexConfigurationId);
QString uniqueName() const;
- static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId);
QString fullDisplayName() const;
QString profile() const;
@@ -707,6 +706,7 @@ public:
QString id() const { return m_id; }
QString profile() const;
void makeModuleProvidersNonTransient();
+ QVariantMap fullProfileConfigsTree() const; // Tree-ified + overridden values
QVariantMap profileConfigs;
QVariantMap overriddenValues;
@@ -737,8 +737,6 @@ private:
bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1,
const std::vector<ArtifactPropertiesPtr> &l2);
-QString multiplexIdToString(const QString &id);
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp
index 1156b138b..00d944e5f 100644
--- a/src/lib/corelib/language/loader.cpp
+++ b/src/lib/corelib/language/loader.cpp
@@ -40,9 +40,10 @@
#include "loader.h"
#include "evaluator.h"
+#include "itempool.h"
#include "language.h"
-#include "moduleloader.h"
#include "projectresolver.h"
+#include "projecttreebuilder.h"
#include "scriptengine.h"
#include <logging/translator.h>
@@ -149,15 +150,16 @@ TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters
const FileTime resolveTime = FileTime::currentTime();
Evaluator evaluator(m_engine);
- ModuleLoader moduleLoader(&evaluator, m_logger);
- moduleLoader.setProgressObserver(m_progressObserver);
- moduleLoader.setSearchPaths(m_searchPaths);
- moduleLoader.setOldProjectProbes(m_oldProjectProbes);
- moduleLoader.setOldProductProbes(m_oldProductProbes);
- moduleLoader.setLastResolveTime(m_lastResolveTime);
- moduleLoader.setStoredProfiles(m_storedProfiles);
- moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo);
- const ModuleLoaderResult loadResult = moduleLoader.load(parameters);
+ ItemPool pool;
+ ProjectTreeBuilder projectTreeBuilder(parameters, pool, evaluator, m_logger);
+ projectTreeBuilder.setProgressObserver(m_progressObserver);
+ projectTreeBuilder.setSearchPaths(m_searchPaths);
+ projectTreeBuilder.setOldProjectProbes(m_oldProjectProbes);
+ projectTreeBuilder.setOldProductProbes(m_oldProductProbes);
+ projectTreeBuilder.setLastResolveTime(m_lastResolveTime);
+ projectTreeBuilder.setStoredProfiles(m_storedProfiles);
+ projectTreeBuilder.setStoredModuleProviderInfo(m_storedModuleProviderInfo);
+ const ProjectTreeBuilder::Result loadResult = projectTreeBuilder.load();
ProjectResolver resolver(&evaluator, loadResult, std::move(parameters), m_logger);
resolver.setProgressObserver(m_progressObserver);
TopLevelProjectPtr project = resolver.resolve();
diff --git a/src/lib/corelib/language/localprofiles.cpp b/src/lib/corelib/language/localprofiles.cpp
new file mode 100644
index 000000000..1571ee15e
--- /dev/null
+++ b/src/lib/corelib/language/localprofiles.cpp
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "localprofiles.h"
+
+#include "evaluator.h"
+#include "item.h"
+#include "qualifiedid.h"
+#include "scriptengine.h"
+#include "value.h"
+
+#include <logging/translator.h>
+#include <tools/profile.h>
+#include <tools/scripttools.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+class LocalProfiles::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger)
+ : parameters(parameters), evaluator(evaluator), logger(logger) {}
+
+ void handleProfile(Item *profileItem);
+ void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem,
+ QVariantMap &values);
+ void collectProfiles(Item *productOrProject, Item *projectScope);
+
+ const SetupProjectParameters &parameters;
+ Evaluator &evaluator;
+ Logger &logger;
+ QVariantMap profiles;
+};
+
+LocalProfiles::LocalProfiles(const SetupProjectParameters &parameters, Evaluator &evaluator,
+ Logger &logger)
+ : d(new Private(parameters, evaluator, logger)) {}
+LocalProfiles::~LocalProfiles() { delete d; }
+
+void LocalProfiles::collectProfilesFromItems(Item *productOrProject, Item *projectScope)
+{
+ d->collectProfiles(productOrProject, projectScope);
+}
+
+const QVariantMap &LocalProfiles::profiles() const
+{
+ return d->profiles;
+}
+
+void LocalProfiles::Private::handleProfile(Item *profileItem)
+{
+ QVariantMap values;
+ evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
+ const bool condition = values.take(StringConstants::conditionProperty()).toBool();
+ if (!condition)
+ return;
+ const QString profileName = values.take(StringConstants::nameProperty()).toString();
+ if (profileName.isEmpty())
+ throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location());
+ if (profileName == Profile::fallbackName()) {
+ throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
+ .arg(profileName), profileItem->location());
+ }
+ if (profiles.contains(profileName)) {
+ throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
+ profileItem->location());
+ }
+ profiles.insert(profileName, values);
+}
+
+void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
+ Item *profileItem, QVariantMap &values)
+{
+ const Item::PropertyMap &props = item->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ QualifiedId name = namePrefix;
+ name << it.key();
+ switch (it.value()->type()) {
+ case Value::ItemValueType:
+ evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(),
+ profileItem, values);
+ break;
+ case Value::VariantValueType:
+ values.insert(name.join(QLatin1Char('.')),
+ std::static_pointer_cast<VariantValue>(it.value())->value());
+ break;
+ case Value::JSSourceValueType:
+ if (item != profileItem)
+ item->setScope(profileItem);
+ const ScopedJsValue sv(evaluator.engine()->context(),
+ evaluator.value(item, it.key()));
+ values.insert(name.join(QLatin1Char('.')),
+ getJsVariant(evaluator.engine()->context(), sv));
+ break;
+ }
+ }
+}
+
+void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *projectScope)
+{
+ Item * scope = productOrProject->type() == ItemType::Project ? projectScope : nullptr;
+ for (auto it = productOrProject->children().begin();
+ it != productOrProject->children().end();) {
+ Item * const childItem = *it;
+ if (childItem->type() == ItemType::Profile) {
+ if (!scope) {
+ const ItemValuePtr itemValue = ItemValue::create(productOrProject);
+ scope = Item::create(productOrProject->pool(), ItemType::Scope);
+ scope->setProperty(StringConstants::productVar(), itemValue);
+ scope->setFile(productOrProject->file());
+ scope->setScope(projectScope);
+ }
+ childItem->setScope(scope);
+ try {
+ handleProfile(childItem);
+ } catch (const ErrorInfo &e) {
+ handlePropertyError(e, parameters, logger);
+ }
+ it = productOrProject->children().erase(it); // TODO: delete item and scope
+ } else {
+ if (childItem->type() == ItemType::Product)
+ collectProfiles(childItem, projectScope);
+ ++it;
+ }
+ }
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/localprofiles.h b/src/lib/corelib/language/localprofiles.h
new file mode 100644
index 000000000..3e6b77f4d
--- /dev/null
+++ b/src/lib/corelib/language/localprofiles.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QVariantMap>
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Evaluator;
+class Item;
+class Logger;
+
+// This class evaluates all Profile items encountered in the project tree and holds the results.
+class LocalProfiles
+{
+public:
+ LocalProfiles(const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger);
+ ~LocalProfiles();
+
+ void collectProfilesFromItems(Item *productOrProject, Item *projectScope);
+ const QVariantMap &profiles() const;
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
+
diff --git a/src/lib/corelib/language/moduleinstantiator.cpp b/src/lib/corelib/language/moduleinstantiator.cpp
new file mode 100644
index 000000000..91776213a
--- /dev/null
+++ b/src/lib/corelib/language/moduleinstantiator.cpp
@@ -0,0 +1,337 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "moduleinstantiator.h"
+
+#include "item.h"
+#include "itempool.h"
+#include "modulepropertymerger.h"
+#include "qualifiedid.h"
+#include "value.h"
+
+#include <logging/logger.h>
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <utility>
+
+namespace qbs::Internal {
+class ModuleInstantiator::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ ModulePropertyMerger &propertyMerger, Logger &logger)
+ : parameters(parameters), itemPool(itemPool), propertyMerger(propertyMerger),
+ logger(logger) {}
+
+ void overrideProperties(const Context &context);
+ void setupScope(const Context &context);
+ void exchangePlaceholderItem(Item *product, Item *loadingItem, const QString &loadingName,
+ Item *moduleItemForItemValues, const QualifiedId &moduleName, const QString &id,
+ bool isProductDependency, bool alreadyLoaded);
+ std::pair<const Item *, Item *>
+ getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName,
+ const QString &id, bool replace);
+
+ const SetupProjectParameters &parameters;
+ ItemPool &itemPool;
+ ModulePropertyMerger &propertyMerger;
+ Logger &logger;
+ qint64 elapsedTime = 0;
+};
+
+ModuleInstantiator::ModuleInstantiator(
+ const SetupProjectParameters &parameters, ItemPool &itemPool,
+ ModulePropertyMerger &propertyMerger, Logger &logger)
+ : d(new Private(parameters, itemPool, propertyMerger, logger)) {}
+ModuleInstantiator::~ModuleInstantiator() { delete d; }
+
+void ModuleInstantiator::instantiate(const Context &context)
+{
+ AccumulatingTimer timer(d->parameters.logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ // This part needs to be done only once per module and product, and only if the module
+ // was successfully loaded.
+ if (context.module && !context.alreadyLoaded) {
+ context.module->setType(ItemType::ModuleInstance);
+ d->overrideProperties(context);
+ d->setupScope(context);
+ }
+
+ // This strange-looking code deals with the fact that our syntax cannot properly handle
+ // dependencies on several multiplexed variants of the same product.
+ // See getOrSetModuleInstanceItem() below for details.
+ Item * const moduleItemForItemValues
+ = context.moduleWithSameName ? context.moduleWithSameName
+ : context.module;
+
+ // Now attach the module instance as an item value to the loading item, potentially
+ // evicting a previously attached placeholder item and merging its values into the instance.
+ // Note that we potentially do this twice, once for the actual loading item and once
+ // for the product item, if the two are different. The reason is this:
+ // For convenience, in the product item we allow attaching to properties from indirectly
+ // loaded modules. For instance:
+ // Product {
+ // Depends { name: "Qt.core" }
+ // cpp.cxxLanguageVersion: "c++20" // Works even though there is no Depends item for cpp
+ // }
+ // It's debatable whether that's a good feature, but it has been working (accidentally?)
+ // for a long time, and removing it now would break a lot of existing projects.
+ d->exchangePlaceholderItem(
+ context.product, context.loadingItem, context.loadingName, moduleItemForItemValues,
+ context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
+
+ if (!context.alreadyLoaded && context.product && context.product != context.loadingItem) {
+ d->exchangePlaceholderItem(
+ context.product, context.product, context.productName, moduleItemForItemValues,
+ context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
+ }
+}
+
+void ModuleInstantiator::Private::exchangePlaceholderItem(
+ Item *product, Item *loadingItem, const QString &loadingName, Item *moduleItemForItemValues,
+ const QualifiedId &moduleName, const QString &id, bool isProductModule, bool alreadyLoaded)
+{
+ // If we have a module item, set an item value pointing to it as a property on the loading item.
+ // Evict a possibly existing placeholder item, and return it to us, so we can merge its values
+ // into the instance.
+ const auto &[oldItem, newItem] = getOrSetModuleInstanceItem(
+ loadingItem, moduleItemForItemValues, moduleName, id, true);
+
+ // The new item always exists, even if we don't have a module item. In that case, the
+ // function created a placeholder item for us, which we then have to turn into a
+ // non-present module.
+ QBS_CHECK(newItem);
+ if (!moduleItemForItemValues) {
+ createNonPresentModule(itemPool, moduleName.toString(), QLatin1String("not found"),
+ newItem);
+ return;
+ }
+
+ // If the old and the new items are the same, it means the existing item value already
+ // pointed to a module instance (rather than a placeholder).
+ // This can happen in two cases:
+ // a) Multiple identical Depends items on the same level (easily possible with inheritance).
+ // b) Dependencies to multiplexed variants of the same product
+ // (see getOrSetModuleInstanceItem() below for details).
+ if (oldItem == newItem) {
+ QBS_CHECK(oldItem->type() == ItemType::ModuleInstance);
+ QBS_CHECK(alreadyLoaded || isProductModule);
+ return;
+ }
+
+ // In all other cases, our request to set the module instance item must have been honored.
+ QBS_CHECK(newItem == moduleItemForItemValues);
+
+ // If there was no placeholder item, we don't have to merge any values and are done.
+ if (!oldItem)
+ return;
+
+ // If an item was replaced, then it must have been a placeholder.
+ QBS_CHECK(oldItem->type() == ItemType::ModuleInstancePlaceholder);
+
+ // Prevent setting read-only properties.
+ for (auto it = oldItem->properties().cbegin(); it != oldItem->properties().cend(); ++it) {
+ const PropertyDeclaration &pd = moduleItemForItemValues->propertyDeclaration(it.key());
+ if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) {
+ throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
+ it.value()->location());
+ }
+ }
+
+ // Now merge the locally attached values into the actual module instance.
+ propertyMerger.mergeFromLocalInstance(product, loadingItem, loadingName,
+ oldItem, moduleItemForItemValues);
+
+ // TODO: We'd like to delete the placeholder item here, because it's not
+ // being referenced anymore and there's a lot of them. However, this
+ // is not supported by ItemPool. Investigate the use of std::pmr.
+}
+
+Item *ModuleInstantiator::retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name)
+{
+ return d->getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false).second;
+}
+
+Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
+{
+ return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule());
+}
+
+// This important function deals with retrieving and setting (pseudo-)module instances from/on
+// items.
+// Use case 1: Suppose we resolve the dependency cpp in module Qt.core, which also contains
+// property bindings for cpp such as "cpp.defines: [...]".
+// The "cpp" part of this binding is represented by an ItemValue whose
+// item is of type ModuleInstancePlaceholder, originally set up by ItemReaderASTVisitor.
+// This function will be called with the actual cpp module item and will
+// replace the placeholder item in the item value. It will also return
+// the placeholder item for subsequent merging of its properties with the
+// ones of the actual module (in ModulePropertyMerger::mergeFromLocalInstance()).
+// If there were no cpp property bindings defined in Qt.core, then we'd still
+// have to replace the placeholder item, because references to "cpp" on the
+// right-hand-side of other properties must refer to the module item.
+// This is the common use of this function as employed by resolveProdsucDependencies().
+// Note that if a product has dependencies on more than one variant of a multiplexed
+// product, these dependencies are competing for the item value property name,
+// i.e. this case is not properly supported by the syntax. You must therefore not
+// export properties from multiplexed products that will be different between the
+// variants. In this function, the condition manifests itself by a module instance
+// being encountered instead of a module instance placeholder, in which case
+// nothing is done at all.
+// Use case 2: We inject a fake qbs module into a project item, so qbs properties set in profiles
+// can be accessed in the project level. Doing this is discouraged, and the
+// functionality is kept mostly for backwards compatibility. The moduleItem
+// parameter is null in this case, and the item will be created by the function itself.
+// Use case 3: A temporary qbs module is attached to a product during low-level setup, namely
+// in product multiplexing and setting qbs.profile.
+// Use case 4: Module propagation to the the Group level.
+// In all cases, the first returned item is the existing one, and the second returned item
+// is the new one. Depending on the use case, they might be null and might also be the same item.
+std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInstanceItem(
+ Item *container, Item *moduleItem, const QualifiedId &moduleName, const QString &id,
+ bool replace)
+{
+ Item *instance = container;
+ const QualifiedId itemValueName
+ = !id.isEmpty() ? QualifiedId::fromString(id) : moduleName;
+ for (int i = 0; i < itemValueName.size(); ++i) {
+ const QString &moduleNameSegment = itemValueName.at(i);
+ const ValuePtr v = instance->ownProperty(itemValueName.at(i));
+ if (v && v->type() == Value::ItemValueType) {
+ ItemValue * const itemValue = std::static_pointer_cast<ItemValue>(v).get();
+ instance = itemValue->item();
+ if (i == itemValueName.size() - 1) {
+ if (replace && instance != moduleItem
+ && instance->type() == ItemType::ModuleInstancePlaceholder) {
+ if (!moduleItem)
+ moduleItem = Item::create(&itemPool, ItemType::ModuleInstancePlaceholder);
+ itemValue->setItem(moduleItem);
+ }
+ return {instance, itemValue->item()};
+ }
+ } else {
+ Item *newItem = i < itemValueName.size() - 1
+ ? Item::create(&itemPool, ItemType::ModulePrefix) : moduleItem
+ ? moduleItem : Item::create(&itemPool, ItemType::ModuleInstancePlaceholder);
+ instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
+ instance = newItem;
+ }
+ }
+ return {nullptr, instance};
+}
+
+void ModuleInstantiator::printProfilingInfo(int indent)
+{
+ if (!d->parameters.logElapsedTime())
+ return;
+ d->logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ')
+ << Tr::tr("Instantiating modules took %1.")
+ .arg(elapsedTimeString(d->elapsedTime));
+}
+
+void ModuleInstantiator::Private::overrideProperties(const ModuleInstantiator::Context &context)
+{
+ // Users can override module properties on the command line with the
+ // modules.<module-name>.<property-name>:<value> syntax.
+ // For simplicity and backwards compatibility, qbs properties can also be given without
+ // the "modules." prefix, i.e. just qbs.<property-name>:<value>.
+ // In addition, users can override module properties just for certain products
+ // using the products.<product-name>.<module-name>.<property-name>:<value> syntax.
+ // Such product-specific overrides have higher precedence.
+ const QString fullName = context.moduleName.toString();
+ const QString generalOverrideKey = QStringLiteral("modules.") + fullName;
+ const QString perProductOverrideKey = StringConstants::productsOverridePrefix()
+ + context.productName + QLatin1Char('.') + fullName;
+ context.module->overrideProperties(parameters.overriddenValuesTree(), generalOverrideKey,
+ parameters, logger);
+ if (fullName == StringConstants::qbsModule()) {
+ context.module->overrideProperties(parameters.overriddenValuesTree(), fullName, parameters,
+ logger);
+ }
+ context.module->overrideProperties(parameters.overriddenValuesTree(), perProductOverrideKey,
+ parameters, logger);
+}
+
+void ModuleInstantiator::Private::setupScope(const ModuleInstantiator::Context &context)
+{
+ Item * const scope = Item::create(&itemPool, ItemType::Scope);
+ QBS_CHECK(context.module->file());
+ scope->setFile(context.module->file());
+ QBS_CHECK(context.projectScope);
+ context.projectScope->copyProperty(StringConstants::projectVar(), scope);
+ if (context.productScope)
+ context.productScope->copyProperty(StringConstants::productVar(), scope);
+ else
+ QBS_CHECK(context.moduleName.toString() == StringConstants::qbsModule()); // Dummy product.
+
+ if (!context.module->id().isEmpty())
+ scope->setProperty(context.module->id(), ItemValue::create(context.module));
+ for (Item * const child : context.module->children()) {
+ child->setScope(scope);
+ if (!child->id().isEmpty())
+ scope->setProperty(child->id(), ItemValue::create(child));
+ }
+ context.module->setScope(scope);
+
+ if (context.exportingProduct) {
+ QBS_CHECK(context.exportingProduct->type() == ItemType::Product);
+
+ const auto exportingProductItemValue = ItemValue::create(context.exportingProduct);
+ scope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue);
+
+ const auto importingProductItemValue = ItemValue::create(context.product);
+ scope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue);
+
+ // FIXME: This looks wrong. Introduce exportingProject variable?
+ scope->setProperty(StringConstants::projectVar(),
+ ItemValue::create(context.exportingProduct->parent()));
+
+ PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(),
+ PropertyDeclaration::String, QString(),
+ PropertyDeclaration::PropertyNotAvailableInConfig);
+ context.module->setPropertyDeclaration(pd.name(), pd);
+ context.module->setProperty(pd.name(), context.exportingProduct->property(
+ StringConstants::sourceDirectoryProperty()));
+ }
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/moduleinstantiator.h b/src/lib/corelib/language/moduleinstantiator.h
new file mode 100644
index 000000000..f235b83fa
--- /dev/null
+++ b/src/lib/corelib/language/moduleinstantiator.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Item;
+class ItemPool;
+class Logger;
+class ModulePropertyMerger;
+class QualifiedId;
+
+// This class is responsible for setting up a proper module instance from a bunch of items:
+// - Set the item type to ItemType::ModuleInstance (from Module or Export).
+// - Apply possible command-line overrides for module properties.
+// - Replace a possible module instance placeholder in the loading item with the actual instance
+// and merge their values employing the ModulePropertyMerger.
+// - Setting up the module instance scope.
+// In addition, it also provides helper functions for retrieving/setting module instance items
+// for special purposes.
+class ModuleInstantiator
+{
+public:
+ ModuleInstantiator(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ ModulePropertyMerger &propertyMerger, Logger &logger);
+ ~ModuleInstantiator();
+
+ struct Context {
+ Item * const product;
+ const QString &productName;
+ Item * const loadingItem;
+ const QString &loadingName;
+ Item * const module;
+ Item * const moduleWithSameName;
+ Item * const exportingProduct;
+ Item * const productScope;
+ Item * const projectScope;
+ const QualifiedId &moduleName;
+ const QString &id;
+ const bool alreadyLoaded;
+ };
+ void instantiate(const Context &context);
+
+ // Note that these will also create the respective item value if it does not exist yet.
+ Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name);
+ Item *retrieveQbsItem(Item *containerItem);
+
+ void printProfilingInfo(int indent);
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
+
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp
index 1663fccac..6efb2ab4d 100644
--- a/src/lib/corelib/language/moduleloader.cpp
+++ b/src/lib/corelib/language/moduleloader.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
@@ -39,1490 +39,218 @@
#include "moduleloader.h"
-#include "builtindeclarations.h"
#include "evaluator.h"
-#include "filecontext.h"
-#include "item.h"
#include "itemreader.h"
-#include "language.h"
-#include "modulemerger.h"
-#include "moduleproviderloader.h"
-#include "probesresolver.h"
-#include "qualifiedid.h"
-#include "scriptengine.h"
#include "value.h"
#include <api/languageinfo.h>
-#include <language/language.h>
#include <logging/categories.h>
-#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
-#include <tools/fileinfo.h>
-#include <tools/preferences.h>
-#include <tools/profile.h>
-#include <tools/profiling.h>
-#include <tools/progressobserver.h>
-#include <tools/qbsassert.h>
-#include <tools/qttools.h>
-#include <tools/scripttools.h>
-#include <tools/settings.h>
-#include <tools/stlutils.h>
+#include <tools/hostosinfo.h>
+#include <tools/setupprojectparameters.h>
#include <tools/stringconstants.h>
-#include <QtCore/qdebug.h>
-#include <QtCore/qdir.h>
-#include <QtCore/qglobalstatic.h>
-#include <QtCore/qdiriterator.h>
-#include <QtCore/qjsondocument.h>
-#include <QtCore/qjsonobject.h>
-#include <QtCore/qtextstream.h>
-#include <QtCore/qthreadstorage.h>
-
-#include <algorithm>
-#include <memory>
+#include <unordered_map>
#include <utility>
-namespace qbs {
-namespace Internal {
-
-using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap> >;
-Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
-
-static bool multiplexConfigurationIntersects(const QVariantMap &lhs, const QVariantMap &rhs)
-{
- QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
-
- for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
- const auto rhsProperty = rhs.find(lhsProperty.key());
- const bool isCommonProperty = rhsProperty != rhs.constEnd();
- if (isCommonProperty && lhsProperty.value() != rhsProperty.value())
- return false;
- }
-
- return true;
-}
-
-class ModuleLoader::ItemModuleList : public QList<Item::Module> {};
+namespace qbs::Internal {
-class ModuleLoader::ProductSortByDependencies
+class ModuleLoader::Private
{
public:
- ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp)
- {
- }
-
- void apply()
- {
- QHash<QString, std::vector<ProductContext *>> productsMap;
- QList<ProductContext *> allProducts;
- for (ProjectContext * const projectContext : qAsConst(m_tlp.projects)) {
- for (auto &product : projectContext->products) {
- allProducts.push_back(&product);
- productsMap[product.name].push_back(&product);
- }
- }
- Set<ProductContext *> allDependencies;
- for (auto productContext : qAsConst(allProducts)) {
- auto &productDependencies = m_dependencyMap[productContext];
- for (const auto &dep : qAsConst(productContext->info.usedProducts)) {
- QBS_CHECK(!dep.name.isEmpty());
- const auto &deps = productsMap.value(dep.name);
- if (dep.profile == StringConstants::star()) {
- QBS_CHECK(!deps.empty());
- for (ProductContext *depProduct : deps) {
- if (depProduct == productContext)
- continue;
- productDependencies.push_back(depProduct);
- allDependencies << depProduct;
- }
- } else {
- auto it = std::find_if(deps.begin(), deps.end(), [&dep] (ProductContext *p) {
- return p->multiplexConfigurationId == dep.multiplexConfigurationId;
- });
- if (it == deps.end()) {
- QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty());
- const QString productName = ResolvedProduct::fullDisplayName(
- productContext->name, productContext->multiplexConfigurationId);
- const QString depName = ResolvedProduct::fullDisplayName(
- dep.name, dep.multiplexConfigurationId);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not "
- "fulfilled.").arg(productName, depName),
- productContext->item->location());
- }
- productDependencies.push_back(*it);
- allDependencies << *it;
- }
- }
- }
- const Set<ProductContext *> rootProducts
- = rangeTo<Set<ProductContext *>>(allProducts) - allDependencies;
- for (ProductContext * const rootProduct : rootProducts)
- traverse(rootProduct);
- if (m_sortedProducts.size() < allProducts.size()) {
- for (auto const product : qAsConst(allProducts)) {
- QList<ModuleLoader::ProductContext *> path;
- findCycle(product, path);
- }
- }
- QBS_CHECK(m_sortedProducts.size() == allProducts.size());
- }
-
- // No product at position i has dependencies to a product at position j > i.
- const QList<ProductContext *> &sortedProducts() const
- {
- return m_sortedProducts;
- }
-
-private:
- void traverse(ModuleLoader::ProductContext *product)
- {
- if (!m_seenProducts.insert(product).second)
- return;
- for (const auto &dependency : m_dependencyMap.value(product))
- traverse(dependency);
- m_sortedProducts << product;
- }
-
- void findCycle(ModuleLoader::ProductContext *product,
- QList<ModuleLoader::ProductContext *> &path)
- {
- if (path.contains(product)) {
- ErrorInfo error(Tr::tr("Cyclic dependencies detected."));
- for (const auto * const p : path)
- error.append(p->name, p->item->location());
- error.append(product->name, product->item->location());
- throw error;
- }
- path << product;
- for (auto const dep : m_dependencyMap.value(product))
- findCycle(dep, path);
- path.removeLast();
- }
-
- TopLevelProjectContext &m_tlp;
- QHash<ProductContext *, std::vector<ProductContext *>> m_dependencyMap;
- Set<ProductContext *> m_seenProducts;
- QList<ProductContext *> m_sortedProducts;
-};
-
-class SearchPathsManager {
-public:
- explicit SearchPathsManager(ItemReader *itemReader)
- : m_itemReader(itemReader)
- , m_oldSize(itemReader->extraSearchPathsStack().size())
- {
- }
- SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths)
- : SearchPathsManager(itemReader)
- {
- m_itemReader->pushExtraSearchPaths(extraSearchPaths);
- }
- ~SearchPathsManager()
- {
- reset();
- }
- void reset()
- {
- while (m_itemReader->extraSearchPathsStack().size() > m_oldSize)
- m_itemReader->popExtraSearchPaths();
- }
-
-private:
- ItemReader * const m_itemReader;
- size_t m_oldSize{0};
-};
-
-ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger)
- : m_pool(nullptr)
- , m_logger(logger)
- , m_progressObserver(nullptr)
- , m_reader(std::make_unique<ItemReader>(logger))
- , m_evaluator(evaluator)
- , m_probesResolver(std::make_unique<ProbesResolver>(m_evaluator, m_logger))
- , m_moduleProviderLoader(
- std::make_unique<ModuleProviderLoader>(m_reader.get(), m_evaluator, m_probesResolver.get(),
- m_logger))
-{
-}
-
-ModuleLoader::~ModuleLoader() = default;
-
-void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver)
-{
- m_progressObserver = progressObserver;
-}
-
-void ModuleLoader::setSearchPaths(const QStringList &searchPaths)
-{
- m_reader->setSearchPaths(searchPaths);
- qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths;
-}
-
-void ModuleLoader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- m_probesResolver->setOldProjectProbes(oldProbes);
-}
-
-void ModuleLoader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- m_probesResolver->setOldProductProbes(oldProbes);
-}
-
-void ModuleLoader::setStoredProfiles(const QVariantMap &profiles)
-{
- m_storedProfiles = profiles;
-}
-
-void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo)
-{
- m_moduleProviderLoader->setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
-{
- TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"),
- parameters.logElapsedTime());
- qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath();
- m_parameters = parameters;
- m_modulePrototypes.clear();
- m_modulePrototypeEnabledInfo.clear();
- m_parameterDeclarations.clear();
- m_disabledItems.clear();
- m_reader->setDeprecationWarningMode(parameters.deprecationWarningMode());
- m_reader->clearExtraSearchPathsStack();
- m_reader->setEnableTiming(parameters.logElapsedTime());
- m_moduleProviderLoader->setProjectParameters(m_parameters);
- m_probesResolver->setProjectParameters(m_parameters);
- m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts
- = m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies
- = m_elapsedTimePropertyChecking = 0;
- m_elapsedTimeModuleProviders = 0;
- m_settings = std::make_unique<Settings>(parameters.settingsDirectory());
-
- const auto keys = m_parameters.overriddenValues().keys();
- for (const QString &key : keys) {
- static const QStringList prefixes({ StringConstants::projectPrefix(),
- QStringLiteral("projects"),
- QStringLiteral("products"), QStringLiteral("modules"),
- StringConstants::moduleProviders(),
- StringConstants::qbsModule()});
- bool ok = false;
- for (const auto &prefix : prefixes) {
- if (key.startsWith(prefix + QLatin1Char('.'))) {
- ok = true;
- break;
- }
- }
- 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"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>."
- "<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>."
- "<property-name>:value"));
- handlePropertyError(e, m_parameters, m_logger);
- }
-
- ModuleLoaderResult result;
- result.profileConfigs = m_storedProfiles;
- m_pool = result.itemPool.get();
- m_reader->setPool(m_pool);
-
- const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree()
- .value(StringConstants::projectPrefix()).toMap()
- .value(StringConstants::qbsSearchPathsProperty()).toStringList();
- Item *root;
- {
- SearchPathsManager searchPathsManager(m_reader.get(), topLevelSearchPaths);
- root = loadItemFromFile(parameters.projectFilePath(), CodeLocation());
- if (!root)
- return ModuleLoaderResult();
- }
-
- switch (root->type()) {
- case ItemType::Product:
- root = wrapInProjectIfNecessary(root);
- break;
- case ItemType::Project:
- break;
- default:
- throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it"
- " is of type '%1'.").arg(root->typeName()), root->location());
- }
-
- const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(),
- TopLevelProject::deriveId(parameters.finalBuildConfigurationTree()));
- root->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath()));
- root->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(buildDirectory));
- root->setProperty(StringConstants::profileProperty(),
- VariantValue::create(m_parameters.topLevelProfile()));
- handleTopLevelProject(&result, root, buildDirectory,
- Set<QString>() << QDir::cleanPath(parameters.projectFilePath()));
- result.root = root;
- result.qbsFiles = m_reader->filesRead() - m_moduleProviderLoader->tempQbsFiles();
- for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it)
- result.profileConfigs.remove(it.key());
- printProfilingInfo();
- return result;
-}
-
-class PropertyDeclarationCheck : public ValueHandler
-{
- const Set<Item *> &m_disabledItems;
- Set<Item *> m_handledItems;
- std::vector<Item *> m_parentItems;
- Item *m_currentModuleInstance = nullptr;
- QualifiedId m_currentModuleName;
- QString m_currentName;
- SetupProjectParameters m_params;
- Logger &m_logger;
-public:
- PropertyDeclarationCheck(const Set<Item *> &disabledItems,
- SetupProjectParameters params, Logger &logger)
- : m_disabledItems(disabledItems)
- , m_params(std::move(params))
- , m_logger(logger)
- {
- }
-
- void operator()(Item *item)
- {
- handleItem(item);
- }
-
-private:
- void handle(JSSourceValue *value) override
- {
- if (!value->createdByPropertiesBlock()) {
- const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
- .arg(m_currentName), value->location());
- handlePropertyError(error, m_params, m_logger);
- }
- }
-
- void handle(ItemValue *value) override
- {
- if (checkItemValue(value))
- handleItem(value->item());
- }
-
- bool checkItemValue(ItemValue *value)
- {
- // TODO: Remove once QBS-1030 is fixed.
- if (parentItem()->type() == ItemType::Artifact)
- return false;
-
- if (parentItem()->type() == ItemType::Properties)
- return false;
-
- if (parentItem()->isOfTypeOrhasParentOfType(ItemType::Export)) {
- // Export item prototypes do not have instantiated modules.
- // The module instances are where the Export is used.
- QBS_ASSERT(m_currentModuleInstance, return false);
- auto hasCurrentModuleName = [this](const Item::Module &m) {
- return m.name == m_currentModuleName;
- };
- if (any_of(m_currentModuleInstance->modules(), hasCurrentModuleName))
- return true;
- }
-
- // TODO: We really should have a dedicated item type for "pre-instantiated" item values
- // and only use ModuleInstance for actual module instances.
- const bool itemIsModuleInstance = value->item()->type() == ItemType::ModuleInstance
- && value->item()->hasProperty(StringConstants::presentProperty());
-
- if (!itemIsModuleInstance
- && value->item()->type() != ItemType::ModulePrefix
- && (!parentItem()->file() || !parentItem()->file()->idScope()
- || !parentItem()->file()->idScope()->hasProperty(m_currentName))
- && !value->createdByPropertiesBlock()) {
- CodeLocation location = value->location();
- for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i)
- location = m_parentItems.at(i)->location();
- const ErrorInfo error(Tr::tr("Item '%1' is not declared. "
- "Did you forget to add a Depends item?")
- .arg(m_currentModuleName.toString()), location);
- handlePropertyError(error, m_params, m_logger);
- return false;
- }
-
- return true;
- }
-
- void handleItem(Item *item)
- {
- if (!m_handledItems.insert(item).second)
- return;
- if (m_disabledItems.contains(item)
- || (item->type() == ItemType::ModuleInstance && !item->isPresentModule())
- || item->type() == ItemType::Properties
-
- // The Properties child of a SubProject item is not a regular item.
- || item->type() == ItemType::PropertiesInSubProject) {
- return;
- }
-
- // If a module was found but its validate script failed, only the canonical
- // module instance will have the "non-present" flag set, so we need to locate it.
- if (item->type() == ItemType::ModuleInstance) {
- const Item *productItem = nullptr;
- for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) {
- if ((*it)->type() == ItemType::Product) {
- productItem = *it;
- break;
- }
- }
- if (productItem) {
- for (const Item::Module &m : productItem->modules()) {
- if (m.name == m_currentModuleName) {
- if (!m.item->isPresentModule())
- return;
- break;
- }
- }
- }
- }
-
- m_parentItems.push_back(item);
- for (Item::PropertyMap::const_iterator it = item->properties().constBegin();
- it != item->properties().constEnd(); ++it) {
- if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders()
- && it.value()->type() == Value::ItemValueType)
- continue;
- const PropertyDeclaration decl = item->propertyDeclaration(it.key());
- if (decl.isValid()) {
- const ErrorInfo deprecationError = decl.checkForDeprecation(
- m_params.deprecationWarningMode(), it.value()->location(), m_logger);
- if (deprecationError.hasError())
- handlePropertyError(deprecationError, m_params, m_logger);
- continue;
- }
- m_currentName = it.key();
- const QualifiedId oldModuleName = m_currentModuleName;
- if (parentItem()->type() != ItemType::ModulePrefix)
- m_currentModuleName.clear();
- m_currentModuleName.push_back(m_currentName);
- it.value()->apply(this);
- m_currentModuleName = oldModuleName;
- }
- m_parentItems.pop_back();
- for (Item * const child : item->children()) {
- switch (child->type()) {
- case ItemType::Export:
- case ItemType::Depends:
- case ItemType::Parameter:
- case ItemType::Parameters:
- break;
- case ItemType::Group:
- if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance)
- break;
- Q_FALLTHROUGH();
- default:
- handleItem(child);
- }
- }
-
- // Properties that don't refer to an existing module with a matching Depends item
- // only exist in the prototype of an Export item, not in the instance.
- // Example 1 - setting a property of an unknown module: Export { abc.def: true }
- // Example 2 - setting a non-existing Export property: Export { blubb: true }
- if (item->type() == ItemType::ModuleInstance && item->prototype()) {
- Item *oldInstance = m_currentModuleInstance;
- m_currentModuleInstance = item;
- handleItem(item->prototype());
- m_currentModuleInstance = oldInstance;
- }
- }
-
- void handle(VariantValue *) override { /* only created internally - no need to check */ }
-
- Item *parentItem() const { return m_parentItems.back(); }
+ Private(const SetupProjectParameters &setupParameters, ItemReader &itemReader,
+ Evaluator &evaluator, Logger &logger)
+ : setupParameters(setupParameters), itemReader(itemReader), evaluator(evaluator),
+ logger(logger) {}
+
+ std::pair<Item *, bool> getModulePrototype(const ModuleLoader::ProductContext &product,
+ const QString &moduleName, const QString &filePath);
+ bool evaluateModuleCondition(const ModuleLoader::ProductContext &product, Item *module,
+ const QString &fullModuleName);
+ void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
+ const Item::Modules &modules);
+
+ const SetupProjectParameters &setupParameters;
+ ItemReader &itemReader;
+ Evaluator &evaluator;
+ Logger &logger;
+
+ // The keys are file paths, the values are module prototype items accompanied by a profile.
+ std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> modulePrototypes;
+
+ // The keys are module prototypes and products, the values specify whether the module's
+ // condition is true for that product.
+ std::unordered_map<std::pair<Item *, const Item *>, bool> modulePrototypeEnabledInfo;
+
+ std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors;
+ std::unordered_map<const Item *, Item::PropertyDeclarationMap> parameterDeclarations;
};
-void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
- const QString &buildDirectory, const Set<QString> &referencedFilePaths)
-{
- TopLevelProjectContext tlp;
- tlp.buildDirectory = buildDirectory;
- handleProject(loadResult, &tlp, projectItem, referencedFilePaths);
- checkProjectNamesInOverrides(tlp);
- collectProductsByName(tlp);
- checkProductNamesInOverrides();
-
- adjustDependenciesForMultiplexing(tlp);
-
- m_dependencyResolvingPass = 1;
- for (ProjectContext * const projectContext : qAsConst(tlp.projects)) {
- m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack);
- for (ProductContext &productContext : projectContext->products) {
- try {
- setupProductDependencies(&productContext, Set<DeferredDependsContext>());
- } catch (const ErrorInfo &err) {
- if (productContext.name.isEmpty())
- throw err;
- handleProductError(err, &productContext);
- }
- // extraSearchPathsStack is changed during dependency resolution, check
- // that we've rolled back all the changes
- QBS_CHECK(m_reader->extraSearchPathsStack() == projectContext->searchPathsStack);
- }
- }
- if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) {
- collectProductsByType(tlp);
- m_dependencyResolvingPass = 2;
-
- // Doing the normalization for the Export items themselves (as opposed to doing it only
- // for the corresponding module instances) serves two purposes:
- // (1) It makes recursive use of Depends.productTypes via Export items work; otherwise,
- // we'd need an additional dependency resolving pass for every export level.
- // (2) The "expanded" Depends items are available to the Exporter.qbs module.
- for (Item * const exportItem : m_exportsWithDeferredDependsItems)
- normalizeDependencies(nullptr, DeferredDependsContext(nullptr, exportItem));
-
- for (const auto &deferredDependsData : m_productsWithDeferredDependsItems) {
- ProductContext * const productContext = deferredDependsData.first;
- m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack);
- try {
- setupProductDependencies(productContext, deferredDependsData.second);
- } catch (const ErrorInfo &err) {
- handleProductError(err, productContext);
- }
- }
- }
-
- ProductSortByDependencies productSorter(tlp);
- productSorter.apply();
- for (ProductContext * const p : productSorter.sortedProducts()) {
- try {
- handleProduct(p);
- if (p->name.startsWith(StringConstants::shadowProductPrefix()))
- tlp.probes << p->info.probes;
- } catch (const ErrorInfo &err) {
- handleProductError(err, p);
- }
- }
+ModuleLoader::ModuleLoader(const SetupProjectParameters &setupParameters, ItemReader &itemReader,
+ Evaluator &evaluator, Logger &logger)
+ : d(new Private(setupParameters, itemReader, evaluator, logger)) { }
- loadResult->projectProbes = tlp.probes;
- loadResult->storedModuleProviderInfo = m_moduleProviderLoader->storedModuleProviderInfo();
-
- m_reader->clearExtraSearchPathsStack();
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimePropertyChecking : nullptr);
- PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger);
- check(projectItem);
-}
+ModuleLoader::~ModuleLoader() { delete d; }
-void ModuleLoader::handleProject(ModuleLoaderResult *loadResult,
- TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths)
+std::pair<Item *, bool> ModuleLoader::loadModuleFile(
+ const ProductContext &product, const QString &moduleName, const QString &filePath)
{
- auto p = std::make_unique<ProjectContext>();
- auto &projectContext = *p;
- projectContext.topLevelProject = topLevelProjectContext;
- projectContext.result = loadResult;
- ItemValuePtr itemValue = ItemValue::create(projectItem);
- projectContext.scope = Item::create(m_pool, ItemType::Scope);
- projectContext.scope->setFile(projectItem->file());
- projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
- ProductContext dummyProductContext;
- dummyProductContext.project = &projectContext;
- dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree();
- projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem));
- overrideItemProperties(projectItem, StringConstants::projectPrefix(),
- m_parameters.overriddenValuesTree());
- projectContext.name = m_evaluator->stringValue(projectItem,
- StringConstants::nameProperty());
- if (projectContext.name.isEmpty()) {
- projectContext.name = FileInfo::baseName(projectItem->location().filePath());
- projectItem->setProperty(StringConstants::nameProperty(),
- VariantValue::create(projectContext.name));
- }
- overrideItemProperties(projectItem,
- StringConstants::projectsOverridePrefix() + projectContext.name,
- m_parameters.overriddenValuesTree());
- if (!checkItemCondition(projectItem)) {
- m_disabledProjects.insert(projectContext.name);
- return;
- }
- topLevelProjectContext->projects.push_back(p.release());
- SearchPathsManager searchPathsManager(m_reader.get(), readExtraSearchPaths(projectItem)
- << projectItem->file()->dirPath());
- projectContext.searchPathsStack = m_reader->extraSearchPathsStack();
- projectContext.item = projectItem;
-
- const QString minVersionStr
- = m_evaluator->stringValue(projectItem, StringConstants::minimumQbsVersionProperty(),
- QStringLiteral("1.3.0"));
- const Version minVersion = Version::fromString(minVersionStr);
- if (!minVersion.isValid()) {
- throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion "
- "is not a valid version string.").arg(minVersionStr), projectItem->location());
- }
- if (!m_qbsVersion.isValid())
- m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION));
- if (m_qbsVersion < minVersion) {
- throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but "
- "this is qbs version %2.").arg(minVersion.toString(),
- m_qbsVersion.toString()));
- }
-
- for (Item * const child : projectItem->children())
- child->setScope(projectContext.scope);
-
- m_probesResolver->resolveProbes(&dummyProductContext, projectItem);
- projectContext.topLevelProject->probes << dummyProductContext.info.probes;
+ qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath;
- handleProfileItems(projectItem, &projectContext);
-
- QList<Item *> multiplexedProducts;
- for (Item * const child : projectItem->children()) {
- if (child->type() == ItemType::Product)
- multiplexedProducts << multiplexProductItem(&dummyProductContext, child);
- }
- for (Item * const additionalProductItem : qAsConst(multiplexedProducts))
- Item::addChild(projectItem, additionalProductItem);
-
- const QList<Item *> originalChildren = projectItem->children();
- for (Item * const child : originalChildren) {
- switch (child->type()) {
- case ItemType::Product:
- prepareProduct(&projectContext, child);
- break;
- case ItemType::SubProject:
- handleSubProject(&projectContext, child, referencedFilePaths);
- break;
- case ItemType::Project:
- copyProperties(projectItem, child);
- handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths);
- break;
- default:
- break;
- }
- }
+ const auto [module, triedToLoad] =
+ d->getModulePrototype(product, moduleName, filePath);
+ if (!module)
+ return {nullptr, triedToLoad};
- const QStringList refs = m_evaluator->stringListValue(
- projectItem, StringConstants::referencesProperty());
- const CodeLocation referencingLocation
- = projectItem->property(StringConstants::referencesProperty())->location();
- QList<Item *> additionalProjectChildren;
- for (const QString &filePath : refs) {
- try {
- additionalProjectChildren << loadReferencedFile(filePath, referencingLocation,
- referencedFilePaths, dummyProductContext);
- } catch (const ErrorInfo &error) {
- if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- m_logger.printWarning(error);
- }
- }
- for (Item * const subItem : qAsConst(additionalProjectChildren)) {
- Item::addChild(projectContext.item, subItem);
- switch (subItem->type()) {
- case ItemType::Product:
- prepareProduct(&projectContext, subItem);
- break;
- case ItemType::Project:
- copyProperties(projectItem, subItem);
- handleProject(loadResult, topLevelProjectContext, subItem,
- Set<QString>(referencedFilePaths) << subItem->file()->filePath());
- break;
- default:
- break;
- }
+ const auto key = std::make_pair(module, product.item);
+ const auto it = d->modulePrototypeEnabledInfo.find(key);
+ if (it != d->modulePrototypeEnabledInfo.end()) {
+ qCDebug(lcModuleLoader) << "prototype cache hit (level 2)";
+ return {it->second ? module : nullptr, triedToLoad};
}
-}
-QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const
-{
- const auto &mprow = table.at(row);
- QVariantMap multiplexConfiguration;
- for (size_t column = 0; column < mprow.size(); ++column) {
- const QString &propertyName = properties.at(column);
- const VariantValuePtr &mpvalue = mprow.at(column);
- multiplexConfiguration.insert(propertyName, mpvalue->value());
- }
- QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration)
- .toJson(QJsonDocument::Compact)
- .toBase64());
- // Cache for later use in:multiplexIdToVariantMap()
- multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
- return id;
-}
-
-QVariantMap ModuleLoader::MultiplexInfo::multiplexIdToVariantMap(const QString &multiplexId)
-{
- if (multiplexId.isEmpty())
- return QVariantMap();
-
- QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId);
- // We assume that MultiplexInfo::toIdString() has been called for this
- // particular multiplex configuration.
- QBS_CHECK(!result.isEmpty());
- return result;
-}
-
-void qbs::Internal::ModuleLoader::ModuleLoader::dump(const ModuleLoader::MultiplexInfo &mpi)
-{
- QStringList header;
- for (const auto &str : mpi.properties)
- header << str;
- qDebug() << header;
-
- for (const auto &row : mpi.table) {
- QVariantList values;
- for (const auto &elem : row) {
- values << elem->value();
- }
- qDebug() << values;
+ if (!d->evaluateModuleCondition(product, module, moduleName)) {
+ qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false";
+ d->modulePrototypeEnabledInfo.insert({key, false});
+ return {nullptr, triedToLoad};
}
-}
-ModuleLoader::MultiplexTable ModuleLoader::combine(const MultiplexTable &table,
- const MultiplexRow &values)
-{
- MultiplexTable result;
- if (table.empty()) {
- result.resize(values.size());
- for (size_t i = 0; i < values.size(); ++i) {
- MultiplexRow row;
- row.resize(1);
- row[0] = values.at(i);
- result[i] = row;
- }
+ if (moduleName == StringConstants::qbsModule()) {
+ module->setProperty(QStringLiteral("hostPlatform"),
+ VariantValue::create(HostOsInfo::hostOSIdentifier()));
+ module->setProperty(QStringLiteral("hostArchitecture"),
+ VariantValue::create(HostOsInfo::hostOSArchitecture()));
+ module->setProperty(QStringLiteral("libexecPath"),
+ VariantValue::create(d->setupParameters.libexecPath()));
+
+ const Version qbsVersion = LanguageInfo::qbsVersion();
+ module->setProperty(QStringLiteral("versionMajor"),
+ VariantValue::create(qbsVersion.majorVersion()));
+ module->setProperty(QStringLiteral("versionMinor"),
+ VariantValue::create(qbsVersion.minorVersion()));
+ module->setProperty(QStringLiteral("versionPatch"),
+ VariantValue::create(qbsVersion.patchLevel()));
} else {
- for (const auto &row : table) {
- for (const auto &value : values) {
- MultiplexRow newRow = row;
- newRow.push_back(value);
- result.push_back(newRow);
- }
- }
- }
- return result;
-}
-
-ModuleLoader::MultiplexInfo ModuleLoader::extractMultiplexInfo(Item *productItem,
- Item *qbsModuleItem)
-{
- static const QString mpmKey = QStringLiteral("multiplexMap");
-
- JSContext * const ctx = m_evaluator->engine()->context();
- const ScopedJsValue multiplexMap(ctx, m_evaluator->value(qbsModuleItem, mpmKey));
- const QStringList multiplexByQbsProperties = m_evaluator->stringListValue(
- productItem, StringConstants::multiplexByQbsPropertiesProperty());
-
- MultiplexInfo multiplexInfo;
- multiplexInfo.aggregate = m_evaluator->boolValue(
- productItem, StringConstants::aggregateProperty());
-
- const QString multiplexedType = m_evaluator->stringValue(
- productItem, StringConstants::multiplexedTypeProperty());
- if (!multiplexedType.isEmpty())
- multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
-
- Set<QString> uniqueMultiplexByQbsProperties;
- for (const QString &key : multiplexByQbsProperties) {
- const QString mappedKey = getJsStringProperty(ctx, multiplexMap, key);
- if (mappedKey.isEmpty())
- throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key));
-
- if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) {
- throw ErrorInfo(Tr::tr("Duplicate entry '%1' in Product.%2.")
- .arg(mappedKey, StringConstants::multiplexByQbsPropertiesProperty()),
- productItem->location());
- }
-
- const ScopedJsValue arr(ctx, m_evaluator->value(qbsModuleItem, key));
- if (JS_IsUndefined(arr))
- continue;
- if (!JS_IsArray(ctx, arr))
- throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key));
-
- const quint32 arrlen = getJsIntProperty(ctx, arr, StringConstants::lengthProperty());
- if (arrlen == 0)
- continue;
-
- MultiplexRow mprow;
- mprow.resize(arrlen);
- QVariantList entriesForKey;
- for (quint32 i = 0; i < arrlen; ++i) {
- const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, arr, i));
- const QVariant value = getJsVariant(ctx, sv);
- if (entriesForKey.contains(value)) {
- throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.")
- .arg(value.toString(), key), productItem->location());
- }
- entriesForKey << value;
- mprow[i] = VariantValue::create(value);
- }
- multiplexInfo.table = combine(multiplexInfo.table, mprow);
- multiplexInfo.properties.push_back(mappedKey);
- }
- return multiplexInfo;
-}
-
-template <typename T, typename F>
-T ModuleLoader::callWithTemporaryBaseModule(ProductContext *productContext, const F &func)
-{
- // Temporarily attach the qbs module here, in case we need to access one of its properties
- // to evaluate properties.
- const QString &qbsKey = StringConstants::qbsModule();
- Item *productItem = productContext->item;
- ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later.
- if (qbsValue)
- qbsValue = qbsValue->clone();
- const Item::Module qbsModule = loadBaseModule(productContext, productItem);
- productItem->addModule(qbsModule);
-
- auto &&result = func(qbsModule);
-
- // "Unload" the qbs module again.
- if (qbsValue)
- productItem->setProperty(qbsKey, qbsValue);
- else
- productItem->removeProperty(qbsKey);
- productItem->removeModules();
-
- return std::forward<T>(result);
-}
-
-QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem)
-{
- QString productName;
- dummyContext->item = productItem;
- auto extractMultiplexInfoFromProduct
- = [this, productItem, &productName](const Item::Module &qbsModule) {
- // Overriding the product item properties must be done here already, because multiplexing
- // properties might depend on product properties.
- const QString &nameKey = StringConstants::nameProperty();
- productName = m_evaluator->stringValue(productItem, nameKey);
- if (productName.isEmpty()) {
- productName = FileInfo::completeBaseName(productItem->file()->filePath());
- productItem->setProperty(nameKey, VariantValue::create(productName));
- }
- overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName,
- m_parameters.overriddenValuesTree());
-
- return extractMultiplexInfo(productItem, qbsModule.item);
- };
- const auto multiplexInfo
- = callWithTemporaryBaseModule<const MultiplexInfo>(dummyContext,
- extractMultiplexInfoFromProduct);
-
- if (multiplexInfo.table.size() > 1)
- productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
-
- VariantValuePtr productNameValue = VariantValue::create(productName);
-
- Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
- QList<Item *> additionalProductItems;
- std::vector<VariantValuePtr> multiplexConfigurationIdValues;
- for (size_t row = 0; row < multiplexInfo.table.size(); ++row) {
- Item *item = productItem;
- const auto &mprow = multiplexInfo.table.at(row);
- QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
- if (row > 0) {
- item = productItem->clone();
- additionalProductItems.push_back(item);
- }
- const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
- const VariantValuePtr multiplexConfigurationIdValue
- = VariantValue::create(multiplexConfigurationId);
- if (multiplexInfo.table.size() > 1 || aggregator) {
- multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue);
- item->setProperty(StringConstants::multiplexConfigurationIdProperty(),
- multiplexConfigurationIdValue);
- }
- if (multiplexInfo.multiplexedType)
- item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
- for (size_t column = 0; column < mprow.size(); ++column) {
- Item *qbsItem = moduleInstanceItem(item, StringConstants::qbsModule());
- const QString &propertyName = multiplexInfo.properties.at(column);
- const VariantValuePtr &mpvalue = mprow.at(column);
- qbsItem->setProperty(propertyName, mpvalue);
- }
- }
-
- if (aggregator) {
- additionalProductItems << aggregator;
-
- // Add dependencies to all multiplexed instances.
- for (const auto &v : multiplexConfigurationIdValues) {
- Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
- dependsItem->setProperty(StringConstants::nameProperty(), productNameValue);
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdProperty(), v);
- dependsItem->setProperty(StringConstants::profilesProperty(),
- VariantValue::create(QStringList()));
- dependsItem->setFile(aggregator->file());
- dependsItem->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item::addChild(aggregator, dependsItem);
- }
- }
-
- return additionalProductItems;
-}
-
-void ModuleLoader::normalizeDependencies(ProductContext *product,
- const DeferredDependsContext &dependsContext)
-{
- std::vector<Item *> dependsItemsToAdd;
- std::vector<Item *> dependsItemsToRemove;
- std::vector<Item *> deferredDependsItems;
- for (Item *dependsItem : dependsContext.parentItem->children()) {
- if (dependsItem->type() != ItemType::Depends)
- continue;
- bool productTypesIsSet;
- const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem,
- StringConstants::productTypesProperty(), &productTypesIsSet);
- if (productTypesIsSet) {
- bool nameIsSet;
- m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), QString(),
- &nameIsSet);
-
- // The second condition is for the case where the dependency comes from an Export item
- // that has itself been normalized in the mean time.
- if (nameIsSet && !dependsItem->variantProperty(StringConstants::nameProperty())) {
- throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually "
- "exclusive."), dependsItem->location());
- }
-
- bool submodulesPropertySet;
- m_evaluator->stringListValue( dependsItem, StringConstants::submodulesProperty(),
- &submodulesPropertySet);
- if (submodulesPropertySet) {
- throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are "
- "mutually exclusive."), dependsItem->location());
- }
-
- // We ignore the "limitToSubProject" property for dependencies from Export items,
- // because we cannot make it work consistently, as the importing product is not
- // yet known when normalizing via an Export item.
- const bool limitToSubProject = dependsContext.parentItem->type() == ItemType::Product
- && m_evaluator->boolValue(dependsItem,
- StringConstants::limitToSubProjectProperty());
- static const auto hasSameSubProject
- = [](const ProductContext &product, const ProductContext &other) {
- for (const Item *otherParent = other.item->parent(); otherParent;
- otherParent = otherParent->parent()) {
- if (otherParent == product.item->parent())
- return true;
- }
- return false;
- };
- std::vector<const ProductContext *> matchingProducts;
- for (const FileTag &typeTag : productTypes) {
- const auto range = m_productsByType.equal_range(typeTag);
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second != product
- && (!product || it->second->name != product->name)
- && (!limitToSubProject || hasSameSubProject(*product, *it->second))) {
- matchingProducts.push_back(it->second);
- }
- }
- }
- if (matchingProducts.empty()) {
- qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
- << dependsItem->location();
- dependsItemsToRemove.push_back(dependsItem);
+ Item::PropertyDeclarationMap decls;
+ const auto &moduleChildren = module->children();
+ for (Item *param : moduleChildren) {
+ if (param->type() != ItemType::Parameter)
continue;
- }
- if (dependsContext.parentItem->type() != ItemType::Export)
- deferredDependsItems.push_back(dependsItem);
- for (std::size_t i = 1; i < matchingProducts.size(); ++i) {
- Item * const dependsClone = dependsItem->clone();
- dependsClone->setProperty(StringConstants::nameProperty(),
- VariantValue::create(matchingProducts.at(i)->name));
- dependsItemsToAdd.push_back(dependsClone);
- if (dependsContext.parentItem->type() != ItemType::Export)
- deferredDependsItems.push_back(dependsClone);
-
- }
- dependsItem->setProperty(StringConstants::nameProperty(),
- VariantValue::create(matchingProducts.front()->name));
+ const auto &paramDecls = param->propertyDeclarations();
+ for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
+ decls.insert(it.key(), it.value());
}
+ d->parameterDeclarations.insert({module, decls});
}
- for (Item * const newDependsItem : dependsItemsToAdd)
- Item::addChild(dependsContext.parentItem, newDependsItem);
- for (Item * const dependsItem : dependsItemsToRemove)
- Item::removeChild(dependsContext.parentItem, dependsItem);
- if (!deferredDependsItems.empty()) {
- auto &allDeferredDependsItems
- = product->deferredDependsItems[dependsContext.exportingProductItem];
- allDeferredDependsItems.insert(allDeferredDependsItems.end(), deferredDependsItems.cbegin(),
- deferredDependsItems.cend());
- }
-}
-
-void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp)
-{
- for (const ProjectContext * const project : tlp.projects) {
- for (const ProductContext &product : project->products)
- adjustDependenciesForMultiplexing(product);
- }
-}
-void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product)
-{
- for (Item *dependsItem : product.item->children()) {
- if (dependsItem->type() == ItemType::Depends)
- adjustDependenciesForMultiplexing(product, dependsItem);
- }
-}
-
-void ModuleLoader::adjustDependenciesForMultiplexing(const ProductContext &product,
- Item *dependsItem)
-{
- const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty());
- const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty();
- if (name == product.name) {
- QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
- return;
- }
-
- bool profilesPropertyIsSet;
- const QStringList profiles = m_evaluator->stringListValue(dependsItem,
- StringConstants::profilesProperty(), &profilesPropertyIsSet);
-
- const auto productRange = m_productsByName.equal_range(name);
- if (productRange.first == productRange.second) {
- // Dependency is a module. Nothing to adjust.
- return;
- }
-
- std::vector<const ProductContext *> multiplexedDependencies;
- bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty())
- multiplexedDependencies.push_back(it->second);
- else
- hasNonMultiplexedDependency = true;
- }
- bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
-
- // These are the allowed cases:
- // (1) Normal dependency with no multiplexing whatsoever.
- // (2) Both product and dependency are multiplexed.
- // (2a) The profiles property is not set, we want to depend on the best
- // matching variant.
- // (2b) The profiles property is set, we want to depend on all variants
- // with a matching profile.
- // (3) The product is not multiplexed, but the dependency is.
- // (3a) The profiles property is not set, the dependency has an aggregator.
- // We want to depend on the aggregator.
- // (3b) The profiles property is not set, the dependency does not have an
- // aggregator. We want to depend on all the multiplexed variants.
- // (3c) The profiles property is set, we want to depend on all variants
- // with a matching profile regardless of whether an aggregator exists or not.
- // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
- // any Depends items.
- // (5) The product is a "shadow product". In that case, we know which product
- // it should have a dependency on, and we make sure we depend on that.
-
- // (1) and (4)
- if (!hasMultiplexedDependencies)
- return;
-
- // (3a)
- if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
- return;
-
- QStringList multiplexIds;
- const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product);
- const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
- const auto productMultiplexConfig =
- MultiplexInfo::multiplexIdToVariantMap(product.multiplexConfigurationId);
-
- for (const ProductContext *dependency : multiplexedDependencies) {
- const bool depMatchesShadowProduct = isShadowProduct
- && dependency->item == product.item->parent();
- const QString depMultiplexId = dependency->multiplexConfigurationId;
- if (depMatchesShadowProduct) { // (5)
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(depMultiplexId));
- return;
- }
- if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
- if (dependency->multiplexConfigurationId == product.multiplexConfigurationId) {
- const ValuePtr &multiplexId = product.item->property(
- StringConstants::multiplexConfigurationIdProperty());
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- multiplexId);
- return;
-
- }
- // Otherwise collect partial matches and decide later
- const auto dependencyMultiplexConfig = MultiplexInfo::multiplexIdToVariantMap(
- dependency->multiplexConfigurationId);
-
- if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
- multiplexIds << dependency->multiplexConfigurationId;
- } else {
- // (2b), (3b) or (3c)
- const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
- || profiles.contains(dependency->profileName);
- if (profileMatch)
- multiplexIds << depMultiplexId;
- }
- }
- if (multiplexIds.empty()) {
- const QString productName = ResolvedProduct::fullDisplayName(
- product.name, product.multiplexConfigurationId);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
- "There are no eligible multiplex candidates.").arg(productName,
- name),
- dependsItem->location());
- }
-
- // In case of (2a), at most 1 match is allowed
- if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
- const QString productName = ResolvedProduct::fullDisplayName(
- product.name, product.multiplexConfigurationId);
- QStringList candidateNames;
- for (const auto &id : qAsConst(multiplexIds))
- candidateNames << ResolvedProduct::fullDisplayName(name, id);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
- "Eligible multiplex candidates: %3.").arg(
- productName, name, candidateNames.join(QLatin1String(", "))),
- dependsItem->location());
- }
-
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(multiplexIds));
+ d->modulePrototypeEnabledInfo.insert({key, true});
+ return {module, triedToLoad};
}
-void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem)
+void ModuleLoader::checkProfileErrorsForModule(
+ Item *module, const QString &moduleName, const QString &productName, const QString &profileName)
{
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimePrepareProducts : nullptr);
- checkCancelation();
- qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
-
- ProductContext productContext;
- productContext.item = productItem;
- productContext.project = projectContext;
- productContext.name = m_evaluator->stringValue(productItem, StringConstants::nameProperty());
- QBS_CHECK(!productContext.name.isEmpty());
- const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule());
- if (!!qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
- qbsItemValue->item()->setProperty(StringConstants::nameProperty(),
- VariantValue::create(StringConstants::nameProperty()));
- auto evaluateQbsProfileProperty = [this](const Item::Module &qbsModule) {
- return m_evaluator->stringValue(qbsModule.item,
- StringConstants::profileProperty(), QString());
- };
- productContext.profileName
- = callWithTemporaryBaseModule<QString>(&productContext,
- evaluateQbsProfileProperty);
- } else {
- productContext.profileName = m_parameters.topLevelProfile();
- }
- productContext.multiplexConfigurationId = m_evaluator->stringValue(
- productItem, StringConstants::multiplexConfigurationIdProperty());
- QBS_CHECK(!productContext.profileName.isEmpty());
- const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName);
- if (it == projectContext->result->profileConfigs.constEnd()) {
- const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles);
- if (!profile.exists()) {
- ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
- productItem->location());
- handleProductError(error, &productContext);
- return;
- }
- const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
- profile, m_parameters.configurationName());
- productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- buildConfig, m_parameters.overriddenValues());
- projectContext->result->profileConfigs.insert(productContext.profileName,
- productContext.moduleProperties);
- } else {
- productContext.moduleProperties = it.value().toMap();
+ const auto it = d->unknownProfilePropertyErrors.find(module);
+ if (it != d->unknownProfilePropertyErrors.cend()) {
+ ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
+ "in profile '%3':").arg(moduleName, productName, profileName));
+ for (const ErrorInfo &e : it->second)
+ error.append(e.toString());
+ handlePropertyError(error, d->setupParameters, d->logger);
}
- initProductProperties(productContext);
-
- ItemValuePtr itemValue = ItemValue::create(productItem);
- productContext.scope = Item::create(m_pool, ItemType::Scope);
- productContext.scope->setProperty(StringConstants::productVar(), itemValue);
- productContext.scope->setFile(productItem->file());
- productContext.scope->setScope(productContext.project->scope);
-
- const bool hasExportItems = mergeExportItems(productContext);
-
- setScopeForDescendants(productItem, productContext.scope);
-
- projectContext->products.push_back(productContext);
-
- if (!hasExportItems || getShadowProductInfo(productContext).first)
- return;
-
- // This "shadow product" exists only to pull in a dependency on the actual product
- // and nothing else, thus providing us with the pure environment that we need to
- // evaluate the product's exported properties in isolation in the project resolver.
- Item * const importer = Item::create(productItem->pool(), ItemType::Product);
- importer->setProperty(QStringLiteral("name"),
- VariantValue::create(StringConstants::shadowProductPrefix()
- + productContext.name));
- importer->setFile(productItem->file());
- importer->setLocation(productItem->location());
- importer->setScope(projectContext->scope);
- importer->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
- dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name));
- dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false));
- dependsItem->setFile(importer->file());
- dependsItem->setLocation(importer->location());
- dependsItem->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item::addChild(importer, dependsItem);
- Item::addChild(productItem, importer);
- prepareProduct(projectContext, importer);
}
-void ModuleLoader::setupProductDependencies(ProductContext *productContext,
- const Set<DeferredDependsContext> &deferredDependsContext)
+std::pair<Item *, bool> ModuleLoader::Private::getModulePrototype(
+ const ProductContext &product, const QString &moduleName, const QString &filePath)
{
- if (m_dependencyResolvingPass == 2) {
- for (const DeferredDependsContext &ctx : deferredDependsContext)
- normalizeDependencies(productContext, ctx);
- for (const auto &deferralData : productContext->deferredDependsItems) {
- for (Item * const deferredDependsItem : deferralData.second) {
-
- // Dependencies from Export items are handled in addProductModuleDependencies().
- if (deferredDependsItem->parent() == productContext->item)
- adjustDependenciesForMultiplexing(*productContext, deferredDependsItem);
- }
+ auto &prototypeList = modulePrototypes[filePath];
+ for (const auto &prototype : prototypeList) {
+ if (prototype.second == product.profile) {
+ qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
+ return {prototype.first, true};
}
}
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimeProductDependencies : nullptr);
- checkCancelation();
- Item *item = productContext->item;
- qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name
- << productContext->item->location();
-
- if (m_dependencyResolvingPass == 1)
- setSearchPathsForProduct(productContext);
- // Module providers may push some extra search paths which we will be cleared
- // by this SearchPathsManager. However, they will be also added to productContext->searchPaths
- // so second pass will take that into account
- SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths);
-
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.productDependencies = &productContext->info.usedProducts;
- resolveDependencies(&dependsContext, item, productContext);
- if (m_dependencyResolvingPass == 2
- || !containsKey(m_productsWithDeferredDependsItems, productContext)) {
- addProductModuleDependencies(productContext);
+ Item * const module = itemReader.setupItemFromFile(filePath, CodeLocation(), evaluator);
+ if (module->type() != ItemType::Module) {
+ qCDebug(lcModuleLoader).nospace()
+ << "Alleged module " << moduleName << " has type '"
+ << module->typeName() << "', so it's not a module after all.";
+ return {nullptr, false};
}
- productContext->project->result->productInfos[item] = productContext->info;
-}
+ prototypeList.emplace_back(module, product.profile);
-// Leaf modules first.
-// TODO: Can this be merged with addTransitiveDependencies? Looks suspiciously similar.
-void ModuleLoader::createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules)
-{
- 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);
-}
-
-Item::Modules ModuleLoader::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;
- }
+ // Module properties that are defined in the profile are used as default values.
+ // This is the reason we need to have different items per profile.
+ const QVariantMap profileModuleProperties
+ = product.profileModuleProperties.value(moduleName).toMap();
+ for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) {
+ if (Q_UNLIKELY(!module->hasProperty(it.key()))) {
+ unknownProfilePropertyErrors[module].emplace_back(Tr::tr("Unknown property: %1.%2")
+ .arg(moduleName, it.key()));
+ continue;
}
+ const PropertyDeclaration decl = module->propertyDeclaration(it.key());
+ VariantValuePtr v = VariantValue::create(
+ PropertyDeclaration::convertToPropertyType(it.value(), decl.type(),
+ QStringList(moduleName), it.key()));
+ v->markAsSetByProfile();
+ module->setProperty(it.key(), v);
}
- return sortedModules;
-}
-
-
-template<typename T> bool insertIntoSet(Set<T> &set, const T &value)
-{
- const auto insertionResult = set.insert(value);
- return insertionResult.second;
-}
-
-void ModuleLoader::setupReverseModuleDependencies(const Item::Module &module,
- ModuleDependencies &deps,
- QualifiedIdSet &seenModules)
-{
- if (!insertIntoSet(seenModules, module.name))
- return;
- for (const Item::Module &m : module.item->modules()) {
- deps[m.name].insert(module.name);
- setupReverseModuleDependencies(m, deps, seenModules);
- }
-}
-ModuleLoader::ModuleDependencies ModuleLoader::setupReverseModuleDependencies(const Item *product)
-{
- ModuleDependencies deps;
- QualifiedIdSet seenModules;
- for (const Item::Module &m : product->modules())
- setupReverseModuleDependencies(m, deps, seenModules);
- return deps;
+ return {module, true};
}
-void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext)
+bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product,
+ Item *module, const QString &fullModuleName)
{
- AccumulatingTimer timer(m_parameters.logElapsedTime() ? &m_elapsedTimeHandleProducts : nullptr);
- if (productContext->info.delayedError.hasError())
- return;
-
- Item * const item = productContext->item;
-
- m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack);
- SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths);
- addTransitiveDependencies(productContext);
-
- // 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);
- ModuleMerger::merge(m_logger, item, productContext->name, &topSortedModules);
-
- // 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);
+ // Evaluator reqires module name to be set.
+ module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
- for (const Item::Module &module : topSortedModules) {
- if (!module.item->isPresentModule())
- continue;
- try {
- m_probesResolver->resolveProbes(productContext, module.item);
- if (module.versionRange.minimum.isValid()
- || module.versionRange.maximum.isValid()) {
- if (module.versionRange.maximum.isValid()
- && module.versionRange.minimum >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module "
- "'%3'").arg(module.versionRange.minimum.toString(),
- module.versionRange.maximum.toString(),
- module.name.toString()));
- }
- const Version moduleVersion = Version::fromString(
- m_evaluator->stringValue(module.item,
- StringConstants::versionProperty()));
- if (moduleVersion < module.versionRange.minimum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "at least %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.minimum.toString()));
- }
- if (module.versionRange.maximum.isValid()
- && moduleVersion >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "lower than %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.maximum.toString()));
- }
+ // Temporarily make the product's qbs module instance available, so the condition
+ // can use qbs.targetOS etc.
+ class TempQbsModuleProvider {
+ public:
+ TempQbsModuleProvider(const ProductContext &product,
+ Item *module, const QString &moduleName)
+ : m_module(module), m_needsQbsItem(moduleName != StringConstants::qbsModule())
+ {
+ if (m_needsQbsItem) {
+ m_prevQbsItemValue = module->property(StringConstants::qbsModule());
+ module->setProperty(StringConstants::qbsModule(),
+ product.item->property(StringConstants::qbsModule()));
}
- } catch (const ErrorInfo &error) {
- handleModuleSetupError(productContext, module, error);
- if (productContext->info.delayedError.hasError())
- return;
}
- }
-
- m_probesResolver->resolveProbes(productContext, item);
-
- // Module validation must happen in an extra pass, after all Probes have been resolved.
- EvalCacheEnabler cacheEnabler(m_evaluator);
- for (const Item::Module &module : topSortedModules) {
- if (!module.item->isPresentModule())
- continue;
- try {
- m_evaluator->boolValue(module.item, StringConstants::validateProperty());
- for (const auto &dep : module.item->modules()) {
- if (dep.requiredValue && !dep.item->isPresentModule()) {
- throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not "
- "loaded successfully")
- .arg(module.name.toString(), dep.name.toString()));
- }
- }
- } catch (const ErrorInfo &error) {
- handleModuleSetupError(productContext, module, error);
- if (productContext->info.delayedError.hasError())
+ ~TempQbsModuleProvider()
+ {
+ if (!m_needsQbsItem)
return;
+ if (m_prevQbsItemValue)
+ m_module->setProperty(StringConstants::qbsModule(), m_prevQbsItemValue);
+ else
+ m_module->removeProperty(StringConstants::qbsModule());
}
- }
-
- if (!checkItemCondition(item)) {
- const auto &exportsData = productContext->project->topLevelProject->productModules;
- for (auto it = exportsData.find(productContext->name);
- it != exportsData.end() && it.key() == productContext->name; ++it) {
- if (it.value().multiplexId == productContext->multiplexConfigurationId) {
- createNonPresentModule(productContext->name, QStringLiteral("disabled"),
- it.value().exportItem);
- break;
- }
- }
- }
-
- checkDependencyParameterDeclarations(productContext);
- copyGroupsFromModulesToProduct(*productContext);
-
- ModuleDependencies reverseModuleDeps;
- for (Item * const child : item->children()) {
- if (child->type() == ItemType::Group) {
- if (reverseModuleDeps.empty())
- reverseModuleDeps = setupReverseModuleDependencies(item);
- handleGroup(productContext, child, reverseModuleDeps);
- }
- }
- productContext->project->result->productInfos[item] = productContext->info;
-}
+ private:
+ Item * const m_module;
+ ValuePtr m_prevQbsItemValue;
+ const bool m_needsQbsItem;
+ };
-static Item *rootPrototype(Item *item)
-{
- Item *modulePrototype = item;
- while (modulePrototype->prototype())
- modulePrototype = modulePrototype->prototype();
- return modulePrototype;
+ const TempQbsModuleProvider tempQbs(product, module, fullModuleName);
+ return evaluator.boolValue(module, StringConstants::conditionProperty());
}
class DependencyParameterDeclarationCheck
{
public:
- DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem,
- const QHash<const Item *, Item::PropertyDeclarationMap> &decls)
+ DependencyParameterDeclarationCheck(
+ const QString &productName, const Item *productItem,
+ const std::unordered_map<const Item *, Item::PropertyDeclarationMap> &decls)
: m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls)
- {
- }
+ {}
- void operator()(const QVariantMap &parameters) const
- {
- check(parameters, QualifiedId());
- }
+ void operator()(const QVariantMap &parameters) const { check(parameters, QualifiedId()); }
private:
void check(const QVariantMap &parameters, const QualifiedId &moduleName) const
@@ -1534,23 +262,22 @@ private:
const auto &deps = m_productItem->modules();
auto m = std::find_if(deps.begin(), deps.end(),
[&moduleName] (const Item::Module &module) {
- return module.name == moduleName;
- });
+ return module.name == moduleName;
+ });
if (m == deps.end()) {
const QualifiedId fullName = QualifiedId(moduleName) << it.key();
throw ErrorInfo(Tr::tr("Cannot set parameter '%1', "
"because '%2' does not have a dependency on '%3'.")
- .arg(fullName.toString(), m_productName, moduleName.toString()),
+ .arg(fullName.toString(), m_productName, moduleName.toString()),
m_productItem->location());
}
- auto decls = m_parameterDeclarations.value(rootPrototype(m->item));
-
- if (!decls.contains(it.key())) {
+ const auto decls = m_parameterDeclarations.find(m->item->rootPrototype());
+ if (decls == m_parameterDeclarations.end() || !decls->second.contains(it.key())) {
const QualifiedId fullName = QualifiedId(moduleName) << it.key();
throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.")
- .arg(fullName.toString()), m_productItem->location());
+ .arg(fullName.toString()), m_productItem->location());
}
}
}
@@ -1565,1113 +292,39 @@ private:
}
const QString &m_productName;
- const Item *m_productItem;
- const QHash<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations;
+ const Item * const m_productItem;
+ const std::unordered_map<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations;
};
-void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext *productContext) const
+void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext &product) const
{
- DependencyParameterDeclarationCheck dpdc(productContext->name, productContext->item,
- m_parameterDeclarations);
- for (const Item::Module &dep : productContext->item->modules()) {
+ DependencyParameterDeclarationCheck dpdc(product.name, product.item, d->parameterDeclarations);
+ for (const Item::Module &dep : product.item->modules()) {
if (!dep.parameters.empty())
dpdc(dep.parameters);
}
}
-void ModuleLoader::handleModuleSetupError(ModuleLoader::ProductContext *productContext,
- const Item::Module &module, const ErrorInfo &error)
-{
- if (module.required) {
- handleProductError(error, productContext);
- } else {
- qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
- << "found, but not usable in product" << productContext->name
- << error.toString();
- createNonPresentModule(module.name.toString(), QStringLiteral("failed validation"),
- module.item);
- }
-}
-
-void ModuleLoader::initProductProperties(const ProductContext &product)
-{
- QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
- product.multiplexConfigurationId);
- buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
- product.item->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(buildDir));
- const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath();
- product.item->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(sourceDir));
-}
-
-void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths)
-{
- qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath();
-
- Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject);
- if (!checkItemCondition(projectItem))
- return;
- if (propertiesItem) {
- propertiesItem->setScope(projectItem);
- if (!checkItemCondition(propertiesItem))
- return;
- }
-
- Item *loadedItem;
- QString subProjectFilePath;
- try {
- const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath());
- const QString relativeFilePath
- = m_evaluator->stringValue(projectItem, StringConstants::filePathProperty());
- subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath);
- if (referencedFilePaths.contains(subProjectFilePath))
- throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.")
- .arg(relativeFilePath), projectItem->location());
- loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location());
- } catch (const ErrorInfo &error) {
- if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- m_logger.printWarning(error);
- return;
- }
-
- loadedItem = wrapInProjectIfNecessary(loadedItem);
- const bool inheritProperties = m_evaluator->boolValue(
- projectItem, StringConstants::inheritPropertiesProperty());
-
- if (inheritProperties)
- copyProperties(projectItem->parent(), loadedItem);
- if (propertiesItem) {
- const Item::PropertyMap &overriddenProperties = propertiesItem->properties();
- for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin();
- it != overriddenProperties.constEnd(); ++it) {
- loadedItem->setProperty(it.key(), it.value());
- }
- }
-
- Item::addChild(projectItem, loadedItem);
- projectItem->setScope(projectContext->scope);
- handleProject(projectContext->result, projectContext->topLevelProject, loadedItem,
- Set<QString>(referencedFilePaths) << subProjectFilePath);
-}
-
-QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath,
- const CodeLocation &referencingLocation,
- const Set<QString> &referencedFilePaths,
- ModuleLoader::ProductContext &dummyContext)
-{
- QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()),
- relativePath);
- if (FileInfo(absReferencePath).isDir()) {
- QString qbsFilePath;
-
- QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards());
- while (dit.hasNext()) {
- if (!qbsFilePath.isEmpty()) {
- throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one "
- "qbs file.").arg(absReferencePath), referencingLocation);
- }
- qbsFilePath = dit.next();
- }
- if (qbsFilePath.isEmpty()) {
- throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.")
- .arg(absReferencePath), referencingLocation);
- }
- absReferencePath = qbsFilePath;
- }
- if (referencedFilePaths.contains(absReferencePath))
- throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath),
- referencingLocation);
- Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation);
- if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) {
- ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.")
- .arg(subItem->typeName()));
- error.append(Tr::tr("Item is defined here."), subItem->location());
- error.append(Tr::tr("File is referenced here."), referencingLocation);
- throw error;
- }
- subItem->setScope(dummyContext.project->scope);
- subItem->setParent(dummyContext.project->item);
- QList<Item *> loadedItems;
- loadedItems << subItem;
- if (subItem->type() == ItemType::Product) {
- handleProfileItems(subItem, dummyContext.project);
- loadedItems << multiplexProductItem(&dummyContext, subItem);
- }
- return loadedItems;
-}
-
-void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies)
-{
- checkCancelation();
- propagateModulesFromParent(productContext, groupItem, reverseDepencencies);
- checkItemCondition(groupItem);
- for (Item * const child : groupItem->children()) {
- if (child->type() == ItemType::Group)
- handleGroup(productContext, child, reverseDepencencies);
- }
-}
-
-void ModuleLoader::handleAllPropertyOptionsItems(Item *item)
-{
- QList<Item *> childItems = item->children();
- auto childIt = childItems.begin();
- while (childIt != childItems.end()) {
- Item * const child = *childIt;
- if (child->type() == ItemType::PropertyOptions) {
- handlePropertyOptions(child);
- childIt = childItems.erase(childIt);
- } else {
- handleAllPropertyOptionsItems(child);
- ++childIt;
- }
- }
- item->setChildren(childItems);
-}
-
-void ModuleLoader::handlePropertyOptions(Item *optionsItem)
-{
- const QString name = m_evaluator->stringValue(optionsItem, StringConstants::nameProperty());
- if (name.isEmpty()) {
- throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"),
- optionsItem->location());
- }
- const QString description = m_evaluator->stringValue(
- optionsItem, StringConstants::descriptionProperty());
- const auto removalVersion = Version::fromString(m_evaluator->stringValue(optionsItem,
- StringConstants::removalVersionProperty()));
- const auto allowedValues = m_evaluator->stringListValue(
- optionsItem, StringConstants::allowedValuesProperty());
-
- PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name);
- if (!decl.isValid()) {
- decl.setName(name);
- decl.setType(PropertyDeclaration::Variant);
- }
- decl.setDescription(description);
- if (removalVersion.isValid()) {
- DeprecationInfo di(removalVersion, description);
- decl.setDeprecationInfo(di);
- }
- decl.setAllowedValues(allowedValues);
- const ValuePtr property = optionsItem->parent()->property(name);
- if (!property && !decl.isExpired()) {
- throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'")
- .arg(name), optionsItem->location());
- }
- if (property && decl.isExpired()) {
- ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
- "is still present.")
- .arg(name, removalVersion.toString()),
- property->location());
- e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
- optionsItem->location());
- m_logger.printWarning(e);
- }
- optionsItem->parent()->setPropertyDeclaration(name, decl);
-}
-
-static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
-{
- if (value->type() == Value::ItemValueType) {
- const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value);
- const Item * const valueItem = itemValue->item();
- Item * const subItem = dst->itemProperty(name, itemValue)->item();
- for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin();
- it != valueItem->properties().constEnd(); ++it)
- mergeProperty(subItem, it.key(), it.value());
- } else {
- // If the property already exists set up the base value.
- if (value->type() == Value::JSSourceValueType) {
- const auto jsValue = static_cast<JSSourceValue *>(value.get());
- if (jsValue->isBuiltinDefaultValue())
- return;
- const ValuePtr baseValue = dst->property(name);
- if (baseValue) {
- QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
- const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
- baseValue->clone());
- jsValue->setBaseValue(jsBaseValue);
- std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
- jsValue->clearAlternatives();
- for (JSSourceValue::Alternative &a : alternatives) {
- a.value->setBaseValue(jsBaseValue);
- jsValue->addAlternative(a);
- }
- }
- }
- dst->setProperty(name, value);
- }
-}
-
-bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext)
-{
- class ScopeHandler {
- public:
- ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
- : m_exportItem(exportItem)
- {
- if (!*cachedScopeItem)
- *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
- Item * const scope = *cachedScopeItem;
- QBS_CHECK(productContext.item->file());
- scope->setFile(productContext.item->file());
- scope->setScope(productContext.item);
- productContext.project->scope->copyProperty(StringConstants::projectVar(), scope);
- productContext.scope->copyProperty(StringConstants::productVar(), scope);
- QBS_CHECK(!exportItem->scope());
- exportItem->setScope(scope);
- }
- ~ScopeHandler() { m_exportItem->setScope(nullptr); }
-
- private:
- Item * const m_exportItem;
- } scopeHandler(exportItem, productContext, &m_tempScopeItem);
- return checkItemCondition(exportItem);
-}
-
-void ModuleLoader::printProfilingInfo()
-{
- if (!m_parameters.logElapsedTime())
- return;
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Project file loading and parsing took %1.")
- .arg(elapsedTimeString(m_reader->elapsedTime()));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Preparing products took %1.")
- .arg(elapsedTimeString(m_elapsedTimePrepareProducts));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Setting up product dependencies took %1.")
- .arg(elapsedTimeString(m_elapsedTimeProductDependencies));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Running module providers took %1.")
- .arg(elapsedTimeString(m_elapsedTimeModuleProviders));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Setting up transitive product dependencies took %1.")
- .arg(elapsedTimeString(m_elapsedTimeTransitiveDependencies));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Handling products took %1.")
- .arg(elapsedTimeString(m_elapsedTimeHandleProducts));
- m_probesResolver->printProfilingInfo();
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Property checking took %1.")
- .arg(elapsedTimeString(m_elapsedTimePropertyChecking));
-}
-
-static 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();
- }
- }
-}
-
-static void adjustParametersScopes(Item *item, Item *scope)
-{
- if (item->type() == ItemType::ModuleParameters) {
- item->setScope(scope);
- return;
- }
-
- for (const auto &value : item->properties()) {
- if (value->type() != Value::ItemValueType)
- continue;
- adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
- }
-}
-
-bool ModuleLoader::mergeExportItems(const ProductContext &productContext)
-{
- std::vector<Item *> exportItems;
- QList<Item *> children = productContext.item->children();
-
- auto isExport = [](Item *item) { return item->type() == ItemType::Export; };
- std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport);
- qbs::Internal::removeIf(children, isExport);
-
- // Note that we do not return if there are no Export items: The "merged" item becomes the
- // "product module", which always needs to exist, regardless of whether the product sources
- // actually contain an Export item or not.
- if (!exportItems.empty())
- productContext.item->setChildren(children);
-
- Item *merged = Item::create(productContext.item->pool(), ItemType::Export);
- const QString &nameKey = StringConstants::nameProperty();
- const ValuePtr nameValue = VariantValue::create(productContext.name);
- merged->setProperty(nameKey, nameValue);
- Set<FileContextConstPtr> filesWithExportItem;
- ProductModuleInfo pmi;
- bool hasDependenciesOnProductType = false;
- for (Item * const exportItem : qAsConst(exportItems)) {
- checkCancelation();
- if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
- throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
- exportItem->location());
- exportItem->setProperty(nameKey, nameValue);
- if (!checkExportItemCondition(exportItem, productContext))
- continue;
- filesWithExportItem += exportItem->file();
- for (Item * const child : exportItem->children()) {
- if (child->type() == ItemType::Parameters) {
- adjustParametersScopes(child, child);
- mergeParameters(pmi.defaultParameters, getJsVariant(
- m_evaluator->engine()->context(), m_evaluator->scriptValue(child)).toMap());
- } else {
- if (child->type() == ItemType::Depends) {
- bool productTypesIsSet;
- m_evaluator->stringValue(child, StringConstants::productTypesProperty(),
- QString(), &productTypesIsSet);
- if (productTypesIsSet)
- hasDependenciesOnProductType = true;
- }
- Item::addChild(merged, child);
- }
- }
- const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations();
- for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) {
- const PropertyDeclaration &newDecl = it.value();
- const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key());
- if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) {
- ErrorInfo error(Tr::tr("Export item in inherited item redeclares property "
- "'%1' with different type.").arg(it.key()), exportItem->location());
- handlePropertyError(error, m_parameters, m_logger);
- }
- merged->setPropertyDeclaration(newDecl.name(), newDecl);
- }
- for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin();
- it != exportItem->properties().constEnd(); ++it) {
- mergeProperty(merged, it.key(), it.value());
- }
- }
- merged->setFile(exportItems.empty()
- ? productContext.item->file() : exportItems.back()->file());
- merged->setLocation(exportItems.empty()
- ? productContext.item->location() : exportItems.back()->location());
- Item::addChild(productContext.item, merged);
- merged->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- pmi.exportItem = merged;
- pmi.multiplexId = productContext.multiplexConfigurationId;
- productContext.project->topLevelProject->productModules.insert(productContext.name, pmi);
- if (hasDependenciesOnProductType)
- m_exportsWithDeferredDependsItems.insert(merged);
- return !exportItems.empty();
-}
-
-Item *ModuleLoader::loadItemFromFile(const QString &filePath,
- const CodeLocation &referencingLocation)
-{
- Item *item = m_reader->readFile(filePath, referencingLocation);
- handleAllPropertyOptionsItems(item);
- return item;
-}
-
-void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext)
-{
- const std::vector<Item *> profileItems = collectProfileItems(item, projectContext);
- for (Item * const profileItem : profileItems) {
- try {
- handleProfile(profileItem);
- } catch (const ErrorInfo &e) {
- handlePropertyError(e, m_parameters, m_logger);
- }
- }
-}
-
-std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext)
-{
- QList<Item *> childItems = item->children();
- std::vector<Item *> profileItems;
- Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr;
- for (auto it = childItems.begin(); it != childItems.end();) {
- Item * const childItem = *it;
- if (childItem->type() == ItemType::Profile) {
- if (!scope) {
- const ItemValuePtr itemValue = ItemValue::create(item);
- scope = Item::create(m_pool, ItemType::Scope);
- scope->setProperty(StringConstants::productVar(), itemValue);
- scope->setFile(item->file());
- scope->setScope(projectContext->scope);
- }
- childItem->setScope(scope);
- profileItems.push_back(childItem);
- it = childItems.erase(it);
- } else {
- if (childItem->type() == ItemType::Product) {
- for (Item * const profileItem : collectProfileItems(childItem, projectContext))
- profileItems.push_back(profileItem);
- }
- ++it;
- }
- }
- if (!profileItems.empty())
- item->setChildren(childItems);
- return profileItems;
-}
-
-void ModuleLoader::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
- Item *profileItem, QVariantMap &values)
-{
- const Item::PropertyMap &props = item->properties();
- for (auto it = props.begin(); it != props.end(); ++it) {
- QualifiedId name = namePrefix;
- name << it.key();
- switch (it.value()->type()) {
- case Value::ItemValueType:
- evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(),
- profileItem, values);
- break;
- case Value::VariantValueType:
- values.insert(name.join(QLatin1Char('.')),
- std::static_pointer_cast<VariantValue>(it.value())->value());
- break;
- case Value::JSSourceValueType:
- item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type
- if (item != profileItem)
- item->setScope(profileItem);
- const ScopedJsValue sv(m_evaluator->engine()->context(),
- m_evaluator->value(item, it.key()));
- values.insert(name.join(QLatin1Char('.')),
- getJsVariant(m_evaluator->engine()->context(), sv));
- break;
- }
- }
-}
-
-void ModuleLoader::handleProfile(Item *profileItem)
-{
- QVariantMap values;
- evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
- const bool condition = values.take(StringConstants::conditionProperty()).toBool();
- if (!condition)
- return;
- const QString profileName = values.take(StringConstants::nameProperty()).toString();
- if (profileName.isEmpty())
- throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location());
- if (profileName == Profile::fallbackName()) {
- throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
- .arg(profileName), profileItem->location());
- }
- if (m_localProfiles.contains(profileName)) {
- throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
- profileItem->location());
- }
- 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) {
- if (m_erroneousProducts.contains(productNameInOverride))
- continue;
- bool found = false;
- for (const auto &kv : m_productsByName) {
- // 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 (kv.first == productNameInOverride
- || kv.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::setSearchPathsForProduct(ModuleLoader::ProductContext *product)
-{
- product->searchPaths = readExtraSearchPaths(product->item);
- Settings settings(m_parameters.settingsDirectory());
- const QVariantMap profileContents = product->project->result->profileConfigs
- .value(product->profileName).toMap();
- const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths();
- const QStringList &currentSearchPaths = m_reader->allSearchPaths();
- for (const QString &p : prefsSearchPaths) {
- if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
- product->searchPaths << p;
- }
-}
-
-ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo(
- const ModuleLoader::ProductContext &product) const
-{
- const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
- return std::make_pair(isShadowProduct, isShadowProduct
- ? product.name.mid(StringConstants::shadowProductPrefix().size())
- : QString());
-}
-
-void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject)
-{
- for (ProjectContext * const project : topLevelProject.projects) {
- for (ProductContext &product : project->products)
- m_productsByName.insert({ product.name, &product });
- }
-}
-
-void ModuleLoader::collectProductsByType(const ModuleLoader::TopLevelProjectContext &topLevelProject)
-{
- for (ProjectContext * const project : topLevelProject.projects) {
- for (ProductContext &product : project->products) {
- try {
- const FileTags productTags
- = m_evaluator->fileTagsValue(product.item, StringConstants::typeProperty());
- for (const FileTag &tag : productTags)
- m_productsByType.insert({ tag, &product});
- } catch (const ErrorInfo &) {
- qCDebug(lcModuleLoader) << "product" << product.name << "has complex type "
- " and won't get an entry in the type map";
- }
- }
- }
-}
-
-void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies)
-{
- QBS_CHECK(groupItem->type() == ItemType::Group);
- QHash<QualifiedId, Item *> moduleInstancesForGroup;
-
- // Step 1: Instantiate the product's modules for the group.
- for (Item::Module m : groupItem->parent()->modules()) {
- Item *targetItem = moduleInstanceItem(groupItem, m.name);
- targetItem->setPrototype(m.item);
-
- Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
- moduleScope->setFile(groupItem->file());
- moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids
- moduleScope->setScope(groupItem);
- targetItem->setScope(moduleScope);
-
- targetItem->setFile(m.item->file());
-
- // "parent" should point to the group/artifact parent
- targetItem->setParent(groupItem->parent());
-
- targetItem->setOuterItem(m.item);
-
- m.item = targetItem;
- groupItem->addModule(m);
- moduleInstancesForGroup.insert(m.name, targetItem);
- }
-
- // Step 2: Make the inter-module references point to the instances created in step 1.
- for (const Item::Module &module : groupItem->modules()) {
- Item::Modules adaptedModules;
- const Item::Modules &oldModules = module.item->prototype()->modules();
- for (Item::Module depMod : oldModules) {
- depMod.item = moduleInstancesForGroup.value(depMod.name);
- adaptedModules << depMod;
- if (depMod.name.front() == module.name.front())
- continue;
- const ItemValuePtr &modulePrefix = groupItem->itemProperty(depMod.name.front());
- QBS_CHECK(modulePrefix);
- module.item->setProperty(depMod.name.front(), modulePrefix);
- }
- module.item->setModules(adaptedModules);
- }
-
- const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(groupItem);
- if (propsSetInGroup.empty())
- return;
- productContext->info.modulePropertiesSetInGroups
- .insert(std::make_pair(groupItem, propsSetInGroup));
-
- // Step 3: Adapt defining items in values. This is potentially necessary if module properties
- // get assigned on the group level.
- for (const Item::Module &module : groupItem->modules()) {
- const QualifiedIdSet &dependents = reverseDepencencies.value(module.name);
- Item::Modules dependentModules;
- dependentModules.reserve(int(dependents.size()));
- for (const QualifiedId &depName : dependents) {
- Item * const itemOfDependent = moduleInstancesForGroup.value(depName);
- QBS_CHECK(itemOfDependent);
- Item::Module depMod;
- depMod.name = depName;
- depMod.item = itemOfDependent;
- dependentModules << depMod;
- }
- adjustDefiningItemsInGroupModuleInstances(module, dependentModules);
- }
-}
-
-static Item *createReplacementForDefiningItem(const Item *definingItem, ItemType type)
-{
- Item *replacement = Item::create(definingItem->pool(), type);
- replacement->setLocation(definingItem->location());
- definingItem->copyProperty(StringConstants::nameProperty(), replacement);
- return replacement;
-}
-
-void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module &module,
- const Item::Modules &dependentModules)
-{
- if (!module.item->isPresentModule())
- return;
-
- // There are three cases:
- // a) The defining item is the "main" module instance, i.e. the one instantiated in the
- // product directly (or a parent group).
- // b) The defining item refers to the module prototype (or the replacement of it
- // created in the module merger [for products] or in this function [for parent groups]).
- // c) The defining item is a different instance of the module, i.e. it was instantiated
- // in some other module.
-
- std::unordered_map<Item *, Item *> definingItemReplacements;
-
- Item *modulePrototype = rootPrototype(module.item->prototype());
- QBS_CHECK(modulePrototype->type() == ItemType::Module
- || modulePrototype->type() == ItemType::Export);
-
- const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations();
- for (const auto &decl : propDecls) {
- const QString &propName = decl.name();
-
- // Module properties assigned in the group are not relevant here, as nothing
- // gets inherited in that case. In particular, setting a list property
- // overwrites the value from the product's (or parent group's) instance completely,
- // rather than appending to it (concatenation happens via outer.concat()).
- ValueConstPtr propValue = module.item->ownProperty(propName);
- if (propValue)
- continue;
-
- // Find the nearest prototype instance that has the value assigned.
- // The result is either an instance of a parent group (or the parent group's
- // parent group and so on) or the instance of the product or the module prototype.
- // In the latter case, we don't have to do anything.
- const Item *instanceWithProperty = module.item;
- int prototypeChainLen = 0;
- do {
- instanceWithProperty = instanceWithProperty->prototype();
- QBS_CHECK(instanceWithProperty);
- ++prototypeChainLen;
- propValue = instanceWithProperty->ownProperty(propName);
- } while (!propValue);
- QBS_CHECK(propValue);
-
- if (propValue->type() != Value::JSSourceValueType)
- continue;
-
- bool hasDefiningItem = false;
- for (ValueConstPtr v = propValue; v && !hasDefiningItem; v = v->next())
- hasDefiningItem = v->definingItem();
- if (!hasDefiningItem)
- continue;
-
- const ValuePtr clonedValue = propValue->clone();
- for (ValuePtr v = clonedValue; v; v = v->next()) {
- QBS_CHECK(v->definingItem());
-
- Item *& replacement = definingItemReplacements[v->definingItem()];
- static const QString caseA = QStringLiteral("__group_case_a");
- if (v->definingItem() == instanceWithProperty
- || v->definingItem()->variantProperty(caseA)) {
- // Case a)
- // For values whose defining item is the product's (or parent group's) instance,
- // we take its scope and replace references to module instances with those from the
- // group's instance. This handles cases like the following:
- // Product {
- // name: "theProduct"
- // aModule.listProp: [name, otherModule.stringProp]
- // Group { name: "theGroup"; otherModule.stringProp: name }
- // ...
- // }
- // In the above example, aModule.listProp is set to ["theProduct", "theGroup"]
- // (plus potential values from the prototype and other module instances,
- // which are different Value objects in the "next chain").
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- v->definingItem()->type());
- Item * const scope = Item::create(v->definingItem()->pool(), ItemType::Scope);
- scope->setProperties(module.item->scope()->properties());
- Item * const scopeScope
- = Item::create(v->definingItem()->pool(), ItemType::Scope);
- scopeScope->setProperties(v->definingItem()->scope()->scope()->properties());
- scope->setScope(scopeScope);
- replacement->setScope(scope);
- const Item::PropertyMap &groupScopeProperties
- = module.item->scope()->scope()->properties();
- for (auto propIt = groupScopeProperties.begin();
- propIt != groupScopeProperties.end(); ++propIt) {
- if (propIt.value()->type() == Value::ItemValueType)
- scopeScope->setProperty(propIt.key(), propIt.value());
- }
- }
- replacement->setPropertyDeclaration(propName, decl);
- replacement->setProperty(propName, v);
- replacement->setProperty(caseA, VariantValue::invalidValue());
- } else if (v->definingItem()->type() == ItemType::Module) {
- // Case b)
- // For values whose defining item is the module prototype, we change the scope to
- // the group's instance, analogous to what we do in
- // ModuleMerger::appendPrototypeValueToNextChain().
- QBS_CHECK(!decl.isScalar());
- QBS_CHECK(!v->next());
- Item *& replacement = definingItemReplacements[v->definingItem()];
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- ItemType::Module);
- replacement->setScope(module.item);
- }
- QBS_CHECK(!replacement->hasOwnProperty(caseA));
- qCDebug(lcModuleLoader).noquote().nospace()
- << "replacing defining item for prototype; module is "
- << module.name.toString() << module.item
- << ", property is " << propName
- << ", old defining item was " << v->definingItem()
- << " with scope" << v->definingItem()->scope()
- << ", new defining item is" << replacement
- << " with scope" << replacement->scope();
- if (v->type() == Value::JSSourceValueType) {
- qCDebug(lcModuleLoader) << "value source code is"
- << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString();
- }
- replacement->setPropertyDeclaration(propName, decl);
- replacement->setProperty(propName, v);
- } else {
- // Look for instance scopes of other module instances in defining items and
- // replace the affected values.
- // This is case c) as introduced above. See ModuleMerger::replaceItemInScopes()
- // for a detailed explanation.
-
- QBS_CHECK(v->definingItem()->scope() && v->definingItem()->scope()->scope());
- bool found = false;
- for (const Item::Module &depMod : dependentModules) {
- const Item *depModPrototype = depMod.item->prototype();
- for (int i = 1; i < prototypeChainLen; ++i)
- depModPrototype = depModPrototype->prototype();
- if (v->definingItem()->scope()->scope() != depModPrototype)
- continue;
-
- found = true;
- Item *& replacement = definingItemReplacements[v->definingItem()];
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- v->definingItem()->type());
- replacement->setProperties(v->definingItem()->properties());
- for (const auto &decl : v->definingItem()->propertyDeclarations())
- replacement->setPropertyDeclaration(decl.name(), decl);
- replacement->setPrototype(v->definingItem()->prototype());
- replacement->setScope(Item::create(v->definingItem()->pool(),
- ItemType::Scope));
- replacement->scope()->setScope(depMod.item);
- }
- QBS_CHECK(!replacement->hasOwnProperty(caseA));
- qCDebug(lcModuleLoader) << "reset instance scope of module"
- << depMod.name.toString() << "in property"
- << propName << "of module" << module.name;
- }
- QBS_CHECK(found);
- }
- QBS_CHECK(replacement);
- v->setDefiningItem(replacement);
- }
- module.item->setProperty(propName, clonedValue);
- }
-}
-
-void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item,
- ProductContext *productContext)
-{
- QBS_CHECK(m_dependencyResolvingPass == 1 || m_dependencyResolvingPass == 2);
-
- if (!productContext || m_dependencyResolvingPass == 1) {
- const Item::Module baseModule = loadBaseModule(dependsContext->product, item);
- item->addModule(baseModule);
- }
-
- // Resolve all Depends items.
- ItemModuleList loadedModules;
- QList<Item *> dependsItemPerLoadedModule;
- ProductDependencies productDependencies;
- const auto handleDependsItem = [&](Item *child) {
- if (child->type() != ItemType::Depends)
- return;
-
- int lastModulesCount = loadedModules.size();
- try {
- resolveDependsItem(dependsContext, child->parent(), child, &loadedModules,
- &productDependencies);
- } catch (const ErrorInfo &e) {
- if (!productContext)
- throw;
- handleProductError(e, productContext);
- }
- for (int i = lastModulesCount; i < loadedModules.size(); ++i)
- dependsItemPerLoadedModule.push_back(child);
-
- };
- if (productContext && m_dependencyResolvingPass == 2) {
- for (const auto &deferData : productContext->deferredDependsItems) {
- dependsContext->exportingProductItem = deferData.first;
- for (Item * const dependsItem : deferData.second)
- handleDependsItem(dependsItem);
- }
- } else {
- for (Item * const child : item->children())
- handleDependsItem(child);
- }
- QBS_CHECK(loadedModules.size() == dependsItemPerLoadedModule.size());
-
- Item *lastDependsItem = nullptr;
- for (Item * const dependsItem : dependsItemPerLoadedModule) {
- if (dependsItem == lastDependsItem)
- continue;
- adjustParametersScopes(dependsItem, dependsItem);
- forwardParameterDeclarations(dependsItem, loadedModules);
- lastDependsItem = dependsItem;
- }
-
- for (int i = 0; i < loadedModules.size(); ++i) {
- Item::Module &module = loadedModules[i];
- mergeParameters(module.parameters, extractParameters(dependsItemPerLoadedModule.at(i)));
- item->addModule(module);
-
- const QString moduleName = module.name.toString();
- std::for_each(productDependencies.begin(), productDependencies.end(),
- [&module, &moduleName] (ModuleLoaderResult::ProductInfo::Dependency &dep) {
- if (dep.name == moduleName)
- dep.parameters = module.parameters;
- });
- }
-
- dependsContext->productDependencies->insert(
- dependsContext->productDependencies->end(),
- productDependencies.cbegin(), productDependencies.cend());
-}
-
-class RequiredChainManager
-{
-public:
- RequiredChainManager(std::vector<bool> &requiredChain, bool required)
- : m_requiredChain(requiredChain)
- {
- m_requiredChain.push_back(required);
- }
-
- ~RequiredChainManager() { m_requiredChain.pop_back(); }
-
-private:
- std::vector<bool> &m_requiredChain;
-};
-
-void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem,
- Item *dependsItem, ItemModuleList *moduleResults,
- ProductDependencies *productResults)
-{
- checkCancelation();
- if (!checkItemCondition(dependsItem)) {
- qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
- return;
- }
- bool nameIsSet;
- const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(),
- QString(), &nameIsSet);
- bool submodulesPropertySet;
- const QStringList submodules = m_evaluator->stringListValue(
- dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet);
- if (submodules.empty() && submodulesPropertySet) {
- qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
- return;
- }
- if (Q_UNLIKELY(submodules.size() > 1 && !dependsItem->id().isEmpty())) {
- QString msg = Tr::tr("A Depends item with more than one module cannot have an id.");
- throw ErrorInfo(msg, dependsItem->location());
- }
- const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled()
- && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty())
- ? FallbackMode::Enabled : FallbackMode::Disabled;
-
- QList<QualifiedId> moduleNames;
- const QualifiedId nameParts = QualifiedId::fromString(name);
- if (submodules.empty()) {
- // Ignore explicit dependencies on the base module, which has already been loaded.
- if (name == StringConstants::qbsModule())
- return;
-
- moduleNames << nameParts;
- } else {
- for (const QString &submodule : submodules)
- moduleNames << nameParts + QualifiedId::fromString(submodule);
- }
-
- Item::Module result;
- bool productTypesIsSet;
- m_evaluator->stringValue(dependsItem, StringConstants::productTypesProperty(),
- QString(), &productTypesIsSet);
- if (m_dependencyResolvingPass == 1 && productTypesIsSet) {
- qCDebug(lcModuleLoader) << "queuing product" << dependsContext->product->name
- << "for a second dependencies resolving pass";
- m_productsWithDeferredDependsItems[dependsContext->product].insert(
- DeferredDependsContext(dependsContext->exportingProductItem, parentItem));
- return;
- }
-
- const bool isRequiredValue =
- m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty());
- const bool isRequired = !productTypesIsSet
- && isRequiredValue
- && !contains(m_requiredChain, false);
- const Version minVersion = Version::fromString(
- m_evaluator->stringValue(dependsItem,
- StringConstants::versionAtLeastProperty()));
- const Version maxVersion = Version::fromString(
- m_evaluator->stringValue(dependsItem, StringConstants::versionBelowProperty()));
- const VersionRange versionRange(minVersion, maxVersion);
- QStringList multiplexConfigurationIds = m_evaluator->stringListValue(
- dependsItem,
- StringConstants::multiplexConfigurationIdsProperty());
- if (multiplexConfigurationIds.empty())
- multiplexConfigurationIds << QString();
-
- for (const QualifiedId &moduleName : qAsConst(moduleNames)) {
- // Don't load the same module twice. Duplicate Depends statements can easily
- // happen due to inheritance.
- const auto it = std::find_if(moduleResults->begin(), moduleResults->end(),
- [moduleName](const Item::Module &m) { return m.name == moduleName; });
- if (it != moduleResults->end()) {
- it->required = it->required || isRequired;
- it->requiredValue = it->requiredValue || isRequiredValue;
- it->fallbackEnabled = it->fallbackEnabled && fallbackMode == FallbackMode::Enabled;
- it->versionRange.narrowDown(versionRange);
- continue;
- }
-
- QVariantMap defaultParameters;
- Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem,
- parentItem, dependsItem->location(), dependsItem->id(),
- moduleName, multiplexConfigurationIds.first(), fallbackMode,
- isRequired, &result.isProduct, &defaultParameters);
- if (!moduleItem) {
- const QString productName = ResolvedProduct::fullDisplayName(
- dependsContext->product->name,
- dependsContext->product->multiplexConfigurationId);
- if (!multiplexConfigurationIds.first().isEmpty()) {
- const QString depName = ResolvedProduct::fullDisplayName(
- moduleName.toString(), multiplexConfigurationIds.first());
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not "
- "fulfilled.").arg(productName, depName));
- }
- ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.")
- .arg(moduleName.toString(), productName), dependsItem->location());
- throw e;
- }
- if (result.isProduct && !m_dependsChain.empty() && !m_dependsChain.back().isProduct) {
- throw ErrorInfo(Tr::tr("Invalid dependency on product '%1': Modules cannot depend on "
- "products. You may want to turn your module into a product and "
- "add the dependency in that product's Export item.")
- .arg(moduleName.toString()), dependsItem->location());
- }
- qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString();
- result.name = moduleName;
- result.item = moduleItem;
- result.requiredValue = isRequiredValue;
- result.required = isRequired;
- result.fallbackEnabled = fallbackMode == FallbackMode::Enabled;
- result.parameters = defaultParameters;
- result.versionRange = versionRange;
- moduleResults->push_back(result);
- if (result.isProduct) {
- qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString();
- bool profilesPropertyWasSet = false;
- QStringList profiles = m_evaluator->stringListValue(dependsItem,
- StringConstants::profilesProperty(),
- &profilesPropertyWasSet);
- if (profiles.empty()) {
- if (profilesPropertyWasSet)
- profiles.push_back(StringConstants::star());
- else
- profiles.push_back(QString());
- }
- for (const QString &profile : qAsConst(profiles)) {
- for (const QString &multiplexId : qAsConst(multiplexConfigurationIds)) {
- ModuleLoaderResult::ProductInfo::Dependency dependency;
- dependency.name = moduleName.toString();
- dependency.profile = profile;
- dependency.multiplexConfigurationId = multiplexId;
- dependency.isRequired = isRequired;
- productResults->push_back(dependency);
- }
- }
- }
- }
-}
-
void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem,
- const ItemModuleList &modules)
+ const Item::Modules &modules)
{
for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) {
if (it.value()->type() != Value::ItemValueType)
continue;
- forwardParameterDeclarations(it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
+ d->forwardParameterDeclarations(it.key(),
+ std::static_pointer_cast<ItemValue>(it.value())->item(),
+ modules);
}
}
-void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const ItemModuleList &modules)
+void ModuleLoader::Private::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
+ const Item::Modules &modules)
{
auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) {
return m.name == moduleName;
});
if (it != modules.end()) {
- item->setPropertyDeclarations(m_parameterDeclarations.value(rootPrototype(it->item)));
+ item->setPropertyDeclarations(parameterDeclarations[it->item->rootPrototype()]);
} else {
for (auto it = item->properties().begin(); it != item->properties().end(); ++it) {
if (it.value()->type() != Value::ItemValueType)
@@ -2683,1143 +336,4 @@ void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, I
}
}
-void ModuleLoader::resolveParameterDeclarations(const Item *module)
-{
- 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());
- }
- m_parameterDeclarations.insert(module, decls);
-}
-
-static bool isItemValue(const ValuePtr &v)
-{
- return v->type() == Value::ItemValueType;
-}
-
-static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
-{
- Item::PropertyMap result;
- auto itEnd = properties.end();
- for (auto it = properties.begin(); it != itEnd; ++it) {
- if (isItemValue(it.value()))
- result.insert(it.key(), it.value());
- }
- return result;
-}
-
-static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
-{
- QVariantMap result;
- handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
- const JSValue u = desc.value;
- if (JS_IsError(ctx, u))
- throw ErrorInfo(getJsString(ctx, u));
- const QString name = getJsString(ctx, prop);
- result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
- ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
- });
- return result;
-}
-
-QVariantMap ModuleLoader::extractParameters(Item *dependsItem) const
-{
- QVariantMap result;
- const Item::PropertyMap &itemProperties = filterItemProperties(
- rootPrototype(dependsItem)->properties());
- if (itemProperties.empty())
- return result;
-
- auto origProperties = dependsItem->properties();
- dependsItem->setProperties(itemProperties);
- JSValue sv = m_evaluator->scriptValue(dependsItem);
- try {
- result = safeToVariant(m_evaluator->engine()->context(), sv);
- } catch (const ErrorInfo &exception) {
- auto ei = exception;
- ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
- throw ei;
- }
- dependsItem->setProperties(origProperties);
- return result;
-}
-
-[[noreturn]] static void throwModuleNamePrefixError(const QualifiedId &shortName,
- const QualifiedId &longName, const CodeLocation &codeLocation)
-{
- throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the "
- "name of module '%2', which is not allowed")
- .arg(shortName.toString(), longName.toString()), codeLocation);
-}
-
-Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName)
-{
- QBS_CHECK(!moduleName.empty());
- Item *instance = containerItem;
- for (int i = 0; i < moduleName.size(); ++i) {
- const QString &moduleNameSegment = moduleName.at(i);
- const ValuePtr v = instance->ownProperty(moduleName.at(i));
- if (v && v->type() == Value::ItemValueType) {
- instance = std::static_pointer_cast<ItemValue>(v)->item();
- } else {
- const ItemType itemType = i < moduleName.size() - 1 ? ItemType::ModulePrefix
- : ItemType::ModuleInstance;
- auto newItem = Item::create(m_pool, itemType);
- instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
- instance = newItem;
- }
- if (i < moduleName.size() - 1) {
- if (instance->type() == ItemType::ModuleInstance) {
- QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1));
- throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation());
- }
- QBS_CHECK(instance->type() == ItemType::ModulePrefix);
- }
- }
- QBS_CHECK(instance != containerItem);
- return instance;
-}
-
-ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext,
- const QString &name, const QString &multiplexId, bool &productNameMatch)
-{
- auto &exportsData = productContext->project->topLevelProject->productModules;
- const auto firstIt = exportsData.find(name);
- productNameMatch = firstIt != exportsData.end();
- for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) {
- if (it.value().multiplexId == multiplexId)
- return &it.value();
- }
- if (multiplexId.isEmpty() && firstIt != exportsData.end())
- return &firstIt.value();
- return nullptr;
-}
-
-ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext,
- const QString &name)
-{
- auto itEnd = projectContext->products.end();
- auto it = std::find_if(projectContext->products.begin(), itEnd,
- [&name] (const ProductContext &ctx) {
- return ctx.name == name;
- });
- return it == itEnd ? nullptr : &*it;
-}
-
-ModuleLoader::ProductContext *ModuleLoader::product(TopLevelProjectContext *tlpContext,
- const QString &name)
-{
- ProductContext *result = nullptr;
- for (auto prj : tlpContext->projects) {
- result = product(prj, name);
- if (result)
- break;
- }
- return result;
-}
-
-class ModuleLoader::DependsChainManager
-{
-public:
- DependsChainManager(std::vector<DependsChainEntry> &dependsChain, const QualifiedId &module,
- const CodeLocation &dependsLocation)
- : m_dependsChain(dependsChain)
- {
- const bool alreadyInChain = Internal::any_of(dependsChain,
- [&module](const DependsChainEntry &e) {
- return e.name == module;
- });
- if (alreadyInChain) {
- ErrorInfo error;
- error.append(Tr::tr("Cyclic dependencies detected:"));
- for (const DependsChainEntry &e : qAsConst(m_dependsChain))
- error.append(e.name.toString(), e.location);
- error.append(module.toString(), dependsLocation);
- throw error;
- }
- m_dependsChain.emplace_back(module, dependsLocation);
- }
-
- ~DependsChainManager() { m_dependsChain.pop_back(); }
-
-private:
- std::vector<DependsChainEntry> &m_dependsChain;
-};
-
-static bool isBaseModule(QStringView fullModuleName)
-{
- return fullModuleName == StringConstants::qbsModule();
-}
-
-class DelayedPropertyChanger
-{
-public:
- ~DelayedPropertyChanger()
- {
- applyNow();
- }
-
- void setLater(Item *item, const QString &name, const ValuePtr &value)
- {
- QBS_CHECK(m_item == nullptr);
- m_item = item;
- m_name = name;
- m_value = value;
- }
-
- void removeLater(Item *item, const QString &name)
- {
- QBS_CHECK(m_item == nullptr);
- m_item = item;
- m_name = name;
- }
-
- void applyNow()
- {
- if (!m_item || m_name.isEmpty())
- return;
- if (m_value)
- m_item->setProperty(m_name, m_value);
- else
- m_item->removeProperty(m_name);
- m_item = nullptr;
- m_name.clear();
- m_value.reset();
- }
-
-private:
- Item *m_item = nullptr;
- QString m_name;
- ValuePtr m_value;
-};
-
-Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem,
- Item *item, const CodeLocation &dependsItemLocation,
- const QString &moduleId, const QualifiedId &moduleName,
- const QString &multiplexId, FallbackMode fallbackMode,
- bool isRequired, bool *isProductDependency,
- QVariantMap *defaultParameters)
-{
- qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId;
-
- RequiredChainManager requiredChainManager(m_requiredChain, isRequired);
- DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation);
-
- Item *moduleInstance = moduleId.isEmpty()
- ? moduleInstanceItem(item, moduleName)
- : moduleInstanceItem(item, QStringList(moduleId));
- if (moduleInstance->scope())
- return moduleInstance; // already handled
-
- if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) {
- for (const Item::Module &m : item->modules()) {
- if (m.name.front() == moduleName.front())
- throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation);
- }
- }
- QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
-
- // Prepare module instance for evaluating Module.condition.
- DelayedPropertyChanger delayedPropertyChanger;
- const QString &qbsModuleName = StringConstants::qbsModule();
- const auto fullName = moduleName.toString();
- if (!isBaseModule(fullName)) {
- ItemValuePtr qbsProp = productContext->item->itemProperty(qbsModuleName);
- if (qbsProp) {
- ValuePtr qbsModuleValue = moduleInstance->ownProperty(qbsModuleName);
- if (qbsModuleValue)
- delayedPropertyChanger.setLater(moduleInstance, qbsModuleName, qbsModuleValue);
- else
- delayedPropertyChanger.removeLater(moduleInstance, qbsModuleName);
- moduleInstance->setProperty(qbsModuleName, qbsProp);
- }
- }
-
- SearchPathsManager searchPathsManager(m_reader.get()); // paths can be added by providers
- Item *modulePrototype = nullptr;
- ProductModuleInfo * const pmi = productModule(productContext, fullName,
- multiplexId, *isProductDependency);
- if (pmi) {
- m_dependsChain.back().isProduct = true;
- modulePrototype = pmi->exportItem;
- if (defaultParameters)
- *defaultParameters = pmi->defaultParameters;
- } else if (!*isProductDependency) {
- modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation,
- moduleName, fallbackMode, isRequired, moduleInstance);
- }
- delayedPropertyChanger.applyNow();
- if (!modulePrototype)
- return nullptr;
-
- searchPathsManager.reset(); // deps must be processed in a clean state
-
- instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype,
- moduleName, pmi);
- return moduleInstance;
-}
-
-struct PrioritizedItem
-{
- PrioritizedItem(Item *item, int priority, int searchPathIndex)
- : item(item), priority(priority), searchPathIndex(searchPathIndex)
- {
- }
-
- Item *item = nullptr;
- int priority = 0;
- int searchPathIndex = 0;
-};
-
-static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates,
- const QString &moduleName)
-{
- auto maxIt = std::max_element(candidates.begin(), candidates.end(),
- [] (const PrioritizedItem &a, const PrioritizedItem &b) {
- if (a.priority < b.priority)
- return true;
- if (a.priority > b.priority)
- return false;
- return a.searchPathIndex > b.searchPathIndex;
- });
-
- size_t nmax = std::count_if(candidates.begin(), candidates.end(),
- [maxIt] (const PrioritizedItem &i) {
- return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex;
- });
-
- if (nmax > 1) {
- ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.")
- .arg(moduleName));
- for (size_t i = 0; i < candidates.size(); ++i) {
- const auto candidate = candidates.at(i);
- if (candidate.priority == maxIt->priority) {
- //: The %1 denotes the number of the candidate.
- e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location());
- }
- }
- throw e;
- }
-
- return maxIt->item;
-}
-
-Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext,
- const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired, Item *moduleInstance)
-{
- auto existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName);
-
- if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
- AccumulatingTimer providersTimer(
- m_parameters.logElapsedTime() ? &m_elapsedTimeModuleProviders : nullptr);
- auto result = m_moduleProviderLoader->executeModuleProviders(
- *productContext,
- dependsItemLocation,
- moduleName,
- fallbackMode);
- if (result.providerAddedSearchPaths) {
- qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
- << "with newly added search paths from module provider";
- existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName);
- }
- }
-
- const QString fullName = moduleName.toString();
- bool triedToLoadModule = false;
- std::vector<PrioritizedItem> candidates;
- candidates.reserve(size_t(existingPaths.size()));
- for (int i = 0; i < existingPaths.size(); ++i) {
- const QString &dirPath = existingPaths.at(i);
- QStringList &moduleFileNames = getModuleFileNames(dirPath);
- for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) {
- const QString &filePath = *it;
- const auto [module, triedToLoad] = loadModuleFile(
- productContext, fullName, filePath, moduleInstance);
- if (module)
- candidates.emplace_back(module, 0, i);
- if (!triedToLoad)
- it = moduleFileNames.erase(it);
- else
- ++it;
- triedToLoadModule = triedToLoadModule || triedToLoad;
- }
- }
-
- if (candidates.empty()) {
- if (!isRequired)
- return createNonPresentModule(fullName, QStringLiteral("not found"), nullptr);
- if (Q_UNLIKELY(triedToLoadModule)) {
- throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName),
- dependsItemLocation);
- }
- return nullptr;
- }
-
- Item *moduleItem;
- if (candidates.size() == 1) {
- moduleItem = candidates.at(0).item;
- } else {
- for (auto &candidate : candidates) {
- candidate.priority = m_evaluator->intValue(candidate.item,
- StringConstants::priorityProperty(),
- candidate.priority);
- }
- moduleItem = chooseModuleCandidate(candidates, fullName);
- }
-
- const auto it = productContext->unknownProfilePropertyErrors.find(moduleItem);
- if (it != productContext->unknownProfilePropertyErrors.cend()) {
- const QString fullProductName = ResolvedProduct::fullDisplayName
- (productContext->name, productContext->multiplexConfigurationId);
- ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
- "in profile '%3':").arg(fullName, fullProductName,
- productContext->profileName));
- for (const ErrorInfo &e : it->second)
- error.append(e.toString());
- handlePropertyError(error, m_parameters, m_logger);
- }
- return moduleItem;
-}
-
-QStringList &ModuleLoader::getModuleFileNames(const QString &dirPath)
-{
- QStringList &moduleFileNames = m_moduleDirListCache[dirPath];
- if (moduleFileNames.empty()) {
- QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
- while (dirIter.hasNext())
- moduleFileNames += dirIter.next();
- }
- return moduleFileNames;
-}
-
-static Item *findDeepestModuleInstance(Item *instance)
-{
- while (instance->prototype() && instance->prototype()->type() == ItemType::ModuleInstance)
- instance = instance->prototype();
- return instance;
-}
-
-std::pair<Item *, bool> ModuleLoader::loadModuleFile(
- ProductContext *productContext, const QString &fullModuleName,
- const QString &filePath, Item *moduleInstance)
-{
- checkCancelation();
-
- qCDebug(lcModuleLoader) << "loadModuleFile" << fullModuleName << "from" << filePath;
-
- const auto [module, triedToLoad] =
- getModulePrototype(productContext, fullModuleName, filePath);
- if (!module)
- return {nullptr, triedToLoad};
-
- const auto key = std::make_pair(module, productContext);
- const auto it = m_modulePrototypeEnabledInfo.find(key);
- if (it != m_modulePrototypeEnabledInfo.end()) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 2)";
- return {it.value() ? module : nullptr, triedToLoad};
- }
-
- // Set the name before evaluating any properties. Evaluator reads the module name.
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
-
- Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance);
- Item *origDeepestModuleInstancePrototype = deepestModuleInstance->prototype();
- deepestModuleInstance->setPrototype(module);
- bool enabled = checkItemCondition(moduleInstance, module);
- deepestModuleInstance->setPrototype(origDeepestModuleInstancePrototype);
- if (!enabled) {
- qCDebug(lcModuleLoader) << "condition of module" << fullModuleName << "is false";
- m_modulePrototypeEnabledInfo.insert(key, false);
- return {nullptr, triedToLoad};
- }
-
- if (isBaseModule(fullModuleName))
- setupBaseModulePrototype(module);
- else
- resolveParameterDeclarations(module);
-
- m_modulePrototypeEnabledInfo.insert(key, true);
- return {module, triedToLoad};
-}
-
-// Returns the module prototype item and a boolean indicating if we tried to load it from the file
-std::pair<Item *, bool> ModuleLoader::getModulePrototype(ProductContext *productContext,
- const QString &fullModuleName, const QString &filePath)
-{
- auto &prototypeList = m_modulePrototypes[filePath];
- for (const auto &prototype : prototypeList) {
- if (prototype.second == productContext->profileName) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
- return {prototype.first, true};
- }
- }
- Item * const module = loadItemFromFile(filePath, CodeLocation());
- if (module->type() != ItemType::Module) {
- qCDebug(lcModuleLoader).nospace()
- << "Alleged module " << fullModuleName << " has type '"
- << module->typeName() << "', so it's not a module after all.";
- return {nullptr, false};
- }
- prototypeList.emplace_back(module, productContext->profileName);
-
- // Module properties that are defined in the profile are used as default values.
- // This is the reason we need to have different items per profile.
- const QVariantMap profileModuleProperties
- = productContext->moduleProperties.value(fullModuleName).toMap();
- for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) {
- if (Q_UNLIKELY(!module->hasProperty(it.key()))) {
- productContext->unknownProfilePropertyErrors[module].emplace_back
- (Tr::tr("Unknown property: %1.%2").arg(fullModuleName, it.key()));
- continue;
- }
- const PropertyDeclaration decl = module->propertyDeclaration(it.key());
- VariantValuePtr v = VariantValue::create(
- PropertyDeclaration::convertToPropertyType(it.value(), decl.type(),
- QStringList(fullModuleName), it.key()));
- module->setProperty(it.key(), v);
- }
-
- return {module, true};
-}
-
-Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item)
-{
- const QualifiedId baseModuleName(StringConstants::qbsModule());
- Item::Module baseModuleDesc;
- baseModuleDesc.name = baseModuleName;
- baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(),
- baseModuleName, QString(), FallbackMode::Disabled, true,
- &baseModuleDesc.isProduct, nullptr);
- if (productContext->item) {
- const Item * const qbsInstanceItem
- = moduleInstanceItem(productContext->item, baseModuleName);
- const Item::PropertyMap &props = qbsInstanceItem->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- if (it.value()->type() == Value::VariantValueType)
- baseModuleDesc.item->setProperty(it.key(), it.value());
- }
- }
- QBS_CHECK(!baseModuleDesc.isProduct);
- if (Q_UNLIKELY(!baseModuleDesc.item))
- throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
- return baseModuleDesc;
-}
-
-void ModuleLoader::setupBaseModulePrototype(Item *prototype)
-{
- prototype->setProperty(QStringLiteral("hostPlatform"),
- VariantValue::create(HostOsInfo::hostOSIdentifier()));
- prototype->setProperty(QStringLiteral("hostArchitecture"),
- VariantValue::create(HostOsInfo::hostOSArchitecture()));
- prototype->setProperty(QStringLiteral("libexecPath"),
- VariantValue::create(m_parameters.libexecPath()));
-
- const Version qbsVersion = LanguageInfo::qbsVersion();
- prototype->setProperty(QStringLiteral("versionMajor"),
- VariantValue::create(qbsVersion.majorVersion()));
- prototype->setProperty(QStringLiteral("versionMinor"),
- VariantValue::create(qbsVersion.minorVersion()));
- prototype->setProperty(QStringLiteral("versionPatch"),
- VariantValue::create(qbsVersion.patchLevel()));
-}
-
-static void collectItemsWithId_impl(Item *item, QList<Item *> *result)
-{
- if (!item->id().isEmpty())
- result->push_back(item);
- for (Item * const child : item->children())
- collectItemsWithId_impl(child, result);
-}
-
-static QList<Item *> collectItemsWithId(Item *item)
-{
- QList<Item *> result;
- collectItemsWithId_impl(item, &result);
- return result;
-}
-
-static std::vector<std::pair<QualifiedId, ItemValuePtr>> instanceItemProperties(Item *item)
-{
- std::vector<std::pair<QualifiedId, ItemValuePtr>> result;
- QualifiedId name;
- const auto func = [&] (Item *item, const auto &f) -> void {
- for (auto it = item->properties().begin(), end = item->properties().end();
- it != end; ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- ItemValuePtr itemValue = std::static_pointer_cast<ItemValue>(it.value());
- if (!itemValue->item())
- continue;
- name.push_back(it.key());
- if (itemValue->item()->type() == ItemType::ModulePrefix)
- f(itemValue->item(), f);
- else
- result.emplace_back(name, itemValue);
- name.removeLast();
- }
- };
- func(item, func);
- return result;
-}
-
-void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct,
- Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo)
-{
- Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance);
- deepestModuleInstance->setPrototype(modulePrototype);
- const QString fullName = moduleName.toString();
- const QString generalOverrideKey = QStringLiteral("modules.") + 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"))
- overrideItemProperties(instance, fullName, m_parameters.overriddenValuesTree());
- overrideItemProperties(instance, perProductOverrideKey,
- m_parameters.overriddenValuesTree());
- if (instance == deepestModuleInstance)
- break;
- }
-
- moduleInstance->setFile(modulePrototype->file());
- moduleInstance->setLocation(modulePrototype->location());
- QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
-
- // create module scope
- Item *moduleScope = Item::create(m_pool, ItemType::Scope);
- QBS_CHECK(instanceScope->file());
- moduleScope->setFile(instanceScope->file());
- moduleScope->setScope(instanceScope);
- QBS_CHECK(productContext->project->scope);
- productContext->project->scope->copyProperty(StringConstants::projectVar(), moduleScope);
- if (productContext->scope)
- productContext->scope->copyProperty(StringConstants::productVar(), moduleScope);
- else
- QBS_CHECK(fullName == StringConstants::qbsModule()); // Dummy product.
-
- if (productModuleInfo) {
- exportingProduct = productModuleInfo->exportItem->parent();
- QBS_CHECK(exportingProduct);
- QBS_CHECK(exportingProduct->type() == ItemType::Product);
- }
-
- if (exportingProduct) {
- const auto exportingProductItemValue = ItemValue::create(exportingProduct);
- moduleScope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue);
-
- const auto importingProductItemValue = ItemValue::create(productContext->item);
- moduleScope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue);
-
- moduleScope->setProperty(StringConstants::projectVar(),
- ItemValue::create(exportingProduct->parent()));
-
- PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(),
- PropertyDeclaration::String, QString(),
- PropertyDeclaration::PropertyNotAvailableInConfig);
- moduleInstance->setPropertyDeclaration(pd.name(), pd);
- ValuePtr v = exportingProduct
- ->property(StringConstants::sourceDirectoryProperty())->clone();
- moduleInstance->setProperty(pd.name(), v);
- }
- moduleInstance->setScope(moduleScope);
-
- QHash<Item *, Item *> prototypeInstanceMap;
- prototypeInstanceMap[modulePrototype] = moduleInstance;
-
- // create instances for every child of the prototype
- createChildInstances(moduleInstance, modulePrototype, &prototypeInstanceMap);
-
- // create ids from from the prototype in the instance
- if (modulePrototype->file()->idScope()) {
- const auto items = collectItemsWithId(modulePrototype);
- for (Item * const itemWithId : items) {
- Item *idProto = itemWithId;
- Item *idInstance = prototypeInstanceMap.value(idProto);
- QBS_ASSERT(idInstance, continue);
- ItemValuePtr idInstanceValue = ItemValue::create(idInstance);
- moduleScope->setProperty(itemWithId->id(), idInstanceValue);
- }
- }
-
- // For foo.bar in modulePrototype create an item foo in moduleInstance.
- for (const auto &[propertyName, itemValue] : instanceItemProperties(modulePrototype)) {
- if (itemValue->item()->properties().empty())
- continue;
- qCDebug(lcModuleLoader) << "The prototype of " << moduleName
- << " sets properties on " << propertyName.toString();
- Item *item = moduleInstanceItem(moduleInstance, propertyName);
- item->setPrototype(itemValue->item());
- if (itemValue->createdByPropertiesBlock()) {
- ItemValuePtr itemValue = moduleInstance->itemProperty(propertyName.front());
- for (int i = 1; i < propertyName.size(); ++i)
- itemValue = itemValue->item()->itemProperty(propertyName.at(i));
- itemValue->setCreatedByPropertiesBlock(true);
- }
- }
-
- // Resolve dependencies of this module instance.
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.exportingProductItem = exportingProduct;
- QBS_ASSERT(moduleInstance->modules().empty(), moduleInstance->removeModules());
- if (productModuleInfo) {
- dependsContext.productDependencies = &productContext->productModuleDependencies[fullName];
- resolveDependencies(&dependsContext, moduleInstance);
- } else if (!isBaseModule(fullName)) {
- dependsContext.productDependencies = &productContext->info.usedProducts;
- resolveDependencies(&dependsContext, moduleInstance);
- }
-
- // Check readonly properties.
- const auto end = moduleInstance->properties().cend();
- for (auto it = moduleInstance->properties().cbegin(); it != end; ++it) {
- const PropertyDeclaration &pd = moduleInstance->propertyDeclaration(it.key());
- if (!pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag))
- continue;
- throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
- moduleInstance->property(pd.name())->location());
- }
-}
-
-void ModuleLoader::createChildInstances(Item *instance, Item *prototype,
- QHash<Item *, Item *> *prototypeInstanceMap) const
-{
- instance->childrenReserve(instance->children().size() + prototype->children().size());
-
- for (Item * const childPrototype : prototype->children()) {
- Item *childInstance = Item::create(m_pool, childPrototype->type());
- prototypeInstanceMap->insert(childPrototype, childInstance);
- childInstance->setPrototype(childPrototype);
- childInstance->setFile(childPrototype->file());
- childInstance->setId(childPrototype->id());
- childInstance->setLocation(childPrototype->location());
- childInstance->setScope(instance->scope());
- Item::addChild(instance, childInstance);
- createChildInstances(childInstance, childPrototype, prototypeInstanceMap);
- }
-}
-
-void ModuleLoader::checkCancelation() const
-{
- if (m_progressObserver && m_progressObserver->canceled()) {
- throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
- .arg(TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree())));
- }
-}
-
-bool ModuleLoader::checkItemCondition(Item *item, Item *itemToDisable)
-{
- if (m_evaluator->boolValue(item, StringConstants::conditionProperty()))
- return true;
- m_disabledItems += itemToDisable ? itemToDisable : item;
- return false;
-}
-
-QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet)
-{
- QStringList result;
- const QStringList paths = m_evaluator->stringListValue(
- item, StringConstants::qbsSearchPathsProperty(), wasSet);
- const JSSourceValueConstPtr prop = item->sourceProperty(
- StringConstants::qbsSearchPathsProperty());
-
- // Value can come from within a project file or as an overridden value from the user
- // (e.g command line).
- const QString basePath = FileInfo::path(prop ? prop->file()->filePath()
- : m_parameters.projectFilePath());
- for (const QString &path : paths)
- result += FileInfo::resolvePath(basePath, path);
- return result;
-}
-
-void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject)
-{
- if (!sourceProject)
- return;
- const QList<PropertyDeclaration> builtinProjectProperties = BuiltinDeclarations::instance()
- .declarationsForType(ItemType::Project).properties();
- Set<QString> builtinProjectPropertyNames;
- for (const PropertyDeclaration &p : builtinProjectProperties)
- builtinProjectPropertyNames << p.name();
-
- for (Item::PropertyDeclarationMap::ConstIterator it
- = sourceProject->propertyDeclarations().constBegin();
- it != sourceProject->propertyDeclarations().constEnd(); ++it) {
-
- // We must not inherit built-in properties such as "name",
- // but there are exceptions.
- if (it.key() == StringConstants::qbsSearchPathsProperty()
- || it.key() == StringConstants::profileProperty()
- || it.key() == StringConstants::buildDirectoryProperty()
- || it.key() == StringConstants::sourceDirectoryProperty()
- || it.key() == StringConstants::minimumQbsVersionProperty()) {
- const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key());
- QBS_ASSERT(v, continue);
- if (v->sourceCode() == StringConstants::undefinedValue())
- sourceProject->copyProperty(it.key(), targetProject);
- continue;
- }
-
- if (builtinProjectPropertyNames.contains(it.key()))
- continue;
-
- if (targetProject->hasOwnProperty(it.key()))
- continue; // Ignore stuff the target project already has.
-
- targetProject->setPropertyDeclaration(it.key(), it.value());
- sourceProject->copyProperty(it.key(), targetProject);
- }
-}
-
-Item *ModuleLoader::wrapInProjectIfNecessary(Item *item)
-{
- if (item->type() == ItemType::Project)
- return item;
- Item *prj = Item::create(item->pool(), ItemType::Project);
- Item::addChild(prj, item);
- prj->setFile(item->file());
- prj->setLocation(item->location());
- prj->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- return prj;
-}
-
-QString ModuleLoader::findExistingModulePath(const QString &searchPath,
- const QualifiedId &moduleName)
-{
- // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the
- // modules and search paths we've already processed
- auto &moduleInfo = m_existingModulePathCache[{searchPath, moduleName}];
- if (moduleInfo)
- return *moduleInfo;
-
- QString dirPath = searchPath + QStringLiteral("/modules");
-
- for (const QString &moduleNamePart : moduleName) {
- dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
- if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) {
- return *(moduleInfo = QString());
- }
- }
-
- return *(moduleInfo = dirPath);
-}
-
-QStringList ModuleLoader::findExistingModulePaths(
- const QStringList &searchPaths, const QualifiedId &moduleName)
-{
- QStringList result;
- result.reserve(searchPaths.size());
- for (const auto &path: searchPaths) {
- const QString dirPath = findExistingModulePath(path, moduleName);
- if (!dirPath.isEmpty())
- result.append(dirPath);
- }
- return result;
-}
-
-void ModuleLoader::setScopeForDescendants(Item *item, Item *scope)
-{
- for (Item * const child : item->children()) {
- child->setScope(scope);
- setScopeForDescendants(child, scope);
- }
-}
-
-void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey,
- const QVariantMap &buildConfig)
-{
- const QVariant buildConfigValue = buildConfig.value(buildConfigKey);
- if (buildConfigValue.isNull())
- return;
- item->overrideProperties(buildConfigValue.toMap(), buildConfigKey, m_parameters, m_logger);
-}
-
-void ModuleLoader::collectAllModules(Item *item, std::vector<Item::Module> *modules)
-{
- for (const Item::Module &m : item->modules()) {
- if (moduleRepresentsDisabledProduct(m))
- m.item->removeModules();
- auto it = std::find_if(modules->begin(), modules->end(),
- [m] (const Item::Module &m2) { return m.name == m2.name; });
- if (it != modules->end()) {
- // If a module is required somewhere, it is required in the top-level item.
- if (m.required)
- it->required = true;
- it->versionRange.narrowDown(m.versionRange);
- it->fallbackEnabled = it->fallbackEnabled && m.fallbackEnabled;
- continue;
- }
- modules->push_back(m);
- collectAllModules(m.item, modules);
- }
-}
-
-std::vector<Item::Module> ModuleLoader::allModules(Item *item)
-{
- std::vector<Item::Module> lst;
- collectAllModules(item, &lst);
- return lst;
-}
-
-bool ModuleLoader::moduleRepresentsDisabledProduct(const Item::Module &module)
-{
- if (!module.isProduct)
- return false;
- const Item *exportItem = module.item->prototype();
- while (exportItem && exportItem->type() != ItemType::Export)
- exportItem = exportItem->prototype();
- QBS_CHECK(exportItem);
- Item * const productItem = exportItem->parent();
- QBS_CHECK(productItem->type() == ItemType::Product);
- return m_disabledItems.contains(productItem) || !checkItemCondition(productItem);
-}
-
-void ModuleLoader::addProductModuleDependencies(ProductContext *productContext, const QString &name)
-{
- auto deps = productContext->productModuleDependencies.at(name);
- QList<ModuleLoaderResult::ProductInfo::Dependency> depsToAdd;
- const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty();
- for (auto &dep : deps) {
- const auto productRange = m_productsByName.equal_range(dep.name);
- std::vector<const ProductContext *> dependencies;
- bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty()) {
- dependencies.push_back(it->second);
- if (productIsMultiplexed && dep.profile.isEmpty())
- break;
- } else {
- hasNonMultiplexedDependency = true;
- break;
- }
- }
-
- if (hasNonMultiplexedDependency) {
- depsToAdd.push_back(dep);
- continue;
- }
-
- for (std::size_t i = 0; i < dependencies.size(); ++i) {
- const bool profileMatch = dep.profile.isEmpty()
- || dep.profile == StringConstants::star()
- || dep.profile == dependencies.at(i)->profileName;
- if (i == 0) {
- if (productIsMultiplexed && dep.profile.isEmpty()) {
- const ValuePtr &multiplexConfigIdProp = productContext->item->property(
- StringConstants::multiplexConfigurationIdProperty());
- dep.multiplexConfigurationId = std::static_pointer_cast<VariantValue>(
- multiplexConfigIdProp)->value().toString();
- depsToAdd.push_back(dep);
- break;
- }
- if (profileMatch) {
- dep.multiplexConfigurationId = dependencies.at(i)->multiplexConfigurationId;
- depsToAdd.push_back(dep);
- }
- } else if (profileMatch) {
- ModuleLoaderResult::ProductInfo::Dependency newDependency = dep;
- newDependency.multiplexConfigurationId
- = dependencies.at(i)->multiplexConfigurationId;
- depsToAdd << newDependency;
- }
- }
- }
- productContext->info.usedProducts.insert(productContext->info.usedProducts.end(),
- depsToAdd.cbegin(), depsToAdd.cend());
-}
-
-static void collectProductModuleDependencies(Item *item, Set<QualifiedId> &allDeps)
-{
- for (const Item::Module &m : item->modules()) {
- if (m.isProduct && allDeps.insert(m.name).second)
- collectProductModuleDependencies(m.item, allDeps);
- }
-}
-
-void ModuleLoader::addProductModuleDependencies(ModuleLoader::ProductContext *ctx)
-{
- Set<QualifiedId> deps;
- collectProductModuleDependencies(ctx->item, deps);
- for (const QualifiedId &dep : deps)
- addProductModuleDependencies(ctx, dep.toString());
-}
-
-void ModuleLoader::addTransitiveDependencies(ProductContext *ctx)
-{
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimeTransitiveDependencies : nullptr);
- qCDebug(lcModuleLoader) << "addTransitiveDependencies";
-
- std::vector<Item::Module> transitiveDeps = allModules(ctx->item);
- std::sort(transitiveDeps.begin(), transitiveDeps.end());
- for (const Item::Module &m : ctx->item->modules()) {
- auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m);
- QBS_CHECK(it != transitiveDeps.end() && it->name == m.name);
- transitiveDeps.erase(it);
- }
- for (const Item::Module &module : qAsConst(transitiveDeps)) {
- if (module.isProduct) {
- ctx->item->addModule(module);
- } else {
- const FallbackMode fallbackMode = module.fallbackEnabled
- ? FallbackMode::Enabled : FallbackMode::Disabled;
- Item::Module dep;
- dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(),
- module.name, QString(), fallbackMode,
- module.required, &dep.isProduct, &dep.parameters);
- if (!dep.item) {
- throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive "
- "dependencies for product '%2'.").arg(module.name.toString(),
- ctx->name),
- ctx->item->location());
- }
- dep.name = module.name;
- dep.required = module.required;
- dep.versionRange = module.versionRange;
- dep.fallbackEnabled = fallbackMode == FallbackMode::Enabled;
- ctx->item->addModule(dep);
- }
- }
-}
-
-Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module)
-{
- qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")."
- << "Creating dummy module for presence check.";
- if (!module) {
- module = Item::create(m_pool, ItemType::ModuleInstance);
- module->setFile(FileContext::create());
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(name));
- }
- module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue());
- return module;
-}
-
-void ModuleLoader::handleProductError(const ErrorInfo &error,
- ModuleLoader::ProductContext *productContext)
-{
- const bool alreadyHadError = productContext->info.delayedError.hasError();
- if (!alreadyHadError) {
- productContext->info.delayedError.append(Tr::tr("Error while handling product '%1':")
- .arg(productContext->name),
- productContext->item->location());
- }
- if (error.isInternalError()) {
- if (alreadyHadError) {
- qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
- << "in product" << productContext->name;
- return;
- }
- for (const auto &kv : productContext->productModuleDependencies) {
- const auto rangeForName = m_productsByName.equal_range(kv.first);
- for (auto rangeIt = rangeForName.first; rangeIt != rangeForName.second; ++rangeIt) {
- const ProductContext * const dep = rangeIt->second;
- if (dep->info.delayedError.hasError()) {
- qCDebug(lcModuleLoader()) << "ignoring internal error" << error.toString()
- << "in product" << productContext->name
- << "assumed to be caused by erroneous dependency"
- << dep->name;
- return;
- }
- }
- }
- }
- const auto errorItems = error.items();
- for (const ErrorItem &ei : errorItems)
- productContext->info.delayedError.append(ei.description(), ei.codeLocation());
- productContext->project->result->productInfos[productContext->item] = productContext->info;
- m_disabledItems << productContext->item;
- m_erroneousProducts.insert(productContext->name);
-}
-
-static void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
- QualifiedIdSet &properties)
-{
- const Item::PropertyMap &props = iv->item()->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- switch (it.value()->type()) {
- case Value::JSSourceValueType:
- properties << (QualifiedId(prefix) << it.key());
- break;
- case Value::ItemValueType:
- if (iv->item()->type() == ItemType::ModulePrefix) {
- gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
- QualifiedId(prefix) << it.key(), properties);
- }
- break;
- default:
- break;
- }
- }
-}
-
-QualifiedIdSet ModuleLoader::gatherModulePropertiesSetInGroup(const Item *group)
-{
- QualifiedIdSet propsSetInGroup;
- const Item::PropertyMap &props = group->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- if (it.value()->type() == Value::ItemValueType) {
- gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
- QualifiedId(it.key()), propsSetInGroup);
- }
- }
- return propsSetInGroup;
-}
-
-void ModuleLoader::markModuleTargetGroups(Item *group, const Item::Module &module)
-{
- QBS_CHECK(group->type() == ItemType::Group);
- if (m_evaluator->boolValue(group, StringConstants::filesAreTargetsProperty())) {
- group->setProperty(StringConstants::modulePropertyInternal(),
- VariantValue::create(module.name.toString()));
- }
- for (Item * const child : group->children())
- markModuleTargetGroups(child, module);
-}
-
-void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext,
- const Item::Module &module,
- const Item *modulePrototype)
-{
- for (Item * const child : modulePrototype->children()) {
- if (child->type() == ItemType::Group) {
- Item * const clonedGroup = child->clone();
- clonedGroup->setScope(productContext.scope);
- setScopeForDescendants(clonedGroup, productContext.scope);
- Item::addChild(productContext.item, clonedGroup);
- markModuleTargetGroups(clonedGroup, module);
- }
- }
-}
-
-void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext)
-{
- for (const Item::Module &module : productContext.item->modules()) {
- Item *prototype = module.item;
- bool modulePassedValidation;
- while ((modulePassedValidation = prototype->isPresentModule()) && prototype->prototype())
- prototype = prototype->prototype();
- if (modulePassedValidation)
- copyGroupsFromModuleToProduct(productContext, module, prototype);
- }
-}
-
-QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const
-{
- return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
-}
-
-QString ModuleLoader::ProductContext::uniqueName() const
-{
- return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
-}
-
-} // namespace Internal
-} // namespace qbs
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h
index db0b51cae..2e1140726 100644
--- a/src/lib/corelib/language/moduleloader.h
+++ b/src/lib/corelib/language/moduleloader.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
@@ -37,423 +37,48 @@
**
****************************************************************************/
-#ifndef QBS_MODULELOADER_H
-#define QBS_MODULELOADER_H
+#pragma once
-#include "filetags.h"
-#include "forward_decls.h"
#include "item.h"
-#include "itempool.h"
-#include "moduleproviderinfo.h"
-#include <logging/logger.h>
-#include <tools/filetime.h>
-#include <tools/qttools.h>
-#include <tools/set.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/version.h>
-#include <QtCore/qmap.h>
-#include <QtCore/qstringlist.h>
-#include <QtCore/qvariant.h>
-
-#include <map>
-#include <memory>
-#include <optional>
-#include <unordered_map>
-#include <utility>
-#include <vector>
+#include <QString>
+#include <QVariantMap>
namespace qbs {
-
-class CodeLocation;
-class Settings;
-
+class SetupProjectParameters;
namespace Internal {
-
class Evaluator;
-class Item;
class ItemReader;
-class ModuleProviderLoader;
-class ProbesResolver;
-class ProgressObserver;
-class QualifiedId;
-class SearchPathsManager;
-
-using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
-
-struct ModuleLoaderResult
-{
- ModuleLoaderResult()
- : itemPool(new ItemPool), root(nullptr)
- {}
-
- struct ProductInfo
- {
- struct Dependency
- {
- QString name;
- QString profile; // "*" <=> Match all profiles.
- QString multiplexConfigurationId;
- QVariantMap parameters;
- bool limitToSubProject = false;
- bool isRequired = true;
+class Logger;
- QString uniqueName() const;
- };
-
- std::vector<ProbeConstPtr> probes;
- std::vector<Dependency> usedProducts;
- ModulePropertiesPerGroup modulePropertiesSetInGroups;
- ErrorInfo delayedError;
- };
-
- std::shared_ptr<ItemPool> itemPool;
- Item *root;
- std::unordered_map<Item *, ProductInfo> productInfos;
- std::vector<ProbeConstPtr> projectProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- Set<QString> qbsFiles;
- QVariantMap profileConfigs;
-};
-
-/*
- * Loader stage II. Responsible for
- * - loading modules and module dependencies,
- * - project references,
- * - Probe items.
- */
class ModuleLoader
{
public:
- ModuleLoader(Evaluator *evaluator, Logger &logger);
+ ModuleLoader(const SetupProjectParameters &setupParameters, ItemReader &itemReader,
+ Evaluator &evaluator, Logger &logger);
~ModuleLoader();
- void setProgressObserver(ProgressObserver *progressObserver);
- void setSearchPaths(const QStringList &searchPaths);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; }
- void setStoredProfiles(const QVariantMap &profiles);
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- Evaluator *evaluator() const { return m_evaluator; }
-
- ModuleLoaderResult load(const SetupProjectParameters &parameters);
-
-private:
- friend class ModuleProviderLoader;
- friend class ProbesResolver;
- class ProductSortByDependencies;
-
- class ContextBase
- {
- public:
- ContextBase()
- : item(nullptr), scope(nullptr)
- {}
-
- Item *item;
- Item *scope;
- QString name;
- };
-
- class ProjectContext;
-
- using ProductDependencies = std::vector<ModuleLoaderResult::ProductInfo::Dependency>;
-
- // This is the data we need to store at the point where a dependency is deferred
- // in order to properly resolve the dependency in pass 2.
- struct DeferredDependsContext {
- DeferredDependsContext(Item *exportingProduct, Item *parent)
- : exportingProductItem(exportingProduct), parentItem(parent) {}
- Item *exportingProductItem = nullptr;
- Item *parentItem = nullptr;
- bool operator==(const DeferredDependsContext &other) const
- {
- return exportingProductItem == other.exportingProductItem
- && parentItem == other.parentItem;
- }
- bool operator<(const DeferredDependsContext &other) const
- {
- return parentItem < other.parentItem;
- }
- };
-
- class ProductContext : public ContextBase
- {
- public:
- ProjectContext *project = nullptr;
- ModuleLoaderResult::ProductInfo info;
- QString profileName;
- QString multiplexConfigurationId;
- QVariantMap moduleProperties;
- std::map<QString, ProductDependencies> productModuleDependencies;
- std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors;
- QStringList searchPaths;
-
- std::optional<QVariantMap> theModuleProviderConfig;
-
- // The key corresponds to DeferredDependsContext.exportingProductItem, which is the
- // only value from that data structure that we still need here.
- std::unordered_map<Item *, std::vector<Item *>> deferredDependsItems;
-
- QString uniqueName() const;
- };
-
- class TopLevelProjectContext;
-
- class ProjectContext : public ContextBase
- {
- public:
- TopLevelProjectContext *topLevelProject = nullptr;
- ModuleLoaderResult *result = nullptr;
- std::vector<ProductContext> products;
- std::vector<QStringList> searchPathsStack;
- };
-
- struct ProductModuleInfo
- {
- Item *exportItem = nullptr;
- QString multiplexId;
- QVariantMap defaultParameters;
+ struct ProductContext {
+ const Item *item = nullptr;
+ const QString &name;
+ const QString &profile;
+ const QVariantMap &profileModuleProperties;
};
+ // TODO: Entry point should be searchAndLoadModuleFile(). Needs ProviderLoader clean-up first.
+ std::pair<Item *, bool> loadModuleFile(const ProductContext &product,
+ const QString &moduleName, const QString &filePath);
- class TopLevelProjectContext
- {
- Q_DISABLE_COPY(TopLevelProjectContext)
- public:
- TopLevelProjectContext() = default;
- ~TopLevelProjectContext() { qDeleteAll(projects); }
+ void checkDependencyParameterDeclarations(const ProductContext &product) const;
+ void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules);
- std::vector<ProjectContext *> projects;
- QMultiHash<QString, ProductModuleInfo> productModules;
- std::vector<ProbeConstPtr> probes;
- QString buildDirectory;
- };
-
- class DependsContext
- {
- public:
- ProductContext *product = nullptr;
- Item *exportingProductItem = nullptr;
- ProductDependencies *productDependencies = nullptr;
- };
-
- void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
- const QString &buildDirectory, const Set<QString> &referencedFilePaths);
- void handleProject(ModuleLoaderResult *loadResult,
- TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths);
-
- using MultiplexRow = std::vector<VariantValuePtr>;
- using MultiplexTable = std::vector<MultiplexRow>;
-
- struct MultiplexInfo
- {
- std::vector<QString> properties;
- MultiplexTable table;
- bool aggregate = false;
- VariantValuePtr multiplexedType;
-
- QString toIdString(size_t row) const;
- static QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
- };
-
- void dump(const MultiplexInfo &mpi);
- static MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values);
- MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
- QList<Item *> multiplexProductItem(ProductContext *dummyContext, Item *productItem);
- void normalizeDependencies(ProductContext *product,
- const DeferredDependsContext &dependsContext);
- void adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp);
- void adjustDependenciesForMultiplexing(const ProductContext &product);
- void adjustDependenciesForMultiplexing(const ProductContext &product, Item *dependsItem);
-
- void prepareProduct(ProjectContext *projectContext, Item *productItem);
- void setupProductDependencies(ProductContext *productContext,
- const Set<DeferredDependsContext> &deferredDependsContext);
- void handleProduct(ProductContext *productContext);
- void checkDependencyParameterDeclarations(const ProductContext *productContext) const;
- void handleModuleSetupError(ProductContext *productContext, const Item::Module &module,
- const ErrorInfo &error);
- void initProductProperties(const ProductContext &product);
- void handleSubProject(ProjectContext *projectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths);
- QList<Item *> loadReferencedFile(const QString &relativePath,
- const CodeLocation &referencingLocation,
- const Set<QString> &referencedFilePaths,
- ProductContext &dummyContext);
- void handleAllPropertyOptionsItems(Item *item);
- void handlePropertyOptions(Item *optionsItem);
-
- using ModuleDependencies = QHash<QualifiedId, QualifiedIdSet>;
- void setupReverseModuleDependencies(const Item::Module &module, ModuleDependencies &deps,
- QualifiedIdSet &seenModules);
- ModuleDependencies setupReverseModuleDependencies(const Item *product);
- void handleGroup(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies);
- void propagateModulesFromParent(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies);
- void adjustDefiningItemsInGroupModuleInstances(const Item::Module &module,
- const Item::Modules &dependentModules);
-
- bool mergeExportItems(const ProductContext &productContext);
- void resolveDependencies(DependsContext *dependsContext, Item *item,
- ProductContext *productContext = nullptr);
- class ItemModuleList;
- void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem,
- ItemModuleList *moduleResults, ProductDependencies *productResults);
- void forwardParameterDeclarations(const Item *dependsItem, const ItemModuleList &modules);
- void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const ItemModuleList &modules);
- void resolveParameterDeclarations(const Item *module);
- QVariantMap extractParameters(Item *dependsItem) const;
- Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName);
- static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name,
- const QString &multiplexId, bool &productNameMatch);
- static ProductContext *product(ProjectContext *projectContext, const QString &name);
- static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name);
-
- enum class FallbackMode { Enabled, Disabled };
- Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item,
- const CodeLocation &dependsItemLocation, const QString &moduleId,
- const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode,
- bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters);
- Item *searchAndLoadModuleFile(ProductContext *productContext,
- const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired, Item *moduleInstance);
- QStringList &getModuleFileNames(const QString &dirPath);
- std::pair<Item *, bool> loadModuleFile(
- ProductContext *productContext, const QString &fullModuleName,
- const QString &filePath, Item *moduleInstance);
- std::pair<Item *, bool> getModulePrototype(ProductContext *productContext,
- const QString &fullModuleName, const QString &filePath);
- Item::Module loadBaseModule(ProductContext *productContext, Item *item);
- void setupBaseModulePrototype(Item *prototype);
- template <typename T, typename F>
- T callWithTemporaryBaseModule(ProductContext *productContext, const F &func);
- void instantiateModule(ProductContext *productContext, Item *exportingProductItem,
- Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo);
- void createChildInstances(Item *instance, Item *prototype,
- QHash<Item *, Item *> *prototypeInstanceMap) const;
- void checkCancelation() const;
- bool checkItemCondition(Item *item, Item *itemToDisable = nullptr);
- QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
- void copyProperties(const Item *sourceProject, Item *targetProject);
- Item *wrapInProjectIfNecessary(Item *item);
- QString findExistingModulePath(const QString &searchPath, const QualifiedId &moduleName);
- QStringList findExistingModulePaths(
- const QStringList &searchPaths, const QualifiedId &moduleName);
-
- static void setScopeForDescendants(Item *item, Item *scope);
- void overrideItemProperties(Item *item, const QString &buildConfigKey,
- const QVariantMap &buildConfig);
- void addProductModuleDependencies(ProductContext *ctx, const QString &name);
- void addProductModuleDependencies(ProductContext *ctx);
- void addTransitiveDependencies(ProductContext *ctx);
- Item *createNonPresentModule(const QString &name, const QString &reason, Item *module);
- void copyGroupsFromModuleToProduct(const ProductContext &productContext,
- const Item::Module &module, const Item *modulePrototype);
- void copyGroupsFromModulesToProduct(const ProductContext &productContext);
- void markModuleTargetGroups(Item *group, const Item::Module &module);
- bool checkExportItemCondition(Item *exportItem, const ProductContext &productContext);
-
- void printProfilingInfo();
- void handleProductError(const ErrorInfo &error, ProductContext *productContext);
- QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group);
- Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation);
- void collectProductsByName(const TopLevelProjectContext &topLevelProject);
- void collectProductsByType(const TopLevelProjectContext &topLevelProject);
-
- void handleProfileItems(Item *item, ProjectContext *projectContext);
- std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext);
- 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();
- void setSearchPathsForProduct(ProductContext *product);
-
- Item::Modules modulesSortedByDependency(const Item *productItem);
- void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules);
- void collectAllModules(Item *item, std::vector<Item::Module> *modules);
- std::vector<Item::Module> allModules(Item *item);
- bool moduleRepresentsDisabledProduct(const Item::Module &module);
-
- using ShadowProductInfo = std::pair<bool, QString>;
- ShadowProductInfo getShadowProductInfo(const ProductContext &product) const;
-
- ItemPool *m_pool;
- Logger &m_logger;
- ProgressObserver *m_progressObserver;
- const std::unique_ptr<ItemReader> m_reader;
- Evaluator *m_evaluator;
- const std::unique_ptr<ProbesResolver> m_probesResolver;
- const std::unique_ptr<ModuleProviderLoader> m_moduleProviderLoader;
- QMap<QString, QStringList> m_moduleDirListCache;
- QHash<std::pair<QString, QualifiedId>, std::optional<QString>> m_existingModulePathCache;
-
- // The keys are file paths, the values are module prototype items accompanied by a profile.
- std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> m_modulePrototypes;
-
- // The keys are module prototypes and products, the values specify whether the module's
- // condition is true for that product.
- QHash<std::pair<Item *, ProductContext *>, bool> m_modulePrototypeEnabledInfo;
-
- QHash<const Item *, Item::PropertyDeclarationMap> m_parameterDeclarations;
- Set<Item *> m_disabledItems;
- std::vector<bool> m_requiredChain;
-
- struct DependsChainEntry
- {
- DependsChainEntry(QualifiedId name, const CodeLocation &location)
- : name(std::move(name)), location(location)
- {
- }
-
- QualifiedId name;
- CodeLocation location;
- bool isProduct = false;
- };
- class DependsChainManager;
- std::vector<DependsChainEntry> m_dependsChain;
-
- FileTime m_lastResolveTime;
- QVariantMap m_storedProfiles;
- QVariantMap m_localProfiles;
- std::multimap<QString, const ProductContext *> m_productsByName;
- std::multimap<FileTag, const ProductContext *> m_productsByType;
-
- std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems;
- Set<Item *> m_exportsWithDeferredDependsItems;
-
- SetupProjectParameters m_parameters;
- std::unique_ptr<Settings> m_settings;
- Version m_qbsVersion;
- Item *m_tempScopeItem = nullptr;
-
- qint64 m_elapsedTimeProbes = 0;
- qint64 m_elapsedTimePrepareProducts = 0;
- qint64 m_elapsedTimeProductDependencies = 0;
- qint64 m_elapsedTimeModuleProviders = 0;
- qint64 m_elapsedTimeTransitiveDependencies = 0;
- qint64 m_elapsedTimeHandleProducts = 0;
- qint64 m_elapsedTimePropertyChecking = 0;
- Set<QString> m_projectNamesUsedInOverrides;
- Set<QString> m_productNamesUsedInOverrides;
- Set<QString> m_disabledProjects;
- Set<QString> m_erroneousProducts;
-
- int m_dependencyResolvingPass = 0;
+ // TODO: Remove once entry point is changed.
+ void checkProfileErrorsForModule(Item *module, const QString &moduleName,
+ const QString &productName, const QString &profileName);
+private:
+ class Private;
+ Private * const d;
};
} // namespace Internal
} // namespace qbs
-QT_BEGIN_NAMESPACE
-Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE);
-Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE);
-QT_END_NAMESPACE
-
-#endif // QBS_MODULELOADER_H
diff --git a/src/lib/corelib/language/modulemerger.cpp b/src/lib/corelib/language/modulemerger.cpp
deleted file mode 100644
index 6ad8e2259..000000000
--- a/src/lib/corelib/language/modulemerger.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "modulemerger.h"
-
-#include "value.h"
-
-#include <logging/translator.h>
-#include <tools/qbsassert.h>
-#include <tools/qttools.h>
-#include <tools/stlutils.h>
-#include <tools/stringconstants.h>
-
-namespace qbs {
-namespace Internal {
-
-ModuleMerger::ModuleMerger(Logger &logger, Item *productItem, const QString &productName,
- const Item::Modules::iterator &modulesBegin,
- const Item::Modules::iterator &modulesEnd)
- : m_logger(logger)
- , m_productItem(productItem)
- , m_mergedModule(*modulesBegin)
- , m_isBaseModule(m_mergedModule.name.first() == StringConstants::qbsModule())
- , m_isShadowProduct(productName.startsWith(StringConstants::shadowProductPrefix()))
- , m_modulesBegin(std::next(modulesBegin))
- , m_modulesEnd(modulesEnd)
-{
- QBS_CHECK(modulesBegin->item->type() == ItemType::ModuleInstance);
-}
-
-void ModuleMerger::replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace)
-{
- QBS_CHECK(!moduleName.empty());
- QBS_CHECK(containerItem != m_mergedModule.item);
- const QString moduleNamePrefix = moduleName.takeFirst();
- const Item::PropertyMap &properties = containerItem->properties();
- for (auto it = properties.begin(); it != properties.end(); ++it) {
- if (it.key() != moduleNamePrefix)
- continue;
- Value * const val = it.value().get();
- QBS_CHECK(val);
- QBS_CHECK(val->type() == Value::ItemValueType);
- const auto itemVal = static_cast<ItemValue *>(val);
- if (moduleName.empty()) {
- QBS_CHECK(itemVal->item() == toReplace);
- itemVal->setItem(m_mergedModule.item);
- } else {
- replaceItemInValues(moduleName, itemVal->item(), toReplace);
- }
- }
-}
-
-void ModuleMerger::start()
-{
- // Iterate over any module that our product depends on. These modules
- // may depend on m_mergedModule and contribute property assignments.
- Item::PropertyMap props;
- for (auto module = m_modulesBegin; module != m_modulesEnd; module++)
- mergeModule(&props, *module);
-
- // Module property assignments in the product have the highest priority
- // and are thus prepended.
- Item::Module m;
- m.item = m_productItem;
- mergeModule(&props, m);
-
- // The module's prototype is the essential unmodified module as loaded
- // from the cache.
- Item *moduleProto = m_mergedModule.item->prototype();
- while (moduleProto->prototype())
- moduleProto = moduleProto->prototype();
-
- // The prototype item might contain default values which get appended in
- // case of list properties. Scalar properties will only be set if not
- // already specified above.
- Item::PropertyMap mergedProps = m_mergedModule.item->properties();
- for (auto it = props.constBegin(); it != props.constEnd(); ++it) {
- appendPrototypeValueToNextChain(moduleProto, it.key(), it.value());
- mergedProps[it.key()] = it.value();
- }
-
- m_mergedModule.item->setProperties(mergedProps);
-
- // Update all sibling instances of the to-be-merged module to behave identical
- // to the merged module.
- for (Item *moduleInstanceContainer : qAsConst(m_moduleInstanceContainers)) {
- Item::Modules modules;
- for (const Item::Module &dep : moduleInstanceContainer->modules()) {
- const bool isTheModule = dep.name == m_mergedModule.name;
- Item::Module m = dep;
- if (isTheModule && m.item != m_mergedModule.item) {
- QBS_CHECK(m.item->type() == ItemType::ModuleInstance);
- replaceItemInValues(m.name, moduleInstanceContainer, m.item);
- m.item = m_mergedModule.item;
- m.required = m_mergedModule.required;
- m.versionRange = m_mergedModule.versionRange;
- m.fallbackEnabled = m_mergedModule.fallbackEnabled;
- }
- modules << m;
- }
- moduleInstanceContainer->setModules(modules);
- }
-}
-
-void ModuleMerger::mergeModule(Item::PropertyMap *dstProps, const Item::Module &module)
-{
- const Item::Module *dep = findModule(module.item, m_mergedModule.name);
- if (!dep)
- return;
-
- const bool mergingProductItem = (module.item == m_productItem);
- Item *srcItem = dep->item;
- Item *origSrcItem = srcItem;
- do {
- if (m_seenInstances.insert(srcItem).second) {
- for (auto it = srcItem->properties().constBegin();
- it != srcItem->properties().constEnd(); ++it) {
- const ValuePtr &srcVal = it.value();
- if (srcVal->type() == Value::ItemValueType)
- continue;
- if (it.key() == StringConstants::qbsSourceDirPropertyInternal())
- continue;
- const PropertyDeclaration srcDecl = srcItem->propertyDeclaration(it.key());
- if (!srcDecl.isValid())
- continue;
-
- // Scalar variant values could stem from product multiplexing, in which case
- // the merged qbs module instance needs to get that value.
- if (srcVal->type() == Value::VariantValueType
- && (!srcDecl.isScalar() || !m_isBaseModule)) {
- continue;
- }
-
- ValuePtr clonedSrcVal = srcVal->clone();
- clonedSrcVal->setDefiningItem(origSrcItem);
-
- ValuePtr &dstVal = (*dstProps)[it.key()];
- if (dstVal) {
- if (srcDecl.isScalar()) {
- // Scalar properties get replaced.
- if ((dstVal->type() == Value::JSSourceValueType)
- && (srcVal->type() == Value::JSSourceValueType)) {
- // Warn only about conflicting source code values
- const JSSourceValuePtr dstJsVal =
- std::static_pointer_cast<JSSourceValue>(dstVal);
- const JSSourceValuePtr srcJsVal =
- std::static_pointer_cast<JSSourceValue>(srcVal);
- const bool overriddenInProduct =
- m_mergedModule.item->properties().contains(it.key());
-
- if (dstJsVal->sourceCode() != srcJsVal->sourceCode()
- && !mergingProductItem && !overriddenInProduct
- && !m_isShadowProduct) {
- m_logger.qbsWarning()
- << Tr::tr("Conflicting scalar values at %1 and %2.").arg(
- dstJsVal->location().toString(),
- srcJsVal->location().toString());
- }
- }
- } else {
- // List properties get prepended
- QBS_CHECK(!clonedSrcVal->next());
- clonedSrcVal->setNext(dstVal);
- }
- }
- dstVal = clonedSrcVal;
- }
- }
- srcItem = srcItem->prototype();
- } while (srcItem && srcItem->type() == ItemType::ModuleInstance);
-
- // Update dependency constraints
- if (dep->required)
- m_mergedModule.required = true;
- // if one dep has fallback disabled, disable it for the merged module
- m_mergedModule.fallbackEnabled = m_mergedModule.fallbackEnabled && dep->fallbackEnabled;
- m_mergedModule.versionRange.narrowDown(dep->versionRange);
-
- // We need to touch the unmerged module instances later once more
- m_moduleInstanceContainers << module.item;
-}
-
-void ModuleMerger::appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName,
- const ValuePtr &sv)
-{
- const PropertyDeclaration pd = m_mergedModule.item->propertyDeclaration(propertyName);
- if (pd.isScalar())
- return;
- if (!m_clonedModulePrototype) {
- m_clonedModulePrototype = Item::create(moduleProto->pool(), ItemType::Module);
- m_clonedModulePrototype->setScope(m_mergedModule.item);
- m_clonedModulePrototype->setLocation(moduleProto->location());
- moduleProto->copyProperty(StringConstants::nameProperty(), m_clonedModulePrototype);
- }
- const ValuePtr &protoValue = moduleProto->property(propertyName);
- QBS_CHECK(protoValue);
- const ValuePtr clonedValue = protoValue->clone();
- lastInNextChain(sv)->setNext(clonedValue);
- clonedValue->setDefiningItem(m_clonedModulePrototype);
- m_clonedModulePrototype->setPropertyDeclaration(propertyName, pd);
- m_clonedModulePrototype->setProperty(propertyName, clonedValue);
-}
-
-ValuePtr ModuleMerger::lastInNextChain(const ValuePtr &v)
-{
- ValuePtr n = v;
- while (n->next())
- n = n->next();
- return n;
-}
-
-const Item::Module *ModuleMerger::findModule(const Item *item, const QualifiedId &name)
-{
- for (const auto &module : item->modules()) {
- if (module.name == name)
- return &module;
- }
- return nullptr;
-}
-
-void ModuleMerger::merge(Logger &logger, Item *product, const QString &productName,
- Item::Modules *topSortedModules)
-{
- for (auto it = topSortedModules->begin(); it != topSortedModules->end(); ++it)
- ModuleMerger(logger, product, productName, it, topSortedModules->end()).start();
-}
-
-
-
-} // namespace Internal
-} // namespace qbs
diff --git a/src/lib/corelib/language/modulepropertymerger.cpp b/src/lib/corelib/language/modulepropertymerger.cpp
new file mode 100644
index 000000000..d45329d12
--- /dev/null
+++ b/src/lib/corelib/language/modulepropertymerger.cpp
@@ -0,0 +1,304 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "modulepropertymerger.h"
+
+#include "evaluator.h"
+#include "item.h"
+#include "value.h"
+
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/set.h>
+#include <tools/setupprojectparameters.h>
+
+namespace qbs::Internal {
+class ModulePropertyMerger::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger)
+ : parameters(parameters), evaluator(evaluator), logger(logger) {}
+
+ int compareValuePriorities(const Item *productItem, const ValueConstPtr &v1,
+ const ValueConstPtr &v2);
+ ValuePtr mergeListValues(const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem);
+ void mergePropertyFromLocalInstance(const Item *productItem, Item *loadingItem,
+ const QString &loadingName, Item *globalInstance,
+ const QString &name, const ValuePtr &value);
+ bool doFinalMerge(const Item *productItem, Item *moduleItem);
+ bool doFinalMerge(const Item *productItem, const PropertyDeclaration &propertyDecl,
+ ValuePtr &propertyValue);
+
+ const SetupProjectParameters &parameters;
+ Evaluator &evaluator;
+ Logger &logger;
+ qint64 elapsedTime = 0;
+};
+
+void ModulePropertyMerger::mergeFromLocalInstance(
+ const Item *productItem, Item *loadingItem, const QString &loadingName,
+ const Item *localInstance, Item *globalInstance)
+{
+ AccumulatingTimer t(d->parameters.logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ for (auto it = localInstance->properties().constBegin();
+ it != localInstance->properties().constEnd(); ++it) {
+ d->mergePropertyFromLocalInstance(productItem, loadingItem, loadingName,
+ globalInstance, it.key(), it.value());
+ }
+}
+
+void ModulePropertyMerger::doFinalMerge(const Item *productItem)
+{
+ AccumulatingTimer t(d->parameters.logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ Set<const Item *> itemsToInvalidate;
+ for (const Item::Module &module : productItem->modules()) {
+ if (d->doFinalMerge(productItem, module.item))
+ itemsToInvalidate << module.item;
+ }
+ const auto collectDependentItems = [&itemsToInvalidate](const Item *item,
+ const auto &collect) -> bool {
+ const bool alreadyInSet = itemsToInvalidate.contains(item);
+ bool addItem = false;
+ for (const Item::Module &m : item->modules()) {
+ if (collect(m.item, collect))
+ addItem = true;
+ }
+ if (addItem && !alreadyInSet)
+ itemsToInvalidate << item;
+ return addItem || alreadyInSet;
+ };
+ collectDependentItems(productItem, collectDependentItems);
+ for (const Item * const item : itemsToInvalidate)
+ d->evaluator.clearCache(item);
+}
+
+void ModulePropertyMerger::printProfilingInfo(int indent)
+{
+ if (!d->parameters.logElapsedTime())
+ return;
+ d->logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ')
+ << Tr::tr("Merging module property values took %1.")
+ .arg(elapsedTimeString(d->elapsedTime));
+}
+
+ModulePropertyMerger::ModulePropertyMerger(
+ const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger)
+ : d(new Private(parameters, evaluator, logger)) { }
+ModulePropertyMerger::~ModulePropertyMerger() { delete d; }
+
+int ModulePropertyMerger::Private::compareValuePriorities(
+ const Item *productItem, const ValueConstPtr &v1, const ValueConstPtr &v2)
+{
+ QBS_CHECK(v1);
+ QBS_CHECK(v2);
+ QBS_CHECK(v1->scope() != v2->scope());
+ QBS_CHECK(v1->type() == Value::JSSourceValueType || v2->type() == Value::JSSourceValueType);
+
+ const int prio1 = v1->priority(productItem);
+ const int prio2 = v2->priority(productItem);
+ if (prio1 != prio2)
+ return prio1 - prio2;
+ const int prioDiff = v1->scopeName().compare(v2->scopeName()); // Sic! See 8ff1dd0044
+ QBS_CHECK(prioDiff != 0);
+ return prioDiff;
+}
+
+ValuePtr ModulePropertyMerger::Private::mergeListValues(
+ const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem)
+{
+ QBS_CHECK(newElem);
+ QBS_CHECK(!newElem->next());
+
+ if (!currentHead)
+ return !newElem->expired(productItem) ? newElem : newElem->next();
+
+ QBS_CHECK(!currentHead->expired(productItem));
+
+ if (newElem->expired(productItem))
+ return currentHead;
+
+ if (compareValuePriorities(productItem, currentHead, newElem) < 0) {
+ newElem->setNext(currentHead);
+ return newElem;
+ }
+ currentHead->setNext(mergeListValues(productItem, currentHead->next(), newElem));
+ return currentHead;
+}
+
+void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
+ const Item *productItem, Item *loadingItem, const QString &loadingName, Item *globalInstance,
+ const QString &name, const ValuePtr &value)
+{
+ const PropertyDeclaration decl = globalInstance->propertyDeclaration(name);
+ if (!decl.isValid()) {
+ if (value->type() == Value::ItemValueType || value->createdByPropertiesBlock())
+ return;
+ throw ErrorInfo(Tr::tr("Property '%1' is not declared.")
+ .arg(name), value->location());
+ }
+ if (const ErrorInfo error = decl.checkForDeprecation(
+ parameters.deprecationWarningMode(), value->location(), logger);
+ error.hasError()) {
+ handlePropertyError(error, parameters, logger);
+ return;
+ }
+ if (value->setInternally()) { // E.g. qbs.architecture after multiplexing.
+ globalInstance->setProperty(decl.name(), value);
+ return;
+ }
+ QBS_CHECK(value->type() != Value::ItemValueType);
+ const ValuePtr globalVal = globalInstance->ownProperty(decl.name());
+ value->setScope(loadingItem, loadingName);
+ QBS_CHECK(globalVal);
+
+ // Values set internally cannot be overridden by JS values.
+ // The same goes for values set on the command line.
+ // Note that in both cases, there is no merging for list properties: The override is absolute.
+ if (globalVal->setInternally() || globalVal->setByCommandLine())
+ return;
+
+ QBS_CHECK(value->type() == Value::JSSourceValueType);
+
+ if (decl.isScalar()) {
+ QBS_CHECK(!globalVal->expired(productItem));
+ QBS_CHECK(!value->expired(productItem));
+ if (compareValuePriorities(productItem, globalVal, value) < 0) {
+ value->setCandidates(globalVal->candidates());
+ globalVal->setCandidates({});
+ value->addCandidate(globalVal);
+ globalInstance->setProperty(decl.name(), value);
+ } else {
+ globalVal->addCandidate(value);
+ }
+ } else {
+ if (const ValuePtr &newChainStart = mergeListValues(productItem, globalVal, value);
+ newChainStart != globalVal) {
+ globalInstance->setProperty(decl.name(), newChainStart);
+ }
+ }
+}
+
+bool ModulePropertyMerger::Private::doFinalMerge(const Item *productItem, Item *moduleItem)
+{
+ if (!moduleItem->isPresentModule())
+ return false;
+ bool mustInvalidateCache = false;
+ for (auto it = moduleItem->properties().begin(); it != moduleItem->properties().end(); ++it) {
+ if (doFinalMerge(productItem, moduleItem->propertyDeclaration(it.key()), it.value()))
+ mustInvalidateCache = true;
+ }
+ return mustInvalidateCache;
+}
+
+bool ModulePropertyMerger::Private::doFinalMerge(
+ const Item *productItem, const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue)
+{
+ if (propertyValue->type() == Value::VariantValueType) {
+ QBS_CHECK(!propertyValue->next());
+ return false;
+ }
+ if (!propertyDecl.isValid())
+ return false; // Caught later by dedicated checker.
+ propertyValue->resetPriority();
+ if (propertyDecl.isScalar()) {
+ if (propertyValue->candidates().empty())
+ return false;
+ std::pair<int, std::vector<ValuePtr>> candidatesWithHighestPrio;
+ candidatesWithHighestPrio.first = propertyValue->priority(productItem);
+ candidatesWithHighestPrio.second.push_back(propertyValue);
+ for (const ValuePtr &v : propertyValue->candidates()) {
+ const int prio = v->priority(productItem);
+ if (prio < candidatesWithHighestPrio.first)
+ continue;
+ if (prio > candidatesWithHighestPrio.first) {
+ candidatesWithHighestPrio.first = prio;
+ candidatesWithHighestPrio.second = {v};
+ continue;
+ }
+ candidatesWithHighestPrio.second.push_back(v);
+ }
+ ValuePtr chosenValue = candidatesWithHighestPrio.second.front();
+ if (int(candidatesWithHighestPrio.second.size()) > 1) {
+ ErrorInfo error(Tr::tr("Conflicting scalar values for property '%1'.")
+ .arg(propertyDecl.name()));
+ error.append({}, chosenValue->location());
+ QBS_CHECK(chosenValue->type() == Value::JSSourceValueType);
+ QStringView sourcCode = static_cast<JSSourceValue *>(
+ chosenValue.get())->sourceCode();
+ for (int i = 1; i < int(candidatesWithHighestPrio.second.size()); ++i) {
+ const ValuePtr &v = candidatesWithHighestPrio.second.at(i);
+ QBS_CHECK(v->type() == Value::JSSourceValueType);
+
+ // Note that this is a bit silly: The source code could still evaluate to
+ // different values in the end.
+ if (static_cast<JSSourceValue *>(v.get())->sourceCode() != sourcCode)
+ error.append({}, v->location());
+ }
+ if (error.items().size() > 2)
+ logger.printWarning(error);
+ }
+
+ if (propertyValue == chosenValue)
+ return false;
+ propertyValue = chosenValue;
+ return true;
+ }
+ if (!propertyValue->next())
+ return false;
+ std::vector<ValuePtr> singleValuesBefore;
+ for (ValuePtr current = propertyValue; current;) {
+ singleValuesBefore.push_back(current);
+ const ValuePtr next = current->next();
+ if (next)
+ current->setNext({});
+ current = next;
+ }
+ ValuePtr newValue;
+ for (const ValuePtr &v : singleValuesBefore)
+ newValue = mergeListValues(productItem, newValue, v);
+ std::vector<ValuePtr> singleValuesAfter;
+ for (ValuePtr current = propertyValue; current; current = current->next())
+ singleValuesAfter.push_back(current);
+ propertyValue = newValue;
+ return singleValuesBefore != singleValuesAfter;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/modulepropertymerger.h b/src/lib/corelib/language/modulepropertymerger.h
new file mode 100644
index 000000000..fc388cfbf
--- /dev/null
+++ b/src/lib/corelib/language/modulepropertymerger.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Evaluator;
+class Item;
+class Logger;
+
+// This class comprises functions for collecting values attached to module properties
+// in different contexts.
+// For example, in the Qt.core module you will find a property binding such as this:
+// cpp.defines: "QT_CORE_LIB"
+// while in the Qt.widgets module, it will look like this:
+// cpp.defines: "QT_WIDGETS_LIB"
+// A product with a dependency on both these modules will end up with a value of
+// ["QT_WIDGETS_LIB", "QT_CORE_LIB"], plus potentially other defines set elsewhere.
+// Each of these values is assigned a priority that roughly corresponds to the "level" at which
+// the module containing the property binding resides in the dependency hierarchy.
+// For list properties, the priorities determine the order of the respecive values in the
+// final array, for scalar values they determine which one survives. Different scalar values
+// with the same priority trigger a warning message.
+// Since the right-hand side of a binding can refer to properties of the surrounding context,
+// each such value gets its own scope.
+class ModulePropertyMerger
+{
+public:
+ ModulePropertyMerger(const SetupProjectParameters &parameters, Evaluator &evaluator,
+ Logger &logger);
+ ~ModulePropertyMerger();
+
+ // This function is called when a module is loaded via a Depends item.
+ // loadingItem is the product or module containing the Depends item.
+ // loadingName is the name of that module. It is used as a tie-breaker for list property values
+ // with equal priority.
+ // localInstance is the module instance placeholder in the ItemValue of a property binding,
+ // i.e. the "cpp" in "cpp.defines".
+ // globalInstance is the actual module into which the properties from localInstance get merged.
+ void mergeFromLocalInstance(const Item *productItem, Item *loadingItem,
+ const QString &loadingName, const Item *localInstance,
+ Item *globalInstance);
+
+ // This function is called after all dependencies have been resolved. It uses its global
+ // knowledge of module priorities to potentially adjust the order of list values or
+ // favor different scalar values. It can also remove previously merged-in values again;
+ // this can happen if a module fails to load after it already merged some values, or
+ // if it fails validation in the end.
+ void doFinalMerge(const Item *productItem);
+
+ void printProfilingInfo(int indent);
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/language/moduleproviderloader.cpp b/src/lib/corelib/language/moduleproviderloader.cpp
index dae8bba15..3f578f19b 100644
--- a/src/lib/corelib/language/moduleproviderloader.cpp
+++ b/src/lib/corelib/language/moduleproviderloader.cpp
@@ -42,8 +42,8 @@
#include "builtindeclarations.h"
#include "evaluator.h"
+#include "item.h"
#include "itemreader.h"
-#include "moduleloader.h"
#include "probesresolver.h"
#include <language/scriptengine.h>
@@ -269,6 +269,7 @@ QVariantMap ModuleProviderLoader::evaluateQbsModule(ProductContext &product) con
{
const QString properties[] = {
QStringLiteral("sysroot"),
+ QStringLiteral("toolchain"),
};
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
product.item->property(StringConstants::qbsModule()));
@@ -277,8 +278,15 @@ QVariantMap ModuleProviderLoader::evaluateQbsModule(ProductContext &product) con
const ScopedJsValue val(m_evaluator->engine()->context(),
m_evaluator->value(qbsItemValue->item(), property));
auto value = getJsVariant(m_evaluator->engine()->context(), val);
- if (value.isValid())
- result[property] = std::move(value);
+ if (!value.isValid())
+ continue;
+
+ // The xcode module sets qbs.sysroot; the resulting value is bogus before the probes
+ // have run.
+ if (property == QLatin1String("sysroot") && !FileInfo::isAbsolute(value.toString()))
+ continue;
+
+ result[property] = std::move(value);
}
return result;
}
@@ -342,8 +350,8 @@ QStringList ModuleProviderLoader::evaluateModuleProvider(
<< endl;
stream << "}" << endl;
stream.flush();
- Item * const providerItem =
- m_reader->readFile(dummyItemFile.fileName(), dependsItemLocation);
+ Item * const providerItem = m_reader->setupItemFromFile(
+ dummyItemFile.fileName(), dependsItemLocation, *m_evaluator);
if (providerItem->type() != ItemType::ModuleProvider) {
throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
"but '%3' was expected.")
@@ -353,7 +361,7 @@ QStringList ModuleProviderLoader::evaluateModuleProvider(
providerItem->setScope(createProviderScope(product, qbsModule));
- providerItem->overrideProperties(moduleConfig, name.toString(), m_parameters, m_logger);
+ providerItem->overrideProperties(moduleConfig, name, m_parameters, m_logger);
m_probesResolver->resolveProbes(&product, providerItem);
diff --git a/src/lib/corelib/language/moduleproviderloader.h b/src/lib/corelib/language/moduleproviderloader.h
index 91a32ec80..cd6ba42e0 100644
--- a/src/lib/corelib/language/moduleproviderloader.h
+++ b/src/lib/corelib/language/moduleproviderloader.h
@@ -41,7 +41,7 @@
#ifndef MODULEPROVIDERLOADER_H
#define MODULEPROVIDERLOADER_H
-#include "moduleloader.h"
+#include "projecttreebuilder.h"
#include "moduleproviderinfo.h"
#include "probesresolver.h"
@@ -50,14 +50,12 @@
namespace qbs {
namespace Internal {
-
+class ItemReader;
class Logger;
class ModuleProviderLoader
{
public:
- using ProductContext = ModuleLoader::ProductContext;
- using FallbackMode = ModuleLoader::FallbackMode;
explicit ModuleProviderLoader(ItemReader *itemReader, Evaluator *evaluator,
ProbesResolver *probesResolver, Logger &logger);
diff --git a/src/lib/corelib/language/probesresolver.cpp b/src/lib/corelib/language/probesresolver.cpp
index d2f3fefa2..05308c933 100644
--- a/src/lib/corelib/language/probesresolver.cpp
+++ b/src/lib/corelib/language/probesresolver.cpp
@@ -46,7 +46,6 @@
#include "item.h"
#include "itemreader.h"
#include "language.h"
-#include "modulemerger.h"
#include "qualifiedid.h"
#include "scriptengine.h"
#include "value.h"
@@ -109,7 +108,7 @@ void ProbesResolver::setOldProductProbes(
m_oldProductProbes = oldProbes;
}
-void ProbesResolver::resolveProbes(ModuleLoader::ProductContext *productContext, Item *item)
+void ProbesResolver::resolveProbes(ProductContext *productContext, Item *item)
{
AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr);
EvalContextSwitcher evalContextSwitcher(m_evaluator->engine(), EvalContext::ProbeExecution);
@@ -118,7 +117,7 @@ void ProbesResolver::resolveProbes(ModuleLoader::ProductContext *productContext,
resolveProbe(productContext, item, child);
}
-void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext, Item *parent,
+void ProbesResolver::resolveProbe(ProductContext *productContext, Item *parent,
Item *probe)
{
qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString();
@@ -291,16 +290,18 @@ bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition,
&& !probe->needsReconfigure(m_lastResolveTime)));
}
-void ProbesResolver::printProfilingInfo()
+void ProbesResolver::printProfilingInfo(int indent)
{
if (!m_parameters.logElapsedTime())
return;
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Running Probes took %1.")
- .arg(elapsedTimeString(m_elapsedTimeProbes));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("%1 probes encountered, %2 configure scripts executed, "
- "%3 re-used from current run, %4 re-used from earlier run.")
+ const QByteArray prefix(indent, ' ');
+ m_logger.qbsLog(LoggerInfo, true)
+ << prefix
+ << Tr::tr("Running Probes took %1.").arg(elapsedTimeString(m_elapsedTimeProbes));
+ m_logger.qbsLog(LoggerInfo, true)
+ << prefix
+ << Tr::tr("%1 probes encountered, %2 configure scripts executed, "
+ "%3 re-used from current run, %4 re-used from earlier run.")
.arg(m_probesEncountered).arg(m_probesRun).arg(m_probesCachedCurrent)
.arg(m_probesCachedOld);
}
diff --git a/src/lib/corelib/language/probesresolver.h b/src/lib/corelib/language/probesresolver.h
index 1aeec27ce..5235ff846 100644
--- a/src/lib/corelib/language/probesresolver.h
+++ b/src/lib/corelib/language/probesresolver.h
@@ -41,7 +41,9 @@
#ifndef PROBESRESOLVER_H
#define PROBESRESOLVER_H
-#include "moduleloader.h"
+#include "projecttreebuilder.h"
+
+#include <tools/setupprojectparameters.h>
namespace qbs {
namespace Internal {
@@ -53,9 +55,9 @@ public:
void setProjectParameters(SetupProjectParameters parameters);
void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void resolveProbes(ModuleLoader::ProductContext *productContext, Item *item);
- void resolveProbe(ModuleLoader::ProductContext *productContext, Item *parent, Item *probe);
- void printProfilingInfo();
+ void resolveProbes(ProductContext *productContext, Item *item);
+ void resolveProbe(ProductContext *productContext, Item *parent, Item *probe);
+ void printProfilingInfo(int indent);
private:
ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition,
diff --git a/src/lib/corelib/language/productitemmultiplexer.cpp b/src/lib/corelib/language/productitemmultiplexer.cpp
new file mode 100644
index 000000000..c737be7f1
--- /dev/null
+++ b/src/lib/corelib/language/productitemmultiplexer.cpp
@@ -0,0 +1,288 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "productitemmultiplexer.h"
+
+#include "evaluator.h"
+#include "item.h"
+#include "scriptengine.h"
+#include "value.h"
+
+#include <logging/translator.h>
+#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QJsonDocument>
+#include <QThreadStorage>
+
+#include <vector>
+
+
+namespace qbs::Internal {
+namespace {
+using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap>>;
+using MultiplexRow = std::vector<VariantValuePtr>;
+using MultiplexTable = std::vector<MultiplexRow>;
+class MultiplexInfo
+{
+public:
+ std::vector<QString> properties;
+ MultiplexTable table;
+ bool aggregate = false;
+ VariantValuePtr multiplexedType;
+
+ QString toIdString(size_t row) const;
+};
+} // namespace
+
+Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
+
+class ProductItemMultiplexer::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger,
+ QbsItemRetriever qbsItemRetriever)
+ : parameters(parameters), evaluator(evaluator), logger(logger),
+ qbsItemRetriever(std::move(qbsItemRetriever)) {}
+
+ MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
+ MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values);
+
+ const SetupProjectParameters &parameters;
+ Evaluator &evaluator;
+ Logger &logger;
+ const QbsItemRetriever qbsItemRetriever;
+};
+
+ProductItemMultiplexer::ProductItemMultiplexer(
+ const SetupProjectParameters &parameters, Evaluator &evaluator, Logger &logger,
+ const QbsItemRetriever &qbsItemRetriever)
+ : d(new Private(parameters, evaluator, logger, qbsItemRetriever)) {}
+
+ProductItemMultiplexer::~ProductItemMultiplexer() { delete d; }
+
+QList<Item *> ProductItemMultiplexer::multiplex(
+ const QString &productName,
+ Item *productItem,
+ Item *tempQbsModuleItem,
+ const std::function<void ()> &dropTempQbsModule)
+{
+ const auto multiplexInfo = d->extractMultiplexInfo(productItem, tempQbsModuleItem);
+ dropTempQbsModule();
+ if (multiplexInfo.table.size() > 1)
+ productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
+ VariantValuePtr productNameValue = VariantValue::create(productName);
+ Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
+ QList<Item *> additionalProductItems;
+ std::vector<VariantValuePtr> multiplexConfigurationIdValues;
+ for (size_t row = 0; row < multiplexInfo.table.size(); ++row) {
+ Item *item = productItem;
+ const auto &mprow = multiplexInfo.table.at(row);
+ QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
+ if (row > 0) {
+ item = productItem->clone();
+ additionalProductItems.push_back(item);
+ }
+ const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
+ const VariantValuePtr multiplexConfigurationIdValue
+ = VariantValue::create(multiplexConfigurationId);
+ if (multiplexInfo.table.size() > 1 || aggregator) {
+ multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue);
+ item->setProperty(StringConstants::multiplexConfigurationIdProperty(),
+ multiplexConfigurationIdValue);
+ }
+ if (multiplexInfo.multiplexedType)
+ item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
+ for (size_t column = 0; column < mprow.size(); ++column) {
+ Item * const qbsItem = d->qbsItemRetriever(item);
+ const QString &propertyName = multiplexInfo.properties.at(column);
+ const VariantValuePtr &mpvalue = mprow.at(column);
+ qbsItem->setProperty(propertyName, mpvalue);
+ }
+ }
+
+ if (aggregator) {
+ additionalProductItems << aggregator;
+
+ // Add dependencies to all multiplexed instances.
+ for (const auto &v : multiplexConfigurationIdValues) {
+ Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
+ dependsItem->setProperty(StringConstants::nameProperty(), productNameValue);
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), v);
+ dependsItem->setProperty(StringConstants::profilesProperty(),
+ VariantValue::create(QStringList()));
+ dependsItem->setFile(aggregator->file());
+ dependsItem->setupForBuiltinType(d->parameters.deprecationWarningMode(), d->logger);
+ Item::addChild(aggregator, dependsItem);
+ }
+ }
+
+ return additionalProductItems;
+}
+
+MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *productItem,
+ Item *qbsModuleItem)
+{
+ static const QString mpmKey = QStringLiteral("multiplexMap");
+
+ JSContext * const ctx = evaluator.engine()->context();
+ const ScopedJsValue multiplexMap(ctx, evaluator.value(qbsModuleItem, mpmKey));
+ const QStringList multiplexByQbsProperties = evaluator.stringListValue(
+ productItem, StringConstants::multiplexByQbsPropertiesProperty());
+
+ MultiplexInfo multiplexInfo;
+ multiplexInfo.aggregate = evaluator.boolValue(
+ productItem, StringConstants::aggregateProperty());
+
+ const QString multiplexedType = evaluator.stringValue(
+ productItem, StringConstants::multiplexedTypeProperty());
+ if (!multiplexedType.isEmpty())
+ multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
+
+ Set<QString> uniqueMultiplexByQbsProperties;
+ for (const QString &key : multiplexByQbsProperties) {
+ const QString mappedKey = getJsStringProperty(ctx, multiplexMap, key);
+ if (mappedKey.isEmpty())
+ throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key));
+
+ if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) {
+ throw ErrorInfo(Tr::tr("Duplicate entry '%1' in Product.%2.")
+ .arg(mappedKey, StringConstants::multiplexByQbsPropertiesProperty()),
+ productItem->location());
+ }
+
+ const ScopedJsValue arr(ctx, evaluator.value(qbsModuleItem, key));
+ if (JS_IsUndefined(arr))
+ continue;
+ if (!JS_IsArray(ctx, arr))
+ throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key));
+
+ const quint32 arrlen = getJsIntProperty(ctx, arr, StringConstants::lengthProperty());
+ if (arrlen == 0)
+ continue;
+
+ MultiplexRow mprow;
+ mprow.resize(arrlen);
+ QVariantList entriesForKey;
+ for (quint32 i = 0; i < arrlen; ++i) {
+ const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, arr, i));
+ const QVariant value = getJsVariant(ctx, sv);
+ if (entriesForKey.contains(value)) {
+ throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.")
+ .arg(value.toString(), key), productItem->location());
+ }
+ entriesForKey << value;
+ mprow[i] = VariantValue::create(value);
+ }
+ multiplexInfo.table = combine(multiplexInfo.table, mprow);
+ multiplexInfo.properties.push_back(mappedKey);
+ }
+ return multiplexInfo;
+
+}
+
+MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &table,
+ const MultiplexRow &values)
+{
+ MultiplexTable result;
+ if (table.empty()) {
+ result.resize(values.size());
+ for (size_t i = 0; i < values.size(); ++i) {
+ MultiplexRow row;
+ row.resize(1);
+ row[0] = values.at(i);
+ result[i] = row;
+ }
+ } else {
+ for (const auto &row : table) {
+ for (const auto &value : values) {
+ MultiplexRow newRow = row;
+ newRow.push_back(value);
+ result.push_back(newRow);
+ }
+ }
+ }
+ return result;
+}
+
+QVariantMap ProductItemMultiplexer::multiplexIdToVariantMap(const QString &multiplexId)
+{
+ if (multiplexId.isEmpty())
+ return QVariantMap();
+
+ // We assume that MultiplexInfo::toIdString() has been called for this
+ // particular multiplex configuration.
+ QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId);
+ QBS_CHECK(!result.isEmpty());
+ return result;
+}
+
+QString ProductItemMultiplexer::fullProductDisplayName(const QString &name,
+ const QString &multiplexId)
+{
+ static const auto multiplexIdToString =[](const QString &id) {
+ return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
+ };
+ QString result = name;
+ if (!multiplexId.isEmpty())
+ result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexId));
+ return result;
+}
+
+QString MultiplexInfo::toIdString(size_t row) const
+{
+ const auto &mprow = table.at(row);
+ QVariantMap multiplexConfiguration;
+ for (size_t column = 0; column < mprow.size(); ++column) {
+ const QString &propertyName = properties.at(column);
+ const VariantValuePtr &mpvalue = mprow.at(column);
+ multiplexConfiguration.insert(propertyName, mpvalue->value());
+ }
+ QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration)
+ .toJson(QJsonDocument::Compact)
+ .toBase64());
+
+ // Cache for later use in multiplexIdToVariantMap()
+ multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
+
+ return id;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/productitemmultiplexer.h b/src/lib/corelib/language/productitemmultiplexer.h
new file mode 100644
index 000000000..d99267336
--- /dev/null
+++ b/src/lib/corelib/language/productitemmultiplexer.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QList>
+#include <QVariantMap>
+
+#include <functional>
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Evaluator;
+class Item;
+class Logger;
+
+// This class deals with product multiplexing over the various defined axes.
+// For instance, a product with qbs.architectures: ["x86", "arm"] will get multiplexed into
+// two products with qbs.architecture: "x86" and qbs.architecture: "arm", respectively.
+class ProductItemMultiplexer
+{
+public:
+ using QbsItemRetriever = std::function<Item *(Item *)>;
+ ProductItemMultiplexer(const SetupProjectParameters &parameters, Evaluator &evaluator,
+ Logger &logger, const QbsItemRetriever &qbsItemRetriever);
+ ~ProductItemMultiplexer();
+
+ // Checks whether the product item is to be multiplexed and returns the list of additional
+ // product items. In the normal, non-multiplex case, this list is empty.
+ QList<Item *> multiplex(
+ const QString &productName,
+ Item *productItem,
+ Item *tempQbsModuleItem,
+ const std::function<void()> &dropTempQbsModule
+ );
+
+ QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
+
+ static QString fullProductDisplayName(const QString &name, const QString &multiplexId);
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp
index c2c8ba134..403fd35bc 100644
--- a/src/lib/corelib/language/projectresolver.cpp
+++ b/src/lib/corelib/language/projectresolver.cpp
@@ -117,7 +117,7 @@ struct ProjectResolver::ModuleContext
class CancelException { };
-ProjectResolver::ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult,
+ProjectResolver::ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult,
SetupProjectParameters setupParameters, Logger &logger)
: m_evaluator(evaluator)
, m_logger(logger)
@@ -261,7 +261,7 @@ TopLevelProjectPtr ProjectResolver::resolveTopLevelProject()
project->environment = m_engine->environment();
project->buildSystemFiles.unite(m_engine->imports());
makeSubProjectNamesUniqe(project);
- resolveProductDependencies(projectContext);
+ resolveProductDependencies();
collectExportedProductDependencies();
checkForDuplicateProductNames(project);
@@ -411,9 +411,32 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
productContext.product = product;
product->location = item->location();
ProductContextSwitcher contextSwitcher(this, &productContext, m_progressObserver);
+ const auto errorFromDelayedError = [&] {
+ ProjectTreeBuilder::Result::ProductInfo &pi = m_loadResult.productInfos[item];
+ if (pi.delayedError.hasError()) {
+ ErrorInfo errorInfo;
+
+ // First item is "main error", gets prepended again in the catch clause.
+ const QList<ErrorItem> &items = pi.delayedError.items();
+ for (int i = 1; i < items.size(); ++i)
+ errorInfo.append(items.at(i));
+
+ pi.delayedError.clear();
+ return errorInfo;
+ }
+ return ErrorInfo();
+ };
+
+ // Even if we previously encountered an error, try to continue for as long as possible
+ // to provide IDEs with useful data (e.g. the list of files).
+ // If we encounter a follow-up error, suppress it and report the original one instead.
try {
resolveProductFully(item, projectContext);
- } catch (const ErrorInfo &e) {
+ if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
+ throw error;
+ } catch (ErrorInfo e) {
+ if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
+ e = error;
QString mainErrorString = !product->name.isEmpty()
? Tr::tr("Error while handling product '%1':").arg(product->name)
: Tr::tr("Error while handling product:");
@@ -445,21 +468,10 @@ void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectCon
product->multiplexConfigurationId
= m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty());
qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
- m_productsByName.insert(product->uniqueName(), product);
+ m_productsByItem.insert(item, product);
product->enabled = product->enabled
&& m_evaluator->boolValue(item, StringConstants::conditionProperty());
- ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item];
- if (pi.delayedError.hasError()) {
- ErrorInfo errorInfo;
-
- // First item is "main error", gets prepended again in the catch clause.
- const QList<ErrorItem> &items = pi.delayedError.items();
- for (int i = 1; i < items.size(); ++i)
- errorInfo.append(items.at(i));
-
- pi.delayedError.clear();
- throw errorInfo;
- }
+ ProjectTreeBuilder::Result::ProductInfo &pi = m_loadResult.productInfos[item];
gatherProductTypes(product.get(), item);
product->targetName = m_evaluator->stringValue(item, StringConstants::targetNameProperty());
product->sourceDirectory = m_evaluator->stringValue(
@@ -526,8 +538,10 @@ void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectCon
void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext)
{
JobLimits jobLimits;
- for (const Item::Module &m : item->modules())
- resolveModule(m.name, m.item, m.isProduct, m.parameters, jobLimits, projectContext);
+ for (const Item::Module &m : item->modules()) {
+ resolveModule(m.name, m.item, m.productInfo.has_value(), m.parameters, jobLimits,
+ projectContext);
+ }
for (int i = 0; i < jobLimits.count(); ++i) {
const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i);
if (m_productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
@@ -592,15 +606,9 @@ void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, b
void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item)
{
- product->fileTags = m_evaluator->fileTagsValue(item, StringConstants::typeProperty());
- for (const Item::Module &m : item->modules()) {
- if (m.item->isPresentModule()) {
- product->fileTags += m_evaluator->fileTagsValue(m.item,
- StringConstants::additionalProductTypesProperty());
- }
- }
- item->setProperty(StringConstants::typeProperty(),
- VariantValue::create(sorted(product->fileTags.toStringList())));
+ const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty());
+ if (type)
+ product->fileTags = FileTags::fromStringList(type->value().toStringList());
}
SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct,
@@ -678,8 +686,7 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
= QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString();
propsPerModule[moduleName] << fullPropName.last();
}
- EvalCacheEnabler cachingEnabler(m_evaluator);
- m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
+ EvalCacheEnabler cachingEnabler(m_evaluator, m_productContext->product->sourceDirectory);
for (const Item::Module &module : group->modules()) {
const QString &fullModName = module.name.toString();
const QStringList propsForModule = propsPerModule.take(fullModName);
@@ -691,7 +698,6 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
modulesMap.insert(fullModName,
evaluateProperties(module.item, module.item, reusableValues, true, true));
}
- m_evaluator->clearPathPropertiesBaseDir();
return modulesMap;
}
@@ -938,37 +944,27 @@ void ProjectResolver::collectExportedProductDependencies()
if (!exportingProduct->enabled)
continue;
Item * const importingProductItem = exportingProductInfo.second;
- std::vector<QString> directDepNames;
+
+ std::vector<std::pair<ResolvedProductPtr, QVariantMap>> directDeps;
for (const Item::Module &m : importingProductItem->modules()) {
- if (m.name.toString() == exportingProduct->name) {
- for (const Item::Module &dep : m.item->modules()) {
- if (dep.isProduct)
- directDepNames.push_back(dep.name.toString());
+ if (m.name.toString() != exportingProduct->name)
+ continue;
+ for (const Item::Module &dep : m.item->modules()) {
+ if (dep.productInfo) {
+ directDeps.emplace_back(m_productsByItem.value(dep.productInfo->item),
+ m.parameters);
}
- break;
}
}
- const ModuleLoaderResult::ProductInfo &importingProductInfo
- = mapValue(m_loadResult.productInfos, importingProductItem);
- const ProductDependencyInfos &depInfos
- = getProductDependencies(dummyProduct, importingProductInfo);
- for (const auto &dep : depInfos.dependencies) {
- if (dep.product == exportingProduct)
- continue;
-
- // Filter out indirect dependencies.
- // TODO: Depends items using "profile" or "productTypes" will not work.
- if (!contains(directDepNames, dep.product->name))
- continue;
-
+ for (const auto &dep : directDeps) {
if (!contains(exportingProduct->exportedModule.productDependencies,
- dep.product->uniqueName())) {
+ dep.first->uniqueName())) {
exportingProduct->exportedModule.productDependencies.push_back(
- dep.product->uniqueName());
+ dep.first->uniqueName());
}
- if (!dep.parameters.isEmpty()) {
- exportingProduct->exportedModule.dependencyParameters.insert(dep.product,
- dep.parameters);
+ if (!dep.second.isEmpty()) {
+ exportingProduct->exportedModule.dependencyParameters.insert(dep.first,
+ dep.second);
}
}
auto &productDeps = exportingProduct->exportedModule.productDependencies;
@@ -1406,64 +1402,6 @@ void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext
m_productContext->product->scanners.push_back(scanner);
}
-ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies(
- const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo)
-{
- ProductDependencyInfos result;
- result.dependencies.reserve(productInfo.usedProducts.size());
- for (const auto &dependency : productInfo.usedProducts) {
- QBS_CHECK(!dependency.name.isEmpty());
- if (dependency.profile == StringConstants::star()) {
- for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
- if (p->name != dependency.name || p == product || !p->enabled
- || (dependency.limitToSubProject && !product->isInParentProject(p))) {
- continue;
- }
- result.dependencies.emplace_back(p, dependency.parameters);
- }
- } else {
- ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName());
- const QString depDisplayName = ResolvedProduct::fullDisplayName(dependency.name,
- dependency.multiplexConfigurationId);
- if (!usedProduct) {
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(product->fullDisplayName(), depDisplayName),
- product->location);
- }
- if (!dependency.profile.isEmpty() && usedProduct->profile() != dependency.profile) {
- usedProduct.reset();
- for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
- if (p->name == dependency.name && p->profile() == dependency.profile) {
- usedProduct = p;
- break;
- }
- }
- if (!usedProduct) {
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
- "for the requested profile '%3'.")
- .arg(product->fullDisplayName(), depDisplayName,
- dependency.profile),
- product->location);
- }
- }
- if (!usedProduct->enabled) {
- if (!dependency.isRequired)
- continue;
- ErrorInfo e;
- e.append(Tr::tr("Product '%1' depends on '%2',")
- .arg(product->name, usedProduct->name), product->location);
- e.append(Tr::tr("but product '%1' is disabled.").arg(usedProduct->name),
- usedProduct->location);
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw e;
- result.hasDisabledDependency = true;
- }
- result.dependencies.emplace_back(usedProduct, dependency.parameters);
- }
- }
- return result;
-}
-
void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product,
const std::vector<SourceArtifactPtr> &artifacts)
{
@@ -1494,14 +1432,26 @@ void ProjectResolver::printProfilingInfo()
class TempScopeSetter
{
public:
- TempScopeSetter(Item * item, Item *newScope) : m_item(item), m_oldScope(item->scope())
+ TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope())
{
- item->setScope(newScope);
+ value->setScope(newScope, {});
}
- ~TempScopeSetter() { m_item->setScope(m_oldScope); }
+ ~TempScopeSetter() { if (m_value) m_value->setScope(m_oldScope, {}); }
+
+ TempScopeSetter(const TempScopeSetter &) = delete;
+ TempScopeSetter &operator=(const TempScopeSetter &) = delete;
+ TempScopeSetter &operator=(TempScopeSetter &&) = delete;
+
+ TempScopeSetter(TempScopeSetter &&other) noexcept
+ : m_value(std::move(other.m_value)), m_oldScope(other.m_oldScope)
+ {
+ other.m_value.reset();
+ other.m_oldScope = nullptr;
+ }
+
private:
- Item * const m_item;
- Item * const m_oldScope;
+ ValuePtr m_value;
+ Item *m_oldScope;
};
void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance)
@@ -1509,8 +1459,8 @@ void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance
if (!productModuleInstance->isPresentModule())
return;
Item * const exportItem = productModuleInstance->prototype();
- QBS_CHECK(exportItem && exportItem->type() == ItemType::Export);
- TempScopeSetter tempScopeSetter(exportItem, productModuleInstance->scope());
+ QBS_CHECK(exportItem);
+ QBS_CHECK(exportItem->type() == ItemType::Export);
const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance()
.declarationsForType(ItemType::Export).properties();
ExportedModule &exportedModule = m_productContext->product->exportedModule;
@@ -1526,6 +1476,7 @@ void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance
collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance,
exportedModule.modulePropertyValues);
} else {
+ TempScopeSetter tss(it.value(), productModuleInstance);
evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues,
false);
}
@@ -1538,7 +1489,7 @@ void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module
if (!module.item->isPresentModule())
return;
ExportedModule &exportedModule = m_productContext->product->exportedModule;
- if (module.isProduct || module.name.first() == StringConstants::qbsModule())
+ if (module.productInfo || module.name.first() == StringConstants::qbsModule())
return;
const auto checkName = [module](const ExportedModuleDependency &d) {
return module.name.toString() == d.name;
@@ -1551,7 +1502,6 @@ void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module
modulePrototype = modulePrototype->prototype();
if (!modulePrototype) // Can happen for broken products in relaxed mode.
return;
- TempScopeSetter tempScopeSetter(modulePrototype, module.item->scope());
const Item::PropertyMap &props = modulePrototype->properties();
ExportedModuleDependency dep;
dep.name = module.name.toString();
@@ -1565,107 +1515,25 @@ void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module
collectPropertiesForModuleInExportItem(dep);
}
-static bool hasDependencyCycle(Set<ResolvedProduct *> *checked,
- Set<ResolvedProduct *> *branch,
- const ResolvedProductPtr &product,
- ErrorInfo *error)
-{
- if (branch->contains(product.get()))
- return true;
- if (checked->contains(product.get()))
- return false;
- checked->insert(product.get());
- branch->insert(product.get());
- for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) {
- if (hasDependencyCycle(checked, branch, dep, error)) {
- error->prepend(dep->name, dep->location);
- return true;
- }
- }
- branch->remove(product.get());
- return false;
-}
-
-using DependencyMap = QHash<ResolvedProduct *, Set<ResolvedProduct *>>;
-void gatherDependencies(ResolvedProduct *product, DependencyMap &dependencies)
-{
- if (dependencies.contains(product))
- return;
- // Hold locally because the QHash references aren't stable in Qt6.
- Set<ResolvedProduct *> productDeps = dependencies[product];
- for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) {
- productDeps << dep.get();
- gatherDependencies(dep.get(), dependencies);
- productDeps += dependencies.value(dep.get());
- }
- // Now that we gathered the dependencies, put them in the map.
- dependencies[product] = std::move(productDeps);
-}
-
-
-
-static DependencyMap allDependencies(const std::vector<ResolvedProductPtr> &products)
-{
- DependencyMap dependencies;
- for (const ResolvedProductPtr &product : products)
- gatherDependencies(product.get(), dependencies);
- return dependencies;
-}
-
-void ProjectResolver::resolveProductDependencies(const ProjectContext &projectContext)
+void ProjectResolver::resolveProductDependencies()
{
- // Resolve all inter-product dependencies.
- const std::vector<ResolvedProductPtr> allProducts = projectContext.project->allProducts();
- bool disabledDependency = false;
- for (const ResolvedProductPtr &rproduct : allProducts) {
- if (!rproduct->enabled)
- continue;
- Item *productItem = m_productItemMap.value(rproduct);
- const ModuleLoaderResult::ProductInfo &productInfo
- = mapValue(m_loadResult.productInfos, productItem);
- const ProductDependencyInfos &depInfos = getProductDependencies(rproduct, productInfo);
- if (depInfos.hasDisabledDependency)
- disabledDependency = true;
- for (const auto &dep : depInfos.dependencies) {
- if (!contains(rproduct->dependencies, dep.product))
- rproduct->dependencies.push_back(dep.product);
- if (!dep.parameters.empty())
- rproduct->dependencyParameters.insert(dep.product, dep.parameters);
- }
- }
-
- // Check for cyclic dependencies.
- Set<ResolvedProduct *> checked;
- for (const ResolvedProductPtr &rproduct : allProducts) {
- Set<ResolvedProduct *> branch;
- ErrorInfo error;
- if (hasDependencyCycle(&checked, &branch, rproduct, &error)) {
- error.prepend(rproduct->name, rproduct->location);
- error.prepend(Tr::tr("Cyclic dependencies detected."));
- throw error;
- }
- }
-
- // Mark all products as disabled that have a disabled dependency.
- if (disabledDependency && m_setupParams.productErrorMode() == ErrorHandlingMode::Relaxed) {
- const DependencyMap allDeps = allDependencies(allProducts);
- DependencyMap allDepsReversed;
- for (auto it = allDeps.constBegin(); it != allDeps.constEnd(); ++it) {
- for (ResolvedProduct *dep : qAsConst(it.value()))
- allDepsReversed[dep] << it.key();
- }
- for (auto it = allDepsReversed.constBegin(); it != allDepsReversed.constEnd(); ++it) {
- if (it.key()->enabled)
+ for (auto it = m_productsByItem.cbegin(); it != m_productsByItem.cend(); ++it) {
+ const ResolvedProductPtr &product = it.value();
+ for (const Item::Module &module : it.key()->modules()) {
+ if (!module.productInfo)
continue;
- for (ResolvedProduct * const dependingProduct : qAsConst(it.value())) {
- if (dependingProduct->enabled) {
- m_logger.qbsWarning() << Tr::tr("Disabling product '%1', because it depends on "
- "disabled product '%2'.")
- .arg(dependingProduct->name, it.key()->name);
- dependingProduct->enabled = false;
- }
- }
+ const ResolvedProductPtr &dep = m_productsByItem.value(module.productInfo->item);
+ QBS_CHECK(dep);
+ QBS_CHECK(dep != product);
+ it.value()->dependencies << dep;
+ it.value()->dependencyParameters.insert(dep, module.parameters); // TODO: Streamline this with normal module dependencies?
}
+
+ // TODO: We might want to keep the topological sorting and get rid of "module module dependencies".
+ std::sort(product->dependencies.begin(),product->dependencies.end(),
+ [](const ResolvedProductPtr &p1, const ResolvedProductPtr &p2) {
+ return p1->fullDisplayName() < p2->fullDisplayName();
+ });
}
}
@@ -1854,13 +1722,20 @@ void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleNa
{
QBS_CHECK(value->type() == Value::ItemValueType);
Item * const itemValueItem = std::static_pointer_cast<ItemValue>(value)->item();
- if (itemValueItem->type() == ItemType::ModuleInstance) {
+ if (itemValueItem->propertyDeclarations().isEmpty()) {
+ for (const Item::Module &module : moduleInstance->modules()) {
+ if (module.name == moduleName) {
+ itemValueItem->setPropertyDeclarations(module.item->propertyDeclarations());
+ break;
+ }
+ }
+ }
+ if (itemValueItem->type() == ItemType::ModuleInstancePlaceholder) {
struct EvalPreparer {
- EvalPreparer(Item *valueItem, Item *moduleInstance, const QualifiedId &moduleName)
- : valueItem(valueItem), oldScope(valueItem->scope()),
+ EvalPreparer(Item *valueItem, const QualifiedId &moduleName)
+ : valueItem(valueItem),
hadName(!!valueItem->variantProperty(StringConstants::nameProperty()))
{
- valueItem->setScope(moduleInstance);
if (!hadName) {
// Evaluator expects a name here.
valueItem->setProperty(StringConstants::nameProperty(),
@@ -1869,15 +1744,16 @@ void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleNa
}
~EvalPreparer()
{
- valueItem->setScope(oldScope);
if (!hadName)
valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr());
}
Item * const valueItem;
- Item * const oldScope;
const bool hadName;
};
- EvalPreparer ep(itemValueItem, moduleInstance, moduleName);
+ EvalPreparer ep(itemValueItem, moduleName);
+ std::vector<TempScopeSetter> tss;
+ for (const ValuePtr &v : itemValueItem->properties())
+ tss.emplace_back(v, moduleInstance);
moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false));
return;
}
@@ -1892,12 +1768,10 @@ void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleNa
void ProjectResolver::createProductConfig(ResolvedProduct *product)
{
- EvalCacheEnabler cachingEnabler(m_evaluator);
- m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
+ EvalCacheEnabler cachingEnabler(m_evaluator, m_productContext->product->sourceDirectory);
product->moduleProperties->setValue(evaluateModuleValues(m_productContext->item));
product->productProperties = evaluateProperties(m_productContext->item, m_productContext->item,
QVariantMap(), true, true);
- m_evaluator->clearPathPropertiesBaseDir();
}
void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item,
diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h
index 52d835535..a8b06b6e6 100644
--- a/src/lib/corelib/language/projectresolver.h
+++ b/src/lib/corelib/language/projectresolver.h
@@ -41,12 +41,14 @@
#define PROJECTRESOLVER_H
#include "filetags.h"
+#include "item.h"
#include "itemtype.h"
-#include "moduleloader.h"
+#include "projecttreebuilder.h"
#include "qualifiedid.h"
#include <logging/logger.h>
#include <tools/set.h>
+#include <tools/setupprojectparameters.h>
#include <QtCore/qhash.h>
#include <QtCore/qmap.h>
@@ -67,7 +69,7 @@ class ScriptEngine;
class ProjectResolver
{
public:
- ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult,
+ ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult,
SetupProjectParameters setupParameters, Logger &logger);
~ProjectResolver();
@@ -122,7 +124,7 @@ private:
void resolveFileTagger(Item *item, ProjectContext *projectContext);
void resolveJobLimit(Item *item, ProjectContext *projectContext);
void resolveScanner(Item *item, ProjectContext *projectContext);
- void resolveProductDependencies(const ProjectContext &projectContext);
+ void resolveProductDependencies();
void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const;
void applyFileTaggers(const ResolvedProductPtr &product) const;
QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true);
@@ -152,14 +154,6 @@ private:
QVariantMap parameters;
};
- struct ProductDependencyInfos
- {
- std::vector<ProductDependencyInfo> dependencies;
- bool hasDisabledDependency = false;
- };
-
- ProductDependencyInfos getProductDependencies(const ResolvedProductConstPtr &product,
- const ModuleLoaderResult::ProductInfo &productInfo);
QString sourceCodeAsFunction(const JSSourceValueConstPtr &value,
const PropertyDeclaration &decl) const;
QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const;
@@ -181,7 +175,7 @@ private:
ProgressObserver *m_progressObserver = nullptr;
ProductContext *m_productContext = nullptr;
ModuleContext *m_moduleContext = nullptr;
- QMap<QString, ResolvedProductPtr> m_productsByName;
+ QHash<Item *, ResolvedProductPtr> m_productsByItem;
QHash<FileTag, QList<ResolvedProductPtr> > m_productsByType;
QHash<ResolvedProductPtr, Item *> m_productItemMap;
mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap;
@@ -189,7 +183,7 @@ private:
mutable QHash<std::pair<QStringView, QStringList>, QString> m_scriptFunctions;
mutable QHash<QStringView, QString> m_sourceCode;
const SetupProjectParameters m_setupParams;
- ModuleLoaderResult m_loadResult;
+ ProjectTreeBuilder::Result m_loadResult;
Set<CodeLocation> m_groupLocationWarnings;
std::vector<std::pair<ResolvedProductPtr, Item *>> m_productExportInfo;
std::vector<ErrorInfo> m_queuedErrors;
diff --git a/src/lib/corelib/language/projecttreebuilder.cpp b/src/lib/corelib/language/projecttreebuilder.cpp
new file mode 100644
index 000000000..dedf26a88
--- /dev/null
+++ b/src/lib/corelib/language/projecttreebuilder.cpp
@@ -0,0 +1,2293 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "projecttreebuilder.h"
+
+#include "builtindeclarations.h"
+#include "evaluator.h"
+#include "filecontext.h"
+#include "groupshandler.h"
+#include "itemreader.h"
+#include "language.h"
+#include "localprofiles.h"
+#include "moduleinstantiator.h"
+#include "moduleloader.h"
+#include "modulepropertymerger.h"
+#include "moduleproviderloader.h"
+#include "probesresolver.h"
+#include "productitemmultiplexer.h"
+#include "scriptengine.h"
+#include "value.h"
+
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/fileinfo.h>
+#include <tools/filetime.h>
+#include <tools/preferences.h>
+#include <tools/progressobserver.h>
+#include <tools/profile.h>
+#include <tools/profiling.h>
+#include <tools/scripttools.h>
+#include <tools/settings.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QDir>
+#include <QDirIterator>
+#include <QFileInfo>
+
+#include <list>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <utility>
+#include <vector>
+
+namespace qbs::Internal {
+
+using ShadowProductInfo = std::pair<bool, QString>;
+enum class Deferral { Allowed, NotAllowed };
+enum class HandleDependency { Use, Ignore, Defer };
+
+class SearchPathsManager {
+public:
+ SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths);
+ SearchPathsManager(ItemReader &itemReader, ProductContext &product);
+ ~SearchPathsManager();
+
+private:
+ SearchPathsManager(ItemReader &itemReader, ProductContext *product,
+ const QStringList &extraSearchPaths);
+
+ ItemReader &m_itemReader;
+ ProductContext * const m_product = nullptr;
+ size_t m_oldSize{0};
+};
+
+class TimingData {
+public:
+ qint64 prepareProducts = 0;
+ qint64 productDependencies = 0;
+ qint64 moduleProviders = 0;
+ qint64 handleProducts = 0;
+ qint64 propertyChecking = 0;
+};
+
+class ProjectTreeBuilder::Private
+{
+public:
+ Private(const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
+ Logger &logger)
+ : parameters(parameters), itemPool(itemPool), evaluator(evaluator), logger(logger) {}
+
+ Item *loadTopLevelProjectItem();
+ void checkOverriddenValues();
+ void collectNameFromOverride(const QString &overrideString);
+ Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation);
+ Item *wrapInProjectIfNecessary(Item *item);
+ void handleTopLevelProject(Result &loadResult, const Set<QString> &referencedFilePaths);
+ void handleProject(Result *loadResult, TopLevelProjectContext *topLevelProjectContext,
+ Item *projectItem, const Set<QString> &referencedFilePaths);
+ void prepareProduct(ProjectContext &projectContext, Item *productItem);
+ void handleNextProduct(TopLevelProjectContext &tlp);
+ void handleProduct(ProductContext &productContext, Deferral deferral);
+ bool resolveDependencies(ProductContext &product, Deferral deferral);
+ void setSearchPathsForProduct(ProductContext &product);
+ void handleSubProject(ProjectContext &projectContext, Item *projectItem,
+ const Set<QString> &referencedFilePaths);
+ void initProductProperties(const ProductContext &product);
+ void printProfilingInfo();
+ void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp);
+ void checkProductNamesInOverrides();
+ void collectProductsByName(const TopLevelProjectContext &topLevelProject);
+ void adjustDependsItemForMultiplexing(const ProductContext &product, Item *dependsItem);
+ ShadowProductInfo getShadowProductInfo(const ProductContext &product) const;
+ void handleProductError(const ErrorInfo &error, ProductContext &productContext);
+ void handleModuleSetupError(ProductContext &product, const Item::Module &module,
+ const ErrorInfo &error);
+ bool checkItemCondition(Item *item);
+ QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
+ QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem);
+ void checkCancelation() const;
+ QList<Item *> loadReferencedFile(const QString &relativePath,
+ const CodeLocation &referencingLocation,
+ const Set<QString> &referencedFilePaths,
+ ProductContext &dummyContext);
+ void copyProperties(const Item *sourceProject, Item *targetProject);
+ bool mergeExportItems(ProductContext &productContext);
+ bool checkExportItemCondition(Item *exportItem, const ProductContext &product);
+
+ Item *loadBaseModule(ProductContext &product, Item *item);
+
+ struct LoadModuleResult {
+ Item *moduleItem = nullptr;
+ ProductContext *product = nullptr;
+ HandleDependency handleDependency = HandleDependency::Use;
+ };
+ LoadModuleResult loadModule(ProductContext &product, Item *loadingItem,
+ const ProductContext::ResolvedAndMultiplexedDependsItem &dependency,
+ Deferral deferral);
+ Item *searchAndLoadModuleFile(ProductContext *productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
+ FallbackMode fallbackMode, bool isRequired);
+
+ std::optional<ProductContext::ResolvedDependsItem>
+ resolveDependsItem(const ProductContext &product, Item *dependsItem);
+ std::queue<ProductContext::ResolvedAndMultiplexedDependsItem>
+ multiplexDependency(const ProductContext &product,
+ const ProductContext::ResolvedDependsItem &dependency);
+ static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
+ QVariantMap extractParameters(Item *dependsItem) const;
+
+ const SetupProjectParameters &parameters;
+ ItemPool &itemPool;
+ Evaluator &evaluator;
+ Logger &logger;
+ ProgressObserver *progressObserver = nullptr;
+ TimingData timingData;
+ ItemReader reader{logger};
+ ProbesResolver probesResolver{&evaluator, logger};
+ ModuleProviderLoader moduleProviderLoader{&reader, &evaluator, &probesResolver, logger};
+ ModuleLoader moduleLoader{parameters, reader, evaluator, logger};
+ ModulePropertyMerger propertyMerger{parameters, evaluator, logger};
+ ModuleInstantiator moduleInstantiator{parameters, itemPool, propertyMerger, logger};
+ ProductItemMultiplexer multiplexer{parameters, evaluator, logger, [this](Item *productItem) {
+ return moduleInstantiator.retrieveQbsItem(productItem);
+ }};
+ GroupsHandler groupsHandler{parameters, moduleInstantiator, evaluator, logger};
+ LocalProfiles localProfiles{parameters, evaluator, logger};
+ FileTime lastResolveTime;
+ QVariantMap storedProfiles;
+
+ Set<Item *> disabledItems;
+ std::unique_ptr<Settings> settings;
+ Set<QString> projectNamesUsedInOverrides;
+ Set<QString> productNamesUsedInOverrides;
+ Set<QString> disabledProjects;
+ Set<QString> erroneousProducts;
+ std::multimap<QString, ProductContext *> productsByName;
+
+ // For fast look-up when resolving Depends.productTypes.
+ // The contract is that it contains fully handled, error-free, enabled products.
+ std::multimap<FileTag, ProductContext *> productsByType;
+
+ Version qbsVersion;
+ Item *tempScopeItem = nullptr;
+ QHash<std::pair<QString, QualifiedId>, std::optional<QString>> existingModulePathCache;
+ QMap<QString, QStringList> moduleDirListCache;
+
+private:
+ class TempBaseModuleAttacher {
+ public:
+ TempBaseModuleAttacher(Private *d, ProductContext &product);
+ ~TempBaseModuleAttacher() { drop(); }
+ void drop();
+ Item *tempBaseModuleItem() const { return m_tempBaseModule; }
+
+ private:
+ Item * const m_productItem;
+ ValuePtr m_origQbsValue;
+ Item *m_tempBaseModule = nullptr;
+ };
+};
+
+ProjectTreeBuilder::ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ Evaluator &evaluator, Logger &logger)
+ : d(new Private(parameters, itemPool, evaluator, logger))
+{
+ d->reader.setDeprecationWarningMode(parameters.deprecationWarningMode());
+ d->reader.setEnableTiming(parameters.logElapsedTime());
+ d->moduleProviderLoader.setProjectParameters(parameters);
+ d->probesResolver.setProjectParameters(parameters);
+ d->settings = std::make_unique<Settings>(parameters.settingsDirectory());
+}
+
+ProjectTreeBuilder::~ProjectTreeBuilder() { delete d; }
+
+void ProjectTreeBuilder::setProgressObserver(ProgressObserver *progressObserver)
+{
+ d->progressObserver = progressObserver;
+}
+
+void ProjectTreeBuilder::setSearchPaths(const QStringList &searchPaths)
+{
+ d->reader.setSearchPaths(searchPaths);
+ qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths;
+}
+
+void ProjectTreeBuilder::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
+{
+ d->probesResolver.setOldProjectProbes(oldProbes);
+}
+
+void ProjectTreeBuilder::setOldProductProbes(
+ const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
+{
+ d->probesResolver.setOldProductProbes(oldProbes);
+}
+
+void ProjectTreeBuilder::setLastResolveTime(const FileTime &time) { d->lastResolveTime = time; }
+
+void ProjectTreeBuilder::setStoredProfiles(const QVariantMap &profiles)
+{
+ d->storedProfiles = profiles;
+}
+
+void ProjectTreeBuilder::setStoredModuleProviderInfo(
+ const StoredModuleProviderInfo &moduleProviderInfo)
+{
+ d->moduleProviderLoader.setStoredModuleProviderInfo(moduleProviderInfo);
+}
+
+ProjectTreeBuilder::Result ProjectTreeBuilder::load()
+{
+ TimedActivityLogger mainTimer(d->logger, Tr::tr("ProjectTreeBuilder"),
+ d->parameters.logElapsedTime());
+ qCDebug(lcModuleLoader) << "load" << d->parameters.projectFilePath();
+
+ d->checkOverriddenValues();
+ d->reader.setPool(&d->itemPool);
+
+ Result result;
+ result.profileConfigs = d->storedProfiles;
+ result.root = d->loadTopLevelProjectItem();
+ d->handleTopLevelProject(result, {QDir::cleanPath(d->parameters.projectFilePath())});
+
+ result.qbsFiles = d->reader.filesRead() - d->moduleProviderLoader.tempQbsFiles();
+ for (auto it = d->localProfiles.profiles().begin(); it != d->localProfiles.profiles().end();
+ ++it) {
+ result.profileConfigs.remove(it.key());
+ }
+
+ d->printProfilingInfo();
+
+ return result;
+}
+
+Item *ProjectTreeBuilder::Private::loadTopLevelProjectItem()
+{
+ const QStringList topLevelSearchPaths
+ = parameters.finalBuildConfigurationTree()
+ .value(StringConstants::projectPrefix()).toMap()
+ .value(StringConstants::qbsSearchPathsProperty()).toStringList();
+ SearchPathsManager searchPathsManager(reader, topLevelSearchPaths);
+ Item * const root = loadItemFromFile(parameters.projectFilePath(), CodeLocation());
+ if (!root)
+ return {};
+
+ switch (root->type()) {
+ case ItemType::Product:
+ return wrapInProjectIfNecessary(root);
+ case ItemType::Project:
+ return root;
+ default:
+ throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it"
+ " is of type '%1'.").arg(root->typeName()), root->location());
+ }
+}
+
+void ProjectTreeBuilder::Private::checkOverriddenValues()
+{
+ static const auto matchesPrefix = [](const QString &key) {
+ static const QStringList prefixes({StringConstants::projectPrefix(),
+ QStringLiteral("projects"),
+ QStringLiteral("products"), QStringLiteral("modules"),
+ StringConstants::moduleProviders(),
+ StringConstants::qbsModule()});
+ for (const auto &prefix : prefixes) {
+ if (key.startsWith(prefix + QLatin1Char('.')))
+ return true;
+ }
+ return false;
+ };
+ const QVariantMap &overriddenValues = parameters.overriddenValues();
+ for (auto it = overriddenValues.begin(); it != overriddenValues.end(); ++it) {
+ if (matchesPrefix(it.key())) {
+ collectNameFromOverride(it.key());
+ continue;
+ }
+
+ ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(it.key()));
+ e.append(Tr::tr("Please use one of the following:"));
+ e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>."
+ "<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>."
+ "<property-name>:value"));
+ handlePropertyError(e, parameters, logger);
+ }
+}
+
+void ProjectTreeBuilder::Private::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()) {
+ projectNamesUsedInOverrides.insert(projectName);
+ return;
+ }
+ const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString);
+ if (!productName.isEmpty()) {
+ productNamesUsedInOverrides.insert(productName.left(
+ productName.indexOf(StringConstants::dot())));
+ return;
+ }
+}
+
+Item *ProjectTreeBuilder::Private::loadItemFromFile(const QString &filePath,
+ const CodeLocation &referencingLocation)
+{
+ return reader.setupItemFromFile(filePath, referencingLocation, evaluator);
+}
+
+Item *ProjectTreeBuilder::Private::wrapInProjectIfNecessary(Item *item)
+{
+ if (item->type() == ItemType::Project)
+ return item;
+ Item *prj = Item::create(item->pool(), ItemType::Project);
+ Item::addChild(prj, item);
+ prj->setFile(item->file());
+ prj->setLocation(item->location());
+ prj->setupForBuiltinType(parameters.deprecationWarningMode(), logger);
+ return prj;
+}
+
+void ProjectTreeBuilder::Private::handleTopLevelProject(Result &loadResult,
+ const Set<QString> &referencedFilePaths)
+{
+ TopLevelProjectContext tlp;
+ tlp.buildDirectory = TopLevelProject::deriveBuildDirectory(
+ parameters.buildRoot(),
+ TopLevelProject::deriveId(parameters.finalBuildConfigurationTree()));
+ Item * const projectItem = loadResult.root;
+ projectItem->setProperty(StringConstants::sourceDirectoryProperty(),
+ VariantValue::create(QFileInfo(projectItem->file()->filePath())
+ .absolutePath()));
+ projectItem->setProperty(StringConstants::buildDirectoryProperty(),
+ VariantValue::create(tlp.buildDirectory));
+ projectItem->setProperty(StringConstants::profileProperty(),
+ VariantValue::create(parameters.topLevelProfile()));
+ handleProject(&loadResult, &tlp, projectItem, referencedFilePaths);
+ checkProjectNamesInOverrides(tlp);
+ collectProductsByName(tlp);
+ checkProductNamesInOverrides();
+
+ for (ProjectContext * const projectContext : qAsConst(tlp.projects)) {
+ for (ProductContext &productContext : projectContext->products)
+ tlp.productsToHandle.emplace_back(&productContext, -1);
+ }
+ while (!tlp.productsToHandle.empty())
+ handleNextProduct(tlp);
+
+ loadResult.projectProbes = tlp.probes;
+ loadResult.storedModuleProviderInfo = moduleProviderLoader.storedModuleProviderInfo();
+
+ reader.clearExtraSearchPathsStack();
+ AccumulatingTimer timer(parameters.logElapsedTime()
+ ? &timingData.propertyChecking : nullptr);
+ checkPropertyDeclarations(projectItem, disabledItems, parameters, logger);
+}
+
+void ProjectTreeBuilder::Private::handleProject(
+ Result *loadResult, TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
+ const Set<QString> &referencedFilePaths)
+{
+ auto p = std::make_unique<ProjectContext>();
+ auto &projectContext = *p;
+ projectContext.topLevelProject = topLevelProjectContext;
+ projectContext.result = loadResult;
+ ItemValuePtr itemValue = ItemValue::create(projectItem);
+ projectContext.scope = Item::create(&itemPool, ItemType::Scope);
+ projectContext.scope->setFile(projectItem->file());
+ projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
+ ProductContext dummyProductContext;
+ dummyProductContext.project = &projectContext;
+ dummyProductContext.moduleProperties = parameters.finalBuildConfigurationTree();
+ dummyProductContext.profileModuleProperties = dummyProductContext.moduleProperties;
+ dummyProductContext.profileName = parameters.topLevelProfile();
+ loadBaseModule(dummyProductContext, projectItem);
+
+ projectItem->overrideProperties(parameters.overriddenValuesTree(),
+ StringConstants::projectPrefix(), parameters, logger);
+ projectContext.name = evaluator.stringValue(projectItem, StringConstants::nameProperty());
+ if (projectContext.name.isEmpty()) {
+ projectContext.name = FileInfo::baseName(projectItem->location().filePath());
+ projectItem->setProperty(StringConstants::nameProperty(),
+ VariantValue::create(projectContext.name));
+ }
+ projectItem->overrideProperties(parameters.overriddenValuesTree(),
+ StringConstants::projectsOverridePrefix() + projectContext.name,
+ parameters, logger);
+ if (!checkItemCondition(projectItem)) {
+ disabledProjects.insert(projectContext.name);
+ return;
+ }
+ topLevelProjectContext->projects.push_back(p.release());
+ SearchPathsManager searchPathsManager(reader, readExtraSearchPaths(projectItem)
+ << projectItem->file()->dirPath());
+ projectContext.searchPathsStack = reader.extraSearchPathsStack();
+ projectContext.item = projectItem;
+
+ const QString minVersionStr
+ = evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(),
+ QStringLiteral("1.3.0"));
+ const Version minVersion = Version::fromString(minVersionStr);
+ if (!minVersion.isValid()) {
+ throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion "
+ "is not a valid version string.")
+ .arg(minVersionStr), projectItem->location());
+ }
+ if (!qbsVersion.isValid())
+ qbsVersion = Version::fromString(QLatin1String(QBS_VERSION));
+ if (qbsVersion < minVersion) {
+ throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but "
+ "this is qbs version %2.").arg(minVersion.toString(),
+ qbsVersion.toString()));
+ }
+
+ for (Item * const child : projectItem->children())
+ child->setScope(projectContext.scope);
+
+ probesResolver.resolveProbes(&dummyProductContext, projectItem);
+ projectContext.topLevelProject->probes << dummyProductContext.info.probes;
+
+ localProfiles.collectProfilesFromItems(projectItem, projectContext.scope);
+
+ QList<Item *> multiplexedProducts;
+ for (Item * const child : projectItem->children()) {
+ if (child->type() == ItemType::Product)
+ multiplexedProducts << multiplexProductItem(dummyProductContext, child);
+ }
+ for (Item * const additionalProductItem : qAsConst(multiplexedProducts))
+ Item::addChild(projectItem, additionalProductItem);
+
+ const QList<Item *> originalChildren = projectItem->children();
+ for (Item * const child : originalChildren) {
+ switch (child->type()) {
+ case ItemType::Product:
+ prepareProduct(projectContext, child);
+ break;
+ case ItemType::SubProject:
+ handleSubProject(projectContext, child, referencedFilePaths);
+ break;
+ case ItemType::Project:
+ copyProperties(projectItem, child);
+ handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths);
+ break;
+ default:
+ break;
+ }
+ }
+
+ const QStringList refs = evaluator.stringListValue(
+ projectItem, StringConstants::referencesProperty());
+ const CodeLocation referencingLocation
+ = projectItem->property(StringConstants::referencesProperty())->location();
+ QList<Item *> additionalProjectChildren;
+ for (const QString &filePath : refs) {
+ try {
+ additionalProjectChildren << loadReferencedFile(
+ filePath, referencingLocation, referencedFilePaths, dummyProductContext);
+ } catch (const ErrorInfo &error) {
+ if (parameters.productErrorMode() == ErrorHandlingMode::Strict)
+ throw;
+ logger.printWarning(error);
+ }
+ }
+ for (Item * const subItem : qAsConst(additionalProjectChildren)) {
+ Item::addChild(projectContext.item, subItem);
+ switch (subItem->type()) {
+ case ItemType::Product:
+ prepareProduct(projectContext, subItem);
+ break;
+ case ItemType::Project:
+ copyProperties(projectItem, subItem);
+ handleProject(loadResult, topLevelProjectContext, subItem,
+ Set<QString>(referencedFilePaths) << subItem->file()->filePath());
+ break;
+ default:
+ break;
+ }
+ }
+
+}
+
+void ProjectTreeBuilder::Private::prepareProduct(ProjectContext &projectContext, Item *productItem)
+{
+ AccumulatingTimer timer(parameters.logElapsedTime()
+ ? &timingData.prepareProducts : nullptr);
+ checkCancelation();
+ qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
+
+ ProductContext productContext;
+ productContext.item = productItem;
+ productContext.project = &projectContext;
+
+ // Retrieve name, profile and multiplex id.
+ productContext.name = evaluator.stringValue(productItem, StringConstants::nameProperty());
+ QBS_CHECK(!productContext.name.isEmpty());
+ const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule());
+ if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
+ TempBaseModuleAttacher tbma(this, productContext);
+ productContext.profileName = evaluator.stringValue(
+ tbma.tempBaseModuleItem(), StringConstants::profileProperty(), QString());
+ } else {
+ productContext.profileName = parameters.topLevelProfile();
+ }
+ productContext.multiplexConfigurationId = evaluator.stringValue(
+ productItem, StringConstants::multiplexConfigurationIdProperty());
+ QBS_CHECK(!productContext.profileName.isEmpty());
+
+ // Set up full module property map based on the profile.
+ const auto it = projectContext.result->profileConfigs.constFind(productContext.profileName);
+ QVariantMap flatConfig;
+ if (it == projectContext.result->profileConfigs.constEnd()) {
+ const Profile profile(productContext.profileName, settings.get(), localProfiles.profiles());
+ if (!profile.exists()) {
+ ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
+ productItem->location());
+ handleProductError(error, productContext);
+ return;
+ }
+ flatConfig = SetupProjectParameters::expandedBuildConfiguration(
+ profile, parameters.configurationName());
+ projectContext.result->profileConfigs.insert(productContext.profileName, flatConfig);
+ } else {
+ flatConfig = it.value().toMap();
+ }
+ productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree(
+ flatConfig, {});
+ productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
+ flatConfig, parameters.overriddenValues());
+ initProductProperties(productContext);
+
+ // Set up product scope. This is mainly for using the "product" and "project"
+ // variables in some contexts.
+ ItemValuePtr itemValue = ItemValue::create(productItem);
+ productContext.scope = Item::create(&itemPool, ItemType::Scope);
+ productContext.scope->setProperty(StringConstants::productVar(), itemValue);
+ productContext.scope->setFile(productItem->file());
+ productContext.scope->setScope(productContext.project->scope);
+
+ const bool hasExportItems = mergeExportItems(productContext);
+
+ setScopeForDescendants(productItem, productContext.scope);
+
+ projectContext.products.push_back(productContext);
+
+ if (!hasExportItems || getShadowProductInfo(productContext).first)
+ return;
+
+ // This "shadow product" exists only to pull in a dependency on the actual product
+ // and nothing else, thus providing us with the pure environment that we need to
+ // evaluate the product's exported properties in isolation in the project resolver.
+ Item * const importer = Item::create(productItem->pool(), ItemType::Product);
+ importer->setProperty(QStringLiteral("name"),
+ VariantValue::create(StringConstants::shadowProductPrefix()
+ + productContext.name));
+ importer->setFile(productItem->file());
+ importer->setLocation(productItem->location());
+ importer->setScope(projectContext.scope);
+ importer->setupForBuiltinType(parameters.deprecationWarningMode(), logger);
+ Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
+ dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name));
+ dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false));
+ dependsItem->setFile(importer->file());
+ dependsItem->setLocation(importer->location());
+ dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), logger);
+ Item::addChild(importer, dependsItem);
+ Item::addChild(productItem, importer);
+ prepareProduct(projectContext, importer);
+}
+
+void ProjectTreeBuilder::Private::handleNextProduct(TopLevelProjectContext &tlp)
+{
+ auto [product, queueSizeOnInsert] = tlp.productsToHandle.front();
+ tlp.productsToHandle.pop_front();
+
+ // If the queue of in-progress products has shrunk since the last time we tried handling
+ // this product, there has been forward progress and we can allow a deferral.
+ const Deferral deferral = queueSizeOnInsert == -1
+ || queueSizeOnInsert > int(tlp.productsToHandle.size())
+ ? Deferral::Allowed : Deferral::NotAllowed;
+
+ reader.setExtraSearchPathsStack(product->project->searchPathsStack);
+ try {
+ handleProduct(*product, deferral);
+ if (product->name.startsWith(StringConstants::shadowProductPrefix()))
+ tlp.probes << product->info.probes;
+ } catch (const ErrorInfo &err) {
+ handleProductError(err, *product);
+ }
+
+ // The search paths stack can change during dependency resolution (due to module providers);
+ // check that we've rolled back all the changes
+ QBS_CHECK(reader.extraSearchPathsStack() == product->project->searchPathsStack);
+
+ // If we encountered a dependency to an in-progress product or to a bulk dependency,
+ // we defer handling this product if it hasn't failed yet and there is still forward progress.
+ if (!product->info.delayedError.hasError() && !product->resolveDependenciesState.empty())
+ tlp.productsToHandle.emplace_back(product, int(tlp.productsToHandle.size()));
+}
+
+void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferral deferral)
+{
+ checkCancelation();
+
+ AccumulatingTimer timer(parameters.logElapsedTime() ? &timingData.handleProducts : nullptr);
+ if (product.info.delayedError.hasError())
+ return;
+
+ if (!resolveDependencies(product, deferral))
+ return;
+
+ // Run probes for modules and product.
+ for (const Item::Module &module : product.item->modules()) {
+ if (!module.item->isPresentModule())
+ continue;
+ if (module.productInfo && disabledItems.contains(module.productInfo->item)) {
+ createNonPresentModule(itemPool, module.name.toString(),
+ QLatin1String("module's exporting product is disabled"),
+ module.item);
+ continue;
+ }
+ try {
+ probesResolver.resolveProbes(&product, module.item);
+ if (module.versionRange.minimum.isValid()
+ || module.versionRange.maximum.isValid()) {
+ if (module.versionRange.maximum.isValid()
+ && module.versionRange.minimum >= module.versionRange.maximum) {
+ throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module "
+ "'%3'").arg(module.versionRange.minimum.toString(),
+ module.versionRange.maximum.toString(),
+ module.name.toString()));
+ }
+ const Version moduleVersion = Version::fromString(
+ evaluator.stringValue(module.item,
+ StringConstants::versionProperty()));
+ if (moduleVersion < module.versionRange.minimum) {
+ throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
+ "at least %3.").arg(module.name.toString(),
+ moduleVersion.toString(),
+ module.versionRange.minimum.toString()));
+ }
+ if (module.versionRange.maximum.isValid()
+ && moduleVersion >= module.versionRange.maximum) {
+ throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
+ "lower than %3.").arg(module.name.toString(),
+ moduleVersion.toString(),
+ module.versionRange.maximum.toString()));
+ }
+ }
+ } catch (const ErrorInfo &error) {
+ handleModuleSetupError(product, module, error);
+ if (product.info.delayedError.hasError())
+ return;
+ }
+ }
+ probesResolver.resolveProbes(&product, product.item);
+
+ // After the probes have run, we can switch on the evaluator cache.
+ FileTags fileTags = evaluator.fileTagsValue(product.item, StringConstants::typeProperty());
+ EvalCacheEnabler cacheEnabler(&evaluator, evaluator.stringValue(
+ product.item,
+ StringConstants::sourceDirectoryProperty()));
+
+ // Run module validation scripts.
+ for (const Item::Module &module : product.item->modules()) {
+ if (!module.item->isPresentModule())
+ continue;
+ try {
+ evaluator.boolValue(module.item, StringConstants::validateProperty());
+ for (const auto &dep : module.item->modules()) {
+ if (dep.required && !dep.item->isPresentModule()) {
+ throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not "
+ "loaded successfully")
+ .arg(module.name.toString(), dep.name.toString()));
+ }
+ }
+ fileTags += evaluator.fileTagsValue(
+ module.item, StringConstants::additionalProductTypesProperty());
+ } catch (const ErrorInfo &error) {
+ handleModuleSetupError(product, module, error);
+ if (product.info.delayedError.hasError())
+ return;
+ }
+ }
+
+ // Disable modules that have been pulled in only by now-disabled modules.
+ // Note that this has to happen in the reverse order compared to the other loops,
+ // with the leaves checked last.
+ for (auto it = product.item->modules().rbegin(); it != product.item->modules().rend(); ++it) {
+ const Item::Module &module = *it;
+ if (!module.item->isPresentModule())
+ continue;
+ bool hasPresentLoadingItem = false;
+ for (const Item * const loadingItem : module.loadingItems) {
+ if (loadingItem == product.item) {
+ hasPresentLoadingItem = true;
+ break;
+ }
+ if (!loadingItem->isPresentModule())
+ continue;
+ if (loadingItem->prototype() && loadingItem->prototype()->type() == ItemType::Export) {
+ QBS_CHECK(loadingItem->prototype()->parent()->type() == ItemType::Product);
+ if (disabledItems.contains(loadingItem->prototype()->parent()))
+ continue;
+ }
+ hasPresentLoadingItem = true;
+ break;
+ }
+ if (!hasPresentLoadingItem) {
+ createNonPresentModule(itemPool, module.name.toString(),
+ QLatin1String("imported only by disabled module(s)"),
+ module.item);
+ continue;
+ }
+ }
+
+ // Now do the canonical module property values merge. Note that this will remove
+ // previously attached values from modules that failed validation.
+ // Evaluator cache entries that could potentially change due to this will be purged.
+ propertyMerger.doFinalMerge(product.item);
+
+ const bool enabled = checkItemCondition(product.item);
+ moduleLoader.checkDependencyParameterDeclarations({product.item, product.name, {}, {}});
+
+ groupsHandler.setupGroups(product.item, product.scope);
+ product.info.modulePropertiesSetInGroups = groupsHandler.modulePropertiesSetInGroups();
+ disabledItems.unite(groupsHandler.disabledGroups());
+
+ // Collect the full list of fileTags, including the values contributed by modules.
+ if (!product.info.delayedError.hasError() && enabled) {
+ for (const FileTag &tag : fileTags)
+ productsByType.insert({tag, &product});
+ product.item->setProperty(StringConstants::typeProperty(),
+ VariantValue::create(sorted(fileTags.toStringList())));
+ }
+ product.project->result->productInfos[product.item] = product.info;
+}
+
+bool ProjectTreeBuilder::Private::resolveDependencies(ProductContext &product, Deferral deferral)
+{
+ AccumulatingTimer timer(parameters.logElapsedTime()
+ ? &timingData.productDependencies : nullptr);
+
+ // Initialize the state with the direct Depends items of the product item.
+ // This branch is executed once per product, while the function might be entered
+ // multiple times due to deferrals.
+ if (product.resolveDependenciesState.empty()) {
+ setSearchPathsForProduct(product);
+ std::queue<Item *> topLevelDependsItems;
+ for (Item * const child : product.item->children()) {
+ if (child->type() == ItemType::Depends)
+ topLevelDependsItems.push(child);
+ }
+ product.resolveDependenciesState.push_front({product.item, {}, topLevelDependsItems, });
+ product.resolveDependenciesState.front().pendingResolvedDependencies.push(
+ ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency());
+ }
+
+ SearchPathsManager searchPathsMgr(reader, product);
+
+ while (!product.resolveDependenciesState.empty()) {
+fixme:
+ auto &state = product.resolveDependenciesState.front();
+ while (!state.pendingResolvedDependencies.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ const auto dependency = state.pendingResolvedDependencies.front();
+ try {
+ const LoadModuleResult res = loadModule(product, state.loadingItem, dependency,
+ deferral);
+ switch (res.handleDependency) {
+ case HandleDependency::Defer:
+ QBS_CHECK(deferral == Deferral::Allowed);
+ if (res.product)
+ state.pendingResolvedDependencies.front().product = res.product;
+ return false;
+ case HandleDependency::Ignore:
+ state.pendingResolvedDependencies.pop();
+ continue;
+ case HandleDependency::Use:
+ if (dependency.name.toString() == StringConstants::qbsModule()) {
+ state.pendingResolvedDependencies.pop();
+ continue;
+ }
+ break;
+ }
+
+ QBS_CHECK(res.moduleItem);
+ std::queue<Item *> moduleDependsItems;
+ for (Item * const child : res.moduleItem->children()) {
+ if (child->type() == ItemType::Depends)
+ moduleDependsItems.push(child);
+ }
+
+ state.pendingResolvedDependencies.pop();
+ product.resolveDependenciesState.push_front(
+ {res.moduleItem, dependency, moduleDependsItems, {}, {},
+ dependency.requiredGlobally || state.requiredByLoadingItem});
+ product.resolveDependenciesState.front().pendingResolvedDependencies.push(
+ ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency());
+ break;
+ } catch (const ErrorInfo &e) {
+ if (dependency.name.toString() == StringConstants::qbsModule())
+ throw e;
+
+ if (!dependency.requiredLocally) {
+ state.pendingResolvedDependencies.pop();
+ continue;
+ }
+
+ // See QBS-1338 for why we do not abort handling the product.
+ state.pendingResolvedDependencies.pop();
+ Item::Modules &modules = product.item->modules();
+ while (product.resolveDependenciesState.size() > 1) {
+ const auto loadingItemModule = std::find_if(
+ modules.begin(), modules.end(), [&](const Item::Module &m) {
+ return m.item == product.resolveDependenciesState.front().loadingItem;
+ });
+ for (auto it = loadingItemModule; it != modules.end(); ++it) {
+ createNonPresentModule(itemPool, it->name.toString(),
+ QLatin1String("error in Depends chain"), it->item);
+ }
+ modules.erase(loadingItemModule, modules.end());
+ product.resolveDependenciesState.pop_front();
+ }
+ handleProductError(e, product);
+ goto fixme;
+ }
+ }
+ if (&state != &product.resolveDependenciesState.front())
+ continue;
+
+ if (state.currentDependsItem) {
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+
+ // We postpone handling Depends.productTypes for as long as possible, because
+ // the full type of a product becomes available only after its modules have been loaded.
+ if (!state.currentDependsItem->productTypes.empty() && deferral == Deferral::Allowed)
+ return false;
+
+ state.pendingResolvedDependencies = multiplexDependency(product,
+ *state.currentDependsItem);
+ state.currentDependsItem.reset();
+ continue;
+ }
+
+ while (!state.pendingDependsItems.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ Item * const dependsItem = state.pendingDependsItems.front();
+ state.pendingDependsItems.pop();
+ adjustDependsItemForMultiplexing(product, dependsItem);
+ state.currentDependsItem = resolveDependsItem(product, dependsItem);
+ if (!state.currentDependsItem)
+ continue;
+ state.currentDependsItem->requiredGlobally = state.currentDependsItem->requiredLocally
+ && state.loadingItemOrigin.requiredGlobally;
+ goto fixme;
+ }
+
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ QBS_CHECK(state.pendingDependsItems.empty());
+
+ // This ensures a sorted module list in the product (dependers after dependencies).
+ if (product.resolveDependenciesState.size() > 1) {
+ QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
+ Item::Modules &modules = product.item->modules();
+ const auto loadingItemModule = std::find_if(modules.begin(), modules.end(),
+ [&](const Item::Module &m) {
+ return m.item == state.loadingItem;
+ });
+ QBS_CHECK(loadingItemModule != modules.end());
+ const Item::Module tempModule = *loadingItemModule;
+ modules.erase(loadingItemModule);
+ modules.push_back(tempModule);
+ }
+ product.resolveDependenciesState.pop_front();
+ }
+ return true;
+}
+
+void ProjectTreeBuilder::Private::setSearchPathsForProduct(ProductContext &product)
+{
+ QBS_CHECK(product.searchPaths.isEmpty());
+
+ product.searchPaths = readExtraSearchPaths(product.item);
+ Settings settings(parameters.settingsDirectory());
+ const QStringList prefsSearchPaths = Preferences(&settings, product.profileModuleProperties)
+ .searchPaths();
+ const QStringList &currentSearchPaths = reader.allSearchPaths();
+ for (const QString &p : prefsSearchPaths) {
+ if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
+ product.searchPaths << p;
+ }
+}
+
+void ProjectTreeBuilder::Private::handleSubProject(
+ ProjectContext &projectContext, Item *projectItem, const Set<QString> &referencedFilePaths)
+{
+ qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath();
+
+ Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject);
+ if (!checkItemCondition(projectItem))
+ return;
+ if (propertiesItem) {
+ propertiesItem->setScope(projectItem);
+ if (!checkItemCondition(propertiesItem))
+ return;
+ }
+
+ Item *loadedItem = nullptr;
+ QString subProjectFilePath;
+ try {
+ const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath());
+ const QString relativeFilePath
+ = evaluator.stringValue(projectItem, StringConstants::filePathProperty());
+ subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath);
+ if (referencedFilePaths.contains(subProjectFilePath))
+ throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.")
+ .arg(relativeFilePath), projectItem->location());
+ loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location());
+ } catch (const ErrorInfo &error) {
+ if (parameters.productErrorMode() == ErrorHandlingMode::Strict)
+ throw;
+ logger.printWarning(error);
+ return;
+ }
+
+ loadedItem = wrapInProjectIfNecessary(loadedItem);
+ const bool inheritProperties = evaluator.boolValue(
+ projectItem, StringConstants::inheritPropertiesProperty());
+
+ if (inheritProperties)
+ copyProperties(projectItem->parent(), loadedItem);
+ if (propertiesItem) {
+ const Item::PropertyMap &overriddenProperties = propertiesItem->properties();
+ for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it)
+ loadedItem->setProperty(it.key(), it.value());
+ }
+
+ Item::addChild(projectItem, loadedItem);
+ projectItem->setScope(projectContext.scope);
+ handleProject(projectContext.result, projectContext.topLevelProject, loadedItem,
+ Set<QString>(referencedFilePaths) << subProjectFilePath);
+}
+
+void ProjectTreeBuilder::Private::initProductProperties(const ProductContext &product)
+{
+ QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
+ product.multiplexConfigurationId);
+ buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
+ product.item->setProperty(StringConstants::buildDirectoryProperty(),
+ VariantValue::create(buildDir));
+ const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath();
+ product.item->setProperty(StringConstants::sourceDirectoryProperty(),
+ VariantValue::create(sourceDir));
+}
+
+void ProjectTreeBuilder::Private::printProfilingInfo()
+{
+ if (!parameters.logElapsedTime())
+ return;
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Project file loading and parsing took %1.")
+ .arg(elapsedTimeString(reader.elapsedTime()));
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Preparing products took %1.")
+ .arg(elapsedTimeString(timingData.prepareProducts));
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Handling products took %1.")
+ .arg(elapsedTimeString(timingData.handleProducts));
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Setting up product dependencies took %1.")
+ .arg(elapsedTimeString(timingData.productDependencies));
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Running module providers took %1.")
+ .arg(elapsedTimeString(timingData.moduleProviders));
+ moduleInstantiator.printProfilingInfo(6);
+ propertyMerger.printProfilingInfo(6);
+ groupsHandler.printProfilingInfo(4);
+ probesResolver.printProfilingInfo(4);
+ logger.qbsLog(LoggerInfo, true) << " "
+ << Tr::tr("Property checking took %1.")
+ .arg(elapsedTimeString(timingData.propertyChecking));
+}
+
+void ProjectTreeBuilder::Private::checkProjectNamesInOverrides(const TopLevelProjectContext &tlp)
+{
+ for (const QString &projectNameInOverride : projectNamesUsedInOverrides) {
+ if (disabledProjects.contains(projectNameInOverride))
+ continue;
+ if (!any_of(tlp.projects, [&projectNameInOverride](const ProjectContext *p) {
+ return p->name == projectNameInOverride; })) {
+ handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
+ .arg(projectNameInOverride), parameters, logger);
+ }
+ }
+}
+
+void ProjectTreeBuilder::Private::checkProductNamesInOverrides()
+{
+ for (const QString &productNameInOverride : productNamesUsedInOverrides) {
+ if (erroneousProducts.contains(productNameInOverride))
+ continue;
+ if (!any_of(productsByName, [&productNameInOverride](
+ const std::pair<QString, ProductContext *> &elem) {
+ // 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.
+ return elem.first == productNameInOverride
+ || elem.first.startsWith(productNameInOverride + StringConstants::dot());
+ })) {
+ handlePropertyError(Tr::tr("Unknown product '%1' in property override.")
+ .arg(productNameInOverride), parameters, logger);
+ }
+ }
+}
+
+void ProjectTreeBuilder::Private::collectProductsByName(
+ const TopLevelProjectContext &topLevelProject)
+{
+ for (ProjectContext * const project : topLevelProject.projects) {
+ for (ProductContext &product : project->products)
+ productsByName.insert({product.name, &product});
+ }
+}
+
+void ProjectTreeBuilder::Private::adjustDependsItemForMultiplexing(const ProductContext &product,
+ Item *dependsItem)
+{
+ const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
+ const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty();
+ if (name == product.name) {
+ QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
+ return;
+ }
+ const auto productRange = productsByName.equal_range(name);
+ if (productRange.first == productRange.second)
+ return; // Dependency is a module. Nothing to adjust.
+
+ bool profilesPropertyIsSet;
+ const QStringList profiles = evaluator.stringListValue(
+ dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
+
+ std::vector<const ProductContext *> multiplexedDependencies;
+ bool hasNonMultiplexedDependency = false;
+ for (auto it = productRange.first; it != productRange.second; ++it) {
+ if (!it->second->multiplexConfigurationId.isEmpty())
+ multiplexedDependencies.push_back(it->second);
+ else
+ hasNonMultiplexedDependency = true;
+ }
+ bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
+
+ static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs,
+ const QVariantMap &rhs) {
+ QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
+ for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
+ const auto rhsProperty = rhs.find(lhsProperty.key());
+ const bool isCommonProperty = rhsProperty != rhs.constEnd();
+ if (isCommonProperty && lhsProperty.value() != rhsProperty.value())
+ return false;
+ }
+ return true;
+ };
+
+ // These are the allowed cases:
+ // (1) Normal dependency with no multiplexing whatsoever.
+ // (2) Both product and dependency are multiplexed.
+ // (2a) The profiles property is not set, we want to depend on the best
+ // matching variant.
+ // (2b) The profiles property is set, we want to depend on all variants
+ // with a matching profile.
+ // (3) The product is not multiplexed, but the dependency is.
+ // (3a) The profiles property is not set, the dependency has an aggregator.
+ // We want to depend on the aggregator.
+ // (3b) The profiles property is not set, the dependency does not have an
+ // aggregator. We want to depend on all the multiplexed variants.
+ // (3c) The profiles property is set, we want to depend on all variants
+ // with a matching profile regardless of whether an aggregator exists or not.
+ // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
+ // any Depends items.
+ // (5) The product is a "shadow product". In that case, we know which product
+ // it should have a dependency on, and we make sure we depend on that.
+
+ // (1) and (4)
+ if (!hasMultiplexedDependencies)
+ return;
+
+ // (3a)
+ if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
+ return;
+
+ QStringList multiplexIds;
+ const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product);
+ const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
+ const auto productMultiplexConfig
+ = multiplexer.multiplexIdToVariantMap(product.multiplexConfigurationId);
+
+ for (const ProductContext *dependency : multiplexedDependencies) {
+ const bool depMatchesShadowProduct = isShadowProduct
+ && dependency->item == product.item->parent();
+ const QString depMultiplexId = dependency->multiplexConfigurationId;
+ if (depMatchesShadowProduct) { // (5)
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(depMultiplexId));
+ return;
+ }
+ if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
+ if (dependency->multiplexConfigurationId == product.multiplexConfigurationId) {
+ const ValuePtr &multiplexId = product.item->property(
+ StringConstants::multiplexConfigurationIdProperty());
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ multiplexId);
+ return;
+
+ }
+ // Otherwise collect partial matches and decide later
+ const auto dependencyMultiplexConfig
+ = multiplexer.multiplexIdToVariantMap(dependency->multiplexConfigurationId);
+
+ if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
+ multiplexIds << dependency->multiplexConfigurationId;
+ } else {
+ // (2b), (3b) or (3c)
+ const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
+ || profiles.contains(dependency->profileName);
+ if (profileMatch)
+ multiplexIds << depMultiplexId;
+ }
+ }
+ if (multiplexIds.empty()) {
+ const QString productName = ProductItemMultiplexer::fullProductDisplayName(
+ product.name, product.multiplexConfigurationId);
+ throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
+ "There are no eligible multiplex candidates.").arg(productName,
+ name),
+ dependsItem->location());
+ }
+
+ // In case of (2a), at most 1 match is allowed
+ if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
+ const QString productName = ProductItemMultiplexer::fullProductDisplayName(
+ product.name, product.multiplexConfigurationId);
+ QStringList candidateNames;
+ for (const auto &id : qAsConst(multiplexIds))
+ candidateNames << ProductItemMultiplexer::fullProductDisplayName(name, id);
+ throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
+ "Eligible multiplex candidates: %3.").arg(
+ productName, name, candidateNames.join(QLatin1String(", "))),
+ dependsItem->location());
+ }
+
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(multiplexIds));
+}
+
+ShadowProductInfo ProjectTreeBuilder::Private::getShadowProductInfo(
+ const ProductContext &product) const
+{
+ const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
+ return std::make_pair(isShadowProduct, isShadowProduct
+ ? product.name.mid(StringConstants::shadowProductPrefix().size())
+ : QString());
+}
+
+void ProjectTreeBuilder::Private::handleProductError(const ErrorInfo &error,
+ ProductContext &productContext)
+{
+ const bool alreadyHadError = productContext.info.delayedError.hasError();
+ if (!alreadyHadError) {
+ productContext.info.delayedError.append(Tr::tr("Error while handling product '%1':")
+ .arg(productContext.name),
+ productContext.item->location());
+ }
+ if (error.isInternalError()) {
+ if (alreadyHadError) {
+ qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
+ << "in product" << productContext.name;
+ return;
+ }
+ }
+ const auto errorItems = error.items();
+ for (const ErrorItem &ei : errorItems)
+ productContext.info.delayedError.append(ei.description(), ei.codeLocation());
+ productContext.project->result->productInfos[productContext.item] = productContext.info;
+ disabledItems << productContext.item;
+ erroneousProducts.insert(productContext.name);
+}
+
+void ProjectTreeBuilder::Private::handleModuleSetupError(
+ ProductContext &product, const Item::Module &module, const ErrorInfo &error)
+{
+ if (module.required) {
+ handleProductError(error, product);
+ } else {
+ qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
+ << "found, but not usable in product" << product.name
+ << error.toString();
+ createNonPresentModule(itemPool, module.name.toString(),
+ QStringLiteral("failed validation"), module.item);
+ }
+}
+
+bool ProjectTreeBuilder::Private::checkItemCondition(Item *item)
+{
+ if (evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return true;
+ disabledItems += item;
+ return false;
+}
+
+QStringList ProjectTreeBuilder::Private::readExtraSearchPaths(Item *item, bool *wasSet)
+{
+ QStringList result;
+ const QStringList paths = evaluator.stringListValue(
+ item, StringConstants::qbsSearchPathsProperty(), wasSet);
+ const JSSourceValueConstPtr prop = item->sourceProperty(
+ StringConstants::qbsSearchPathsProperty());
+
+ // Value can come from within a project file or as an overridden value from the user
+ // (e.g command line).
+ const QString basePath = FileInfo::path(prop ? prop->file()->filePath()
+ : parameters.projectFilePath());
+ for (const QString &path : paths)
+ result += FileInfo::resolvePath(basePath, path);
+ return result;
+}
+
+QList<Item *> ProjectTreeBuilder::Private::multiplexProductItem(ProductContext &dummyContext,
+ Item *productItem)
+{
+ // Overriding the product item properties must be done here already, because multiplexing
+ // properties might depend on product properties.
+ const QString &nameKey = StringConstants::nameProperty();
+ QString productName = evaluator.stringValue(productItem, nameKey);
+ if (productName.isEmpty()) {
+ productName = FileInfo::completeBaseName(productItem->file()->filePath());
+ productItem->setProperty(nameKey, VariantValue::create(productName));
+ }
+ productItem->overrideProperties(parameters.overriddenValuesTree(),
+ StringConstants::productsOverridePrefix() + productName,
+ parameters, logger);
+ dummyContext.item = productItem;
+ TempBaseModuleAttacher tbma(this, dummyContext);
+ return multiplexer.multiplex(productName, productItem, tbma.tempBaseModuleItem(),
+ [&] { tbma.drop(); });
+}
+
+void ProjectTreeBuilder::Private::checkCancelation() const
+{
+ if (progressObserver && progressObserver->canceled()) {
+ throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
+ .arg(TopLevelProject::deriveId(
+ parameters.finalBuildConfigurationTree())));
+ }
+}
+
+QList<Item *> ProjectTreeBuilder::Private::loadReferencedFile(
+ const QString &relativePath, const CodeLocation &referencingLocation,
+ const Set<QString> &referencedFilePaths, ProductContext &dummyContext)
+{
+ QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()),
+ relativePath);
+ if (FileInfo(absReferencePath).isDir()) {
+ QString qbsFilePath;
+
+ QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards());
+ while (dit.hasNext()) {
+ if (!qbsFilePath.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one "
+ "qbs file.").arg(absReferencePath), referencingLocation);
+ }
+ qbsFilePath = dit.next();
+ }
+ if (qbsFilePath.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.")
+ .arg(absReferencePath), referencingLocation);
+ }
+ absReferencePath = qbsFilePath;
+ }
+ if (referencedFilePaths.contains(absReferencePath))
+ throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath),
+ referencingLocation);
+ Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation);
+ if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) {
+ ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.")
+ .arg(subItem->typeName()));
+ error.append(Tr::tr("Item is defined here."), subItem->location());
+ error.append(Tr::tr("File is referenced here."), referencingLocation);
+ throw error;
+ }
+ subItem->setScope(dummyContext.project->scope);
+ subItem->setParent(dummyContext.project->item);
+ QList<Item *> loadedItems;
+ loadedItems << subItem;
+ if (subItem->type() == ItemType::Product) {
+ localProfiles.collectProfilesFromItems(subItem, dummyContext.project->scope);
+ loadedItems << multiplexProductItem(dummyContext, subItem);
+ }
+ return loadedItems;
+}
+
+void ProjectTreeBuilder::Private::copyProperties(const Item *sourceProject, Item *targetProject)
+{
+ if (!sourceProject)
+ return;
+ const QList<PropertyDeclaration> builtinProjectProperties
+ = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties();
+ Set<QString> builtinProjectPropertyNames;
+ for (const PropertyDeclaration &p : builtinProjectProperties)
+ builtinProjectPropertyNames << p.name();
+
+ for (auto it = sourceProject->propertyDeclarations().begin();
+ it != sourceProject->propertyDeclarations().end(); ++it) {
+
+ // We must not inherit built-in properties such as "name",
+ // but there are exceptions.
+ if (it.key() == StringConstants::qbsSearchPathsProperty()
+ || it.key() == StringConstants::profileProperty()
+ || it.key() == StringConstants::buildDirectoryProperty()
+ || it.key() == StringConstants::sourceDirectoryProperty()
+ || it.key() == StringConstants::minimumQbsVersionProperty()) {
+ const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key());
+ QBS_ASSERT(v, continue);
+ if (v->sourceCode() == StringConstants::undefinedValue())
+ sourceProject->copyProperty(it.key(), targetProject);
+ continue;
+ }
+
+ if (builtinProjectPropertyNames.contains(it.key()))
+ continue;
+
+ if (targetProject->hasOwnProperty(it.key()))
+ continue; // Ignore stuff the target project already has.
+
+ targetProject->setPropertyDeclaration(it.key(), it.value());
+ sourceProject->copyProperty(it.key(), targetProject);
+ }
+}
+
+static 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();
+ }
+ }
+}
+
+static void adjustParametersScopes(Item *item, Item *scope)
+{
+ if (item->type() == ItemType::ModuleParameters) {
+ item->setScope(scope);
+ return;
+ }
+
+ for (const auto &value : item->properties()) {
+ if (value->type() == Value::ItemValueType)
+ adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
+ }
+}
+
+static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
+{
+ if (value->type() == Value::ItemValueType) {
+ const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value);
+ const Item * const valueItem = itemValue->item();
+ Item * const subItem = dst->itemProperty(name, itemValue)->item();
+ for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it)
+ mergeProperty(subItem, it.key(), it.value());
+ return;
+ }
+
+ // If the property already exists, set up the base value.
+ if (value->type() == Value::JSSourceValueType) {
+ const auto jsValue = static_cast<JSSourceValue *>(value.get());
+ if (jsValue->isBuiltinDefaultValue())
+ return;
+ const ValuePtr baseValue = dst->property(name);
+ if (baseValue) {
+ QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
+ const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
+ baseValue->clone());
+ jsValue->setBaseValue(jsBaseValue);
+ std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
+ jsValue->clearAlternatives();
+ for (JSSourceValue::Alternative &a : alternatives) {
+ a.value->setBaseValue(jsBaseValue);
+ jsValue->addAlternative(a);
+ }
+ }
+ }
+ dst->setProperty(name, value);
+}
+
+bool ProjectTreeBuilder::Private::mergeExportItems(ProductContext &productContext)
+{
+ std::vector<Item *> exportItems;
+ QList<Item *> children = productContext.item->children();
+
+ const auto isExport = [](Item *item) { return item->type() == ItemType::Export; };
+ std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport);
+ qbs::Internal::removeIf(children, isExport);
+
+ // Note that we do not return if there are no Export items: The "merged" item becomes the
+ // "product module", which always needs to exist, regardless of whether the product sources
+ // actually contain an Export item or not.
+ if (!exportItems.empty())
+ productContext.item->setChildren(children);
+
+ Item *merged = Item::create(productContext.item->pool(), ItemType::Export);
+ const QString &nameKey = StringConstants::nameProperty();
+ const ValuePtr nameValue = VariantValue::create(productContext.name);
+ merged->setProperty(nameKey, nameValue);
+ Set<FileContextConstPtr> filesWithExportItem;
+ QVariantMap defaultParameters;
+ for (Item * const exportItem : qAsConst(exportItems)) {
+ checkCancelation();
+ if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
+ throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
+ exportItem->location());
+ exportItem->setProperty(nameKey, nameValue);
+ if (!checkExportItemCondition(exportItem, productContext))
+ continue;
+ filesWithExportItem += exportItem->file();
+ for (Item * const child : exportItem->children()) {
+ if (child->type() == ItemType::Parameters) {
+ adjustParametersScopes(child, child);
+ mergeParameters(defaultParameters,
+ getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(child)).toMap());
+ } else {
+ Item::addChild(merged, child);
+ }
+ }
+ const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations();
+ for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) {
+ const PropertyDeclaration &newDecl = it.value();
+ const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key());
+ if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) {
+ ErrorInfo error(Tr::tr("Export item in inherited item redeclares property "
+ "'%1' with different type.").arg(it.key()), exportItem->location());
+ handlePropertyError(error, parameters, logger);
+ }
+ merged->setPropertyDeclaration(newDecl.name(), newDecl);
+ }
+ for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin();
+ it != exportItem->properties().constEnd(); ++it) {
+ mergeProperty(merged, it.key(), it.value());
+ }
+ }
+ merged->setFile(exportItems.empty()
+ ? productContext.item->file() : exportItems.back()->file());
+ merged->setLocation(exportItems.empty()
+ ? productContext.item->location() : exportItems.back()->location());
+ Item::addChild(productContext.item, merged);
+ merged->setupForBuiltinType(parameters.deprecationWarningMode(), logger);
+
+ productContext.mergedExportItem = merged;
+ productContext.defaultParameters = defaultParameters;
+ return !exportItems.empty();
+}
+
+// TODO: This seems dubious. Can we merge the conditions instead?
+bool ProjectTreeBuilder::Private::checkExportItemCondition(Item *exportItem,
+ const ProductContext &product)
+{
+ class ScopeHandler {
+ public:
+ ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
+ : m_exportItem(exportItem)
+ {
+ if (!*cachedScopeItem)
+ *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
+ Item * const scope = *cachedScopeItem;
+ QBS_CHECK(productContext.item->file());
+ scope->setFile(productContext.item->file());
+ scope->setScope(productContext.item);
+ productContext.project->scope->copyProperty(StringConstants::projectVar(), scope);
+ productContext.scope->copyProperty(StringConstants::productVar(), scope);
+ QBS_CHECK(!exportItem->scope());
+ exportItem->setScope(scope);
+ }
+ ~ScopeHandler() { m_exportItem->setScope(nullptr); }
+
+ private:
+ Item * const m_exportItem;
+ } scopeHandler(exportItem, product, &tempScopeItem);
+ return checkItemCondition(exportItem);
+}
+
+static void checkForModuleNamePrefixCollision(
+ const Item *product, const ProductContext::ResolvedAndMultiplexedDependsItem &dependency)
+{
+ if (!product)
+ return;
+
+ for (const Item::Module &m : product->modules()) {
+ if (m.name.length() == dependency.name.length()
+ || m.name.front() != dependency.name.front()) {
+ continue;
+ }
+ QualifiedId shortName;
+ QualifiedId longName;
+ if (m.name < dependency.name) {
+ shortName = m.name;
+ longName = dependency.name;
+ } else {
+ shortName = dependency.name;
+ longName = m.name;
+ }
+ throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the "
+ "name of module '%2', which is not allowed")
+ .arg(shortName.toString(), longName.toString()), dependency.location());
+ }
+}
+
+// Note: This function is never called for regular loading of the base module into a product,
+// but only for the special cases of loading the dummy base module into a project
+// and temporarily providing a base module for product multiplexing.
+Item *ProjectTreeBuilder::Private::loadBaseModule(ProductContext &product, Item *item)
+{
+ const QualifiedId baseModuleName(StringConstants::qbsModule());
+ const auto baseDependency
+ = ProductContext::ResolvedAndMultiplexedDependsItem::makeBaseDependency();
+ Item * const moduleItem = loadModule(product, item, baseDependency, Deferral::NotAllowed)
+ .moduleItem;
+ if (Q_UNLIKELY(!moduleItem))
+ throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
+ return moduleItem;
+}
+
+ProjectTreeBuilder::Private::LoadModuleResult
+ProjectTreeBuilder::Private::loadModule(ProductContext &product, Item *loadingItem,
+ const ProductContext::ResolvedAndMultiplexedDependsItem &dependency,
+ Deferral deferral)
+{
+ qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
+ << "id:" << dependency.id();
+
+ QBS_CHECK(loadingItem);
+
+ const auto findExistingModule = [&dependency](Item *item) -> std::pair<Item::Module *, Item *> {
+ if (!item) // Happens if and only if called via loadBaseModule().
+ return {};
+ Item *moduleWithSameName = nullptr;
+ for (Item::Module &m : item->modules()) {
+ if (m.name != dependency.name)
+ continue;
+ if (!m.productInfo) {
+ QBS_CHECK(!dependency.product);
+ return {&m, m.item};
+ }
+ if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
+ && m.productInfo->multiplexId == dependency.multiplexId) {
+ return {&m, m.item};
+ }
+ moduleWithSameName = m.item;
+ }
+ return {nullptr, moduleWithSameName};
+ };
+ ProductContext *productDep = nullptr;
+ Item *moduleItem = nullptr;
+ static const auto displayName = [](const ProductContext &p) {
+ return ProductItemMultiplexer::fullProductDisplayName(p.name, p.multiplexConfigurationId);
+ };
+ const auto &[existingModule, moduleWithSameName] = findExistingModule(product.item);
+ if (existingModule) {
+ // Merge version range and required property. These will be checked again
+ // after probes resolving.
+ if (existingModule->name.toString() != StringConstants::qbsModule()) {
+ moduleLoader.forwardParameterDeclarations(dependency.item, product.item->modules());
+
+ // TODO: Use priorities like for property values. See QBS-1300.
+ mergeParameters(existingModule->parameters, dependency.parameters);
+
+ existingModule->versionRange.narrowDown(dependency.versionRange);
+ existingModule->required |= dependency.requiredGlobally;
+ if (int(product.resolveDependenciesState.size())
+ > existingModule->maxDependsChainLength) {
+ existingModule->maxDependsChainLength = product.resolveDependenciesState.size();
+ }
+ }
+ QBS_CHECK(existingModule->item);
+ moduleItem = existingModule->item;
+ if (!contains(existingModule->loadingItems, loadingItem))
+ existingModule->loadingItems.push_back(loadingItem);
+ } else if (dependency.product) {
+ // We have already done the look-up.
+ productDep = dependency.product;
+ } else {
+ // First check if there's a matching product.
+ const auto candidates = productsByName.equal_range(dependency.name.toString());
+ for (auto it = candidates.first; it != candidates.second; ++it) {
+ const auto candidate = it->second;
+ if (candidate->multiplexConfigurationId != dependency.multiplexId)
+ continue;
+ if (!dependency.profile.isEmpty() && dependency.profile != candidate->profileName)
+ continue;
+ if (dependency.limitToSubProject && !haveSameSubProject(product, *candidate))
+ continue;
+ productDep = candidate;
+ break;
+ }
+ if (!productDep) {
+ // If we can tell that this is supposed to be a product dependency, we can skip
+ // the module look-up.
+ if (!dependency.profile.isEmpty() || !dependency.multiplexId.isEmpty()) {
+ if (dependency.requiredGlobally) {
+ if (!dependency.profile.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
+ "for the requested profile '%3'.")
+ .arg(displayName(product), dependency.displayName(),
+ dependency.profile),
+ product.item->location());
+ }
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
+ .arg(displayName(product), dependency.displayName()),
+ product.item->location());
+ }
+ } else {
+ // No matching product found, look for a "real" module.
+ moduleItem = searchAndLoadModuleFile(&product, dependency.location(),
+ dependency.name, dependency.fallbackMode,
+ dependency.requiredGlobally);
+
+ if (moduleItem) {
+ Item * const proto = moduleItem;
+ moduleItem = moduleItem->clone();
+ moduleItem->setPrototype(proto); // For parameter declarations.
+ } else if (dependency.requiredGlobally) {
+ throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
+ .arg(dependency.name.toString(), displayName(product)),
+ dependency.location());
+ }
+ }
+ }
+ }
+
+ if (productDep && dependency.checkProduct) {
+ if (productDep == &product) {
+ throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.")
+ .arg(dependency.name.toString()),
+ dependency.location());
+ }
+
+ if (any_of(product.project->topLevelProject->productsToHandle, [productDep](const auto &e) {
+ return e.first == productDep;
+ })) {
+ if (deferral == Deferral::Allowed)
+ return {nullptr, productDep, HandleDependency::Defer};
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ e.append(Tr::tr("First product is '%1'.")
+ .arg(displayName(product)), product.item->location());
+ e.append(Tr::tr("Second product is '%1'.")
+ .arg(displayName(*productDep)), productDep->item->location());
+ e.append(Tr::tr("Requested here."), dependency.location());
+ throw e;
+ }
+
+ // This covers both the case of user-disabled products and products with errors.
+ // The latter are force-disabled in handleProductError().
+ if (disabledItems.contains(productDep->item)) {
+ if (dependency.requiredGlobally) {
+ ErrorInfo e;
+ e.append(Tr::tr("Product '%1' depends on '%2',")
+ .arg(displayName(product), displayName(*productDep)),
+ product.item->location());
+ e.append(Tr::tr("but product '%1' is disabled.").arg(displayName(*productDep)),
+ productDep->item->location());
+ throw e;
+ }
+ productDep = nullptr;
+ }
+ }
+
+ if (productDep) {
+ QBS_CHECK(productDep->mergedExportItem);
+ moduleItem = productDep->mergedExportItem->clone();
+ moduleItem->setParent(nullptr);
+
+ // Needed for isolated Export item evaluation.
+ moduleItem->setPrototype(productDep->mergedExportItem);
+ }
+
+ if (moduleItem) {
+ for (auto it = product.resolveDependenciesState.begin();
+ it != product.resolveDependenciesState.end(); ++it) {
+ Item *itemToCheck = moduleItem;
+ if (it->loadingItem != itemToCheck) {
+ if (!productDep)
+ continue;
+ itemToCheck = productDep->item;
+ }
+ if (it->loadingItem != itemToCheck)
+ continue;
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ while (true) {
+ e.append(it->loadingItemOrigin.name.toString(),
+ it->loadingItemOrigin.location());
+ if (it->loadingItem->type() == ItemType::ModuleInstance) {
+ createNonPresentModule(itemPool, it->loadingItemOrigin.name.toString(),
+ QLatin1String("cyclic dependency"), it->loadingItem);
+ }
+ if (it == product.resolveDependenciesState.begin())
+ break;
+ --it;
+ }
+ e.append(dependency.name.toString(), dependency.location());
+ throw e;
+ }
+ checkForModuleNamePrefixCollision(product.item, dependency);
+ }
+
+ // Can only happen with multiplexing.
+ if (moduleWithSameName && moduleWithSameName != moduleItem)
+ QBS_CHECK(productDep);
+
+ QString loadingName;
+ if (loadingItem == product.item) {
+ loadingName = product.name;
+ } else if (product.resolveDependenciesState.size() > 0) {
+ const auto &loadingItemOrigin = product.resolveDependenciesState.front().loadingItemOrigin;
+ loadingName = loadingItemOrigin.name.toString() + loadingItemOrigin.multiplexId
+ + loadingItemOrigin.profile;
+ }
+ moduleInstantiator.instantiate({
+ product.item, product.name, loadingItem, loadingName, moduleItem, moduleWithSameName,
+ productDep ? productDep->item : nullptr, product.scope, product.project->scope,
+ dependency.name, dependency.id(), bool(existingModule)});
+
+ // At this point, a null module item is only possible for a non-required dependency.
+ // Note that we still needed to to the instantiation above, as that injects the module
+ // name into the surrounding item for the ".present" check.
+ if (!moduleItem) {
+ QBS_CHECK(!dependency.requiredGlobally);
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ const auto createModule = [&] {
+ Item::Module m;
+ m.item = moduleItem;
+ if (productDep) {
+ m.productInfo.emplace(productDep->item, productDep->multiplexConfigurationId,
+ productDep->profileName);
+ }
+ m.name = dependency.name;
+ m.required = dependency.requiredLocally;
+ m.versionRange = dependency.versionRange;
+ return m;
+ };
+ const auto addLocalModule = [&] {
+ if (loadingItem->type() == ItemType::ModuleInstance
+ && !findExistingModule(loadingItem).first) {
+ loadingItem->addModule(createModule());
+ }
+ };
+
+ // The module has already been loaded, so we don't need to add it to the product's list of
+ // modules, nor do we need to handle its dependencies. The only thing we might need to
+ // do is to add it to the "local" list of modules of the loading item, if it isn't in there yet.
+ if (existingModule) {
+ addLocalModule();
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
+ if (product.item) {
+ Item::Module module = createModule();
+
+ 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 = product.resolveDependenciesState.size();
+ product.item->addModule(module);
+ addLocalModule();
+ }
+ return {moduleItem, nullptr, HandleDependency::Use};
+}
+
+bool ProjectTreeBuilder::Private::haveSameSubProject(const ProductContext &p1,
+ const ProductContext &p2)
+{
+ for (const Item *otherParent = p2.item->parent(); otherParent;
+ otherParent = otherParent->parent()) {
+ if (otherParent == p1.item->parent())
+ return true;
+ }
+ return false;
+}
+
+static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
+{
+ Item::PropertyMap result;
+ for (auto it = properties.begin(); it != properties.end(); ++it) {
+ if (it.value()->type() == Value::ItemValueType)
+ result.insert(it.key(), it.value());
+ }
+ return result;
+}
+
+static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
+{
+ QVariantMap result;
+ handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
+ const JSValue u = desc.value;
+ if (JS_IsError(ctx, u))
+ throw ErrorInfo(getJsString(ctx, u));
+ const QString name = getJsString(ctx, prop);
+ result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
+ ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
+ });
+ return result;
+}
+
+QVariantMap ProjectTreeBuilder::Private::extractParameters(Item *dependsItem) const
+{
+ QVariantMap result;
+ const Item::PropertyMap &itemProperties = filterItemProperties(dependsItem->properties());
+ if (itemProperties.empty())
+ return result;
+ auto origProperties = dependsItem->properties();
+
+ // TODO: This is not exception-safe. Also, can't we do the item value check along the
+ // way, without allocationg an extra map and exchanging the list of children?
+ dependsItem->setProperties(itemProperties);
+
+ JSValue sv = evaluator.scriptValue(dependsItem);
+ try {
+ result = safeToVariant(evaluator.engine()->context(), sv);
+ } catch (const ErrorInfo &exception) {
+ auto ei = exception;
+ ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
+ throw ei;
+ }
+ dependsItem->setProperties(origProperties);
+ return result;
+}
+
+// TODO Move the search & load stuff into ModuleLoader once we untangled the data types
+struct PrioritizedItem
+{
+ PrioritizedItem(Item *item, int priority, int searchPathIndex)
+ : item(item), priority(priority), searchPathIndex(searchPathIndex) { }
+
+ Item * const item;
+ int priority = 0;
+ const int searchPathIndex;
+};
+
+static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates,
+ const QString &moduleName)
+{
+ // TODO: This should also consider the version requirement.
+
+ auto maxIt = std::max_element(
+ candidates.begin(), candidates.end(),
+ [] (const PrioritizedItem &a, const PrioritizedItem &b) {
+ if (a.priority < b.priority)
+ return true;
+ if (a.priority > b.priority)
+ return false;
+ return a.searchPathIndex > b.searchPathIndex;
+ });
+
+ size_t nmax = std::count_if(
+ candidates.begin(), candidates.end(),
+ [maxIt] (const PrioritizedItem &i) {
+ return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex;
+ });
+
+ if (nmax > 1) {
+ ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.")
+ .arg(moduleName));
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ const auto candidate = candidates.at(i);
+ if (candidate.priority == maxIt->priority) {
+ //: The %1 denotes the number of the candidate.
+ e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location());
+ }
+ }
+ throw e;
+ }
+
+ return maxIt->item;
+}
+
+Item *ProjectTreeBuilder::Private::searchAndLoadModuleFile(
+ ProductContext *productContext, const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName, FallbackMode fallbackMode, bool isRequired)
+{
+ const auto findExistingModulePath = [&](const QString &searchPath) {
+ // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the
+ // modules and search paths we've already processed
+ auto &moduleInfo = existingModulePathCache[{searchPath, moduleName}];
+ if (moduleInfo)
+ return *moduleInfo;
+
+ QString dirPath = searchPath + QStringLiteral("/modules");
+ for (const QString &moduleNamePart : moduleName) {
+ dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
+ if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) {
+ return *(moduleInfo = QString());
+ }
+ }
+
+ return *(moduleInfo = dirPath);
+ };
+ const auto findExistingModulePaths = [&] {
+ const QStringList &searchPaths = reader.allSearchPaths();
+ QStringList result;
+ result.reserve(searchPaths.size());
+ for (const auto &path: searchPaths) {
+ const QString dirPath = findExistingModulePath(path);
+ if (!dirPath.isEmpty())
+ result.append(dirPath);
+ }
+ return result;
+ };
+
+ auto existingPaths = findExistingModulePaths();
+ if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
+ AccumulatingTimer providersTimer(
+ parameters.logElapsedTime() ? &timingData.moduleProviders : nullptr);
+ auto result = moduleProviderLoader.executeModuleProviders(
+ *productContext,
+ dependsItemLocation,
+ moduleName,
+ fallbackMode);
+ if (result.providerAddedSearchPaths) {
+ qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
+ << "with newly added search paths from module provider";
+ existingPaths = findExistingModulePaths();
+ }
+ }
+
+ const auto getModuleFileNames = [&](const QString &dirPath) -> QStringList & {
+ QStringList &moduleFileNames = moduleDirListCache[dirPath];
+ if (moduleFileNames.empty()) {
+ QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
+ while (dirIter.hasNext())
+ moduleFileNames += dirIter.next();
+ }
+ return moduleFileNames;
+ };
+
+ const QString fullName = moduleName.toString();
+ bool triedToLoadModule = false;
+ std::vector<PrioritizedItem> candidates;
+ candidates.reserve(size_t(existingPaths.size()));
+ for (int i = 0; i < existingPaths.size(); ++i) {
+ const QString &dirPath = existingPaths.at(i);
+ QStringList &moduleFileNames = getModuleFileNames(dirPath);
+ for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) {
+ const QString &filePath = *it;
+ const auto [module, triedToLoad] = moduleLoader.loadModuleFile(
+ {productContext->item, productContext->name, productContext->profileName,
+ productContext->profileModuleProperties},
+ fullName, filePath);
+ if (module)
+ candidates.emplace_back(module, 0, i);
+ if (!triedToLoad)
+ it = moduleFileNames.erase(it);
+ else
+ ++it;
+ triedToLoadModule = triedToLoadModule || triedToLoad;
+ }
+ }
+
+ if (candidates.empty()) {
+ if (!isRequired)
+ return createNonPresentModule(itemPool, fullName, QStringLiteral("not found"), nullptr);
+ if (Q_UNLIKELY(triedToLoadModule)) {
+ throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName),
+ dependsItemLocation);
+ }
+ return nullptr;
+ }
+
+ Item *moduleItem;
+ if (candidates.size() == 1) {
+ moduleItem = candidates.at(0).item;
+ } else {
+ for (auto &candidate : candidates) {
+ candidate.priority = evaluator.intValue(candidate.item,
+ StringConstants::priorityProperty(),
+ candidate.priority);
+ }
+ moduleItem = chooseModuleCandidate(candidates, fullName);
+ }
+
+ const QString fullProductName = ProductItemMultiplexer::fullProductDisplayName(
+ productContext->name, productContext->multiplexConfigurationId);
+ moduleLoader.checkProfileErrorsForModule(moduleItem, fullName, fullProductName,
+ productContext->profileName);
+ return moduleItem;
+}
+
+std::optional<ProductContext::ResolvedDependsItem>
+ProjectTreeBuilder::Private::resolveDependsItem(const ProductContext &product, Item *dependsItem)
+{
+ if (!checkItemCondition(dependsItem)) {
+ qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
+ return {};
+ }
+
+ const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
+ if (name == StringConstants::qbsModule()) // Redundant
+ return {};
+
+ bool submodulesPropertySet;
+ const QStringList submodules = evaluator.stringListValue(
+ dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet);
+ if (submodules.empty() && submodulesPropertySet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
+ return {};
+ }
+ if (Q_UNLIKELY(submodules.size() > 1 && !dependsItem->id().isEmpty())) {
+ QString msg = Tr::tr("A Depends item with more than one module cannot have an id.");
+ throw ErrorInfo(msg, dependsItem->location());
+ }
+ bool productTypesWasSet;
+ const QStringList productTypes = evaluator.stringListValue(
+ dependsItem, StringConstants::productTypesProperty(), &productTypesWasSet);
+ if (!name.isEmpty() && !productTypes.isEmpty()) {
+ throw ErrorInfo(Tr::tr("The name and productTypes are mutually exclusive "
+ "in a Depends item"), dependsItem->location());
+ }
+ if (productTypes.isEmpty() && productTypesWasSet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list.";
+ return {};
+ }
+ if (name.isEmpty() && !productTypesWasSet) {
+ throw ErrorInfo(Tr::tr("Either name or productTypes must be set in a Depends item"),
+ dependsItem->location());
+ }
+
+ const FallbackMode fallbackMode = parameters.fallbackProviderEnabled()
+ && evaluator.boolValue(dependsItem, StringConstants::enableFallbackProperty())
+ ? FallbackMode::Enabled : FallbackMode::Disabled;
+
+ bool profilesPropertyWasSet = false;
+ std::optional<QStringList> profiles;
+ bool required = true;
+ if (productTypes.isEmpty()) {
+ const QStringList profileList = evaluator.stringListValue(
+ dependsItem, StringConstants::profilesProperty(), &profilesPropertyWasSet);
+ if (profilesPropertyWasSet)
+ profiles.emplace(profileList);
+ required = evaluator.boolValue(dependsItem, StringConstants::requiredProperty());
+ }
+ const Version minVersion = Version::fromString(
+ evaluator.stringValue(dependsItem, StringConstants::versionAtLeastProperty()));
+ const Version maxVersion = Version::fromString(
+ evaluator.stringValue(dependsItem, StringConstants::versionBelowProperty()));
+ const bool limitToSubProject = evaluator.boolValue(
+ dependsItem, StringConstants::limitToSubProjectProperty());
+ const QStringList multiplexIds = evaluator.stringListValue(
+ dependsItem, StringConstants::multiplexConfigurationIdsProperty());
+ adjustParametersScopes(dependsItem, dependsItem);
+ moduleLoader.forwardParameterDeclarations(dependsItem, product.item->modules());
+ const QVariantMap parameters = extractParameters(dependsItem);
+
+ return ProductContext::ResolvedDependsItem{dependsItem, QualifiedId::fromString(name),
+ submodules, FileTags::fromStringList(productTypes), multiplexIds, profiles,
+ {minVersion, maxVersion}, parameters, limitToSubProject, fallbackMode, required};
+}
+
+// Potentially multiplexes a dependency along Depends.productTypes, Depends.subModules and
+// Depends.profiles, as well as internally set up multiplexing axes.
+// Each entry in the resulting queue corresponds to exactly one product or module to pull in.
+std::queue<ProductContext::ResolvedAndMultiplexedDependsItem>
+ProjectTreeBuilder::Private::multiplexDependency(
+ const ProductContext &product, const ProductContext::ResolvedDependsItem &dependency)
+{
+ std::queue<ProductContext::ResolvedAndMultiplexedDependsItem> dependencies;
+ if (!dependency.productTypes.empty()) {
+ std::vector<ProductContext *> matchingProducts;
+ for (const FileTag &typeTag : dependency.productTypes) {
+ const auto range = productsByType.equal_range(typeTag);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second != &product
+ && it->second->name != product.name
+ && (!dependency.limitToSubProject
+ || haveSameSubProject(product, *it->second))) {
+ matchingProducts.push_back(it->second);
+ }
+ }
+ }
+ if (matchingProducts.empty()) {
+ qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
+ << dependency.item->location();
+ return {};
+ }
+ for (ProductContext * const match : matchingProducts)
+ dependencies.emplace(match, dependency);
+ return dependencies;
+ }
+
+ const QStringList profiles = dependency.profiles && !dependency.profiles->isEmpty()
+ ? *dependency.profiles : QStringList(QString());
+ const QStringList multiplexIds = !dependency.multiplexIds.isEmpty()
+ ? dependency.multiplexIds : QStringList(QString());
+ const QStringList subModules = !dependency.subModules.isEmpty()
+ ? dependency.subModules : QStringList(QString());
+ for (const QString &profile : profiles) {
+ for (const QString &multiplexId : multiplexIds) {
+ for (const QString &subModule : subModules) {
+ QualifiedId name = dependency.name;
+ if (!subModule.isEmpty())
+ name << subModule.split(QLatin1Char('.'));
+ dependencies.emplace(dependency, name, profile, multiplexId);
+ }
+ }
+ }
+ return dependencies;
+}
+
+SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths)
+ : SearchPathsManager(itemReader, nullptr, extraSearchPaths) {}
+
+SearchPathsManager::SearchPathsManager(ItemReader &itemReader, ProductContext &product)
+ : SearchPathsManager(itemReader, &product, product.searchPaths) {}
+
+SearchPathsManager::SearchPathsManager(ItemReader &itemReader, ProductContext *product,
+ const QStringList &extraSearchPaths)
+ : m_itemReader(itemReader), m_product(product),
+ m_oldSize(itemReader.extraSearchPathsStack().size())
+{
+ if (!extraSearchPaths.isEmpty())
+ m_itemReader.pushExtraSearchPaths(extraSearchPaths);
+}
+
+SearchPathsManager::~SearchPathsManager()
+{
+ while (m_itemReader.extraSearchPathsStack().size() > m_oldSize) {
+ if (m_product && m_itemReader.extraSearchPathsStack().size() > m_oldSize + 1) {
+ for (const QString &pathFromProvider : m_itemReader.extraSearchPathsStack().back())
+ m_product->searchPaths.prepend(pathFromProvider);
+ }
+ m_itemReader.popExtraSearchPaths();
+ }
+}
+
+QString ProductContext::uniqueName() const
+{
+ return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
+}
+
+QString ProductContext::ResolvedAndMultiplexedDependsItem::id() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->id();
+}
+
+CodeLocation ProductContext::ResolvedAndMultiplexedDependsItem::location() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->location();
+}
+
+QString ProductContext::ResolvedAndMultiplexedDependsItem::displayName() const
+{
+ return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
+}
+
+ProjectTreeBuilder::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
+ ProjectTreeBuilder::Private *d, ProductContext &product)
+ : m_productItem(product.item)
+{
+ const ValuePtr qbsValue = m_productItem->property(StringConstants::qbsModule());
+
+ // Cloning is necessary because the original value will get "instantiated" now.
+ if (qbsValue)
+ m_origQbsValue = qbsValue->clone();
+
+ m_tempBaseModule = d->loadBaseModule(product, m_productItem);
+}
+
+void ProjectTreeBuilder::Private::TempBaseModuleAttacher::drop()
+{
+ if (!m_tempBaseModule)
+ return;
+
+ // "Unload" the qbs module again.
+ if (m_origQbsValue)
+ m_productItem->setProperty(StringConstants::qbsModule(), m_origQbsValue);
+ else
+ m_productItem->removeProperty(StringConstants::qbsModule());
+ m_productItem->removeModules();
+ m_tempBaseModule = nullptr;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/projecttreebuilder.h b/src/lib/corelib/language/projecttreebuilder.h
new file mode 100644
index 000000000..db2d621c5
--- /dev/null
+++ b/src/lib/corelib/language/projecttreebuilder.h
@@ -0,0 +1,227 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include "forward_decls.h"
+#include "filetags.h"
+#include "moduleproviderinfo.h"
+#include "qualifiedid.h"
+
+#include <tools/filetime.h>
+#include <tools/stringconstants.h>
+#include <tools/version.h>
+
+#include <QString>
+#include <QVariant>
+
+#include <optional>
+#include <queue>
+#include <stack>
+
+namespace qbs {
+class SetupProjectParameters;
+namespace Internal {
+class Evaluator;
+class FileTime;
+class Item;
+class ItemPool;
+class ProgressObserver;
+
+class ContextBase
+{
+public:
+ Item *item = nullptr;
+ Item *scope = nullptr;
+ QString name;
+};
+
+class ProjectContext;
+
+enum class FallbackMode { Enabled, Disabled };
+
+using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
+
+// TODO: This class only needs to be known inside the ProjectResolver; no need to
+// instantiate them separately when they always appear together.
+// Possibly we can get rid of the Loader class altogether.
+class ProjectTreeBuilder
+{
+public:
+ ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ Evaluator &evaluator, Logger &logger);
+ ~ProjectTreeBuilder();
+
+ struct Result
+ {
+ struct ProductInfo
+ {
+ std::vector<ProbeConstPtr> probes;
+ ModulePropertiesPerGroup modulePropertiesSetInGroups;
+ ErrorInfo delayedError;
+ };
+
+ Item *root = nullptr;
+ std::unordered_map<Item *, ProductInfo> productInfos;
+ std::vector<ProbeConstPtr> projectProbes;
+ StoredModuleProviderInfo storedModuleProviderInfo;
+ Set<QString> qbsFiles;
+ QVariantMap profileConfigs;
+ };
+ Result load();
+
+ void setProgressObserver(ProgressObserver *progressObserver);
+ void setSearchPaths(const QStringList &searchPaths);
+ void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
+ void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
+ void setLastResolveTime(const FileTime &time);
+ void setStoredProfiles(const QVariantMap &profiles);
+ void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
+
+private:
+ class Private;
+ Private * const d;
+};
+
+class ProductContext : public ContextBase
+{
+public:
+ ProjectContext *project = nullptr;
+ Item *mergedExportItem = nullptr;
+ ProjectTreeBuilder::Result::ProductInfo info;
+ QString profileName;
+ QString multiplexConfigurationId;
+ QVariantMap profileModuleProperties; // Tree-ified module properties from profile.
+ QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values.
+ QVariantMap defaultParameters; // In Export item.
+ QStringList searchPaths;
+
+ std::optional<QVariantMap> theModuleProviderConfig;
+
+ struct ResolvedDependsItem {
+ Item *item = nullptr;
+ QualifiedId name;
+ QStringList subModules;
+ FileTags productTypes;
+ QStringList multiplexIds;
+ std::optional<QStringList> profiles;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+ };
+ struct ResolvedAndMultiplexedDependsItem {
+ ResolvedAndMultiplexedDependsItem(ProductContext *product,
+ const ResolvedDependsItem &dependency)
+ : product(product), item(dependency.item), name(product->name),
+ versionRange(dependency.versionRange), parameters(dependency.parameters),
+ fallbackMode(FallbackMode::Disabled), checkProduct(false) {}
+ ResolvedAndMultiplexedDependsItem(const ResolvedDependsItem &dependency,
+ const QualifiedId &name,
+ const QString &profile, const QString &multiplexId)
+ : item(dependency.item), name(name), profile(profile), multiplexId(multiplexId),
+ versionRange(dependency.versionRange), parameters(dependency.parameters),
+ limitToSubProject(dependency.limitToSubProject), fallbackMode(dependency.fallbackMode),
+ requiredLocally(dependency.requiredLocally),
+ requiredGlobally(dependency.requiredGlobally) {}
+ ResolvedAndMultiplexedDependsItem() = default;
+ static ResolvedAndMultiplexedDependsItem makeBaseDependency() {
+ ResolvedAndMultiplexedDependsItem item;
+ item.fallbackMode = FallbackMode::Disabled;
+ item.name = StringConstants::qbsModule();
+ return item;
+ }
+
+ QString id() const;
+ CodeLocation location() const;
+ QString displayName() const;
+
+ ProductContext *product = nullptr;
+ Item *item = nullptr;
+ QualifiedId name;
+ QString profile;
+ QString multiplexId;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+ bool checkProduct = true;
+ };
+ struct DependenciesResolvingState {
+ Item *loadingItem = nullptr;
+ ResolvedAndMultiplexedDependsItem loadingItemOrigin;
+ std::queue<Item *> pendingDependsItems;
+ std::optional<ResolvedDependsItem> currentDependsItem;
+ std::queue<ResolvedAndMultiplexedDependsItem> pendingResolvedDependencies;
+ bool requiredByLoadingItem = true;
+ };
+ std::list<DependenciesResolvingState> resolveDependenciesState;
+
+ QString uniqueName() const;
+};
+
+class TopLevelProjectContext
+{
+public:
+ TopLevelProjectContext() = default;
+ TopLevelProjectContext(const TopLevelProjectContext &) = delete;
+ TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
+ ~TopLevelProjectContext() { qDeleteAll(projects); }
+
+ std::vector<ProjectContext *> projects;
+ std::list<std::pair<ProductContext *, int>> productsToHandle;
+ std::vector<ProbeConstPtr> probes;
+ QString buildDirectory;
+};
+
+class ProjectContext : public ContextBase
+{
+public:
+ TopLevelProjectContext *topLevelProject = nullptr;
+ ProjectTreeBuilder::Result *result = nullptr;
+ std::vector<ProductContext> products;
+ std::vector<QStringList> searchPathsStack;
+};
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp
index f2ac019e9..215918462 100644
--- a/src/lib/corelib/language/propertydeclaration.cpp
+++ b/src/lib/corelib/language/propertydeclaration.cpp
@@ -40,11 +40,15 @@
#include "propertydeclaration.h"
#include "deprecationinfo.h"
-#include <api/languageinfo.h>
+#include "filecontext.h"
+#include "item.h"
+#include "qualifiedid.h"
+#include "value.h"
+#include <api/languageinfo.h>
#include <logging/translator.h>
-
#include <tools/error.h>
+#include <tools/setupprojectparameters.h>
#include <tools/qttools.h>
#include <tools/stringconstants.h>
@@ -304,5 +308,142 @@ QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t,
return c;
}
+namespace {
+class PropertyDeclarationCheck : public ValueHandler
+{
+public:
+ PropertyDeclarationCheck(const Set<Item *> &disabledItems,
+ const SetupProjectParameters &params, Logger &logger)
+ : m_disabledItems(disabledItems)
+ , m_params(params)
+ , m_logger(logger)
+ { }
+ void operator()(Item *item) { handleItem(item); }
+
+private:
+ void handle(JSSourceValue *value) override
+ {
+ if (!value->createdByPropertiesBlock()) {
+ const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
+ .arg(m_currentName), value->location());
+ handlePropertyError(error, m_params, m_logger);
+ }
+ }
+ void handle(ItemValue *value) override
+ {
+ if (checkItemValue(value))
+ handleItem(value->item());
+ }
+ bool checkItemValue(ItemValue *value)
+ {
+ // TODO: Remove once QBS-1030 is fixed.
+ if (parentItem()->type() == ItemType::Artifact)
+ return false;
+
+ if (parentItem()->type() == ItemType::Properties)
+ return false;
+
+ // TODO: Check where the in-between module instances come from.
+ if (value->item()->type() == ItemType::ModuleInstancePlaceholder) {
+ for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) {
+ if ((*it)->type() == ItemType::Group)
+ return false;
+ if ((*it)->type() == ItemType::ModulePrefix)
+ continue;
+ break;
+ }
+ }
+
+ if (value->item()->type() != ItemType::ModuleInstance
+ && value->item()->type() != ItemType::ModulePrefix
+ && (!parentItem()->file() || !parentItem()->file()->idScope()
+ || !parentItem()->file()->idScope()->hasProperty(m_currentName))
+ && !value->createdByPropertiesBlock()) {
+ CodeLocation location = value->location();
+ for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i)
+ location = m_parentItems.at(i)->location();
+ const ErrorInfo error(Tr::tr("Item '%1' is not declared. "
+ "Did you forget to add a Depends item?")
+ .arg(m_currentModuleName.toString()), location);
+ handlePropertyError(error, m_params, m_logger);
+ return false;
+ }
+
+ return true;
+ }
+ void handleItem(Item *item)
+ {
+ if (!m_handledItems.insert(item).second)
+ return;
+ if (m_disabledItems.contains(item)
+ || item->type() == ItemType::Module
+ || item->type() == ItemType::Export
+ || (item->type() == ItemType::ModuleInstance && !item->isPresentModule())
+ || item->type() == ItemType::Properties
+
+ // The Properties child of a SubProject item is not a regular item.
+ || item->type() == ItemType::PropertiesInSubProject) {
+ return;
+ }
+
+ m_parentItems.push_back(item);
+ for (Item::PropertyMap::const_iterator it = item->properties().constBegin();
+ it != item->properties().constEnd(); ++it) {
+ if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders()
+ && it.value()->type() == Value::ItemValueType)
+ continue;
+ const PropertyDeclaration decl = item->propertyDeclaration(it.key());
+ if (decl.isValid()) {
+ const ErrorInfo deprecationError = decl.checkForDeprecation(
+ m_params.deprecationWarningMode(), it.value()->location(), m_logger);
+ if (deprecationError.hasError())
+ handlePropertyError(deprecationError, m_params, m_logger);
+ continue;
+ }
+ m_currentName = it.key();
+ const QualifiedId oldModuleName = m_currentModuleName;
+ if (parentItem()->type() != ItemType::ModulePrefix)
+ m_currentModuleName.clear();
+ m_currentModuleName.push_back(m_currentName);
+ it.value()->apply(this);
+ m_currentModuleName = oldModuleName;
+ }
+ m_parentItems.pop_back();
+ for (Item * const child : item->children()) {
+ switch (child->type()) {
+ case ItemType::Export:
+ case ItemType::Depends:
+ case ItemType::Parameter:
+ case ItemType::Parameters:
+ break;
+ case ItemType::Group:
+ if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance)
+ break;
+ Q_FALLTHROUGH();
+ default:
+ handleItem(child);
+ }
+ }
+ }
+ void handle(VariantValue *) override { /* only created internally - no need to check */ }
+
+ Item *parentItem() const { return m_parentItems.back(); }
+
+ const Set<Item *> &m_disabledItems;
+ Set<Item *> m_handledItems;
+ std::vector<Item *> m_parentItems;
+ QualifiedId m_currentModuleName;
+ QString m_currentName;
+ const SetupProjectParameters &m_params;
+ Logger &m_logger;
+};
+} // namespace
+
+void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
+ const SetupProjectParameters &params, Logger &logger)
+{
+ PropertyDeclarationCheck(disabledItems, params, logger)(topLevelItem);
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h
index d1e114296..8c87faedb 100644
--- a/src/lib/corelib/language/propertydeclaration.h
+++ b/src/lib/corelib/language/propertydeclaration.h
@@ -41,6 +41,7 @@
#define QBS_PROPERTYDECLARATION_H
#include <tools/deprecationwarningmode.h>
+#include <tools/set.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
@@ -52,9 +53,11 @@ QT_END_NAMESPACE
namespace qbs {
class CodeLocation;
class ErrorInfo;
+class SetupProjectParameters;
namespace Internal {
class DeprecationInfo;
class PropertyDeclarationData;
+class Item;
class Logger;
class PropertyDeclaration
@@ -131,6 +134,10 @@ private:
QSharedDataPointer<PropertyDeclarationData> d;
};
+void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
+ const SetupProjectParameters &params, Logger &logger);
+
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp
index 5a4da2c8f..7ba9ff384 100644
--- a/src/lib/corelib/language/value.cpp
+++ b/src/lib/corelib/language/value.cpp
@@ -48,29 +48,63 @@
namespace qbs {
namespace Internal {
-Value::Value(Type t, bool createdByPropertiesBlock)
- : m_type(t), m_definingItem(nullptr), m_createdByPropertiesBlock(createdByPropertiesBlock)
+Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t)
{
+ if (createdByPropertiesBlock)
+ m_flags |= OriginPropertiesBlock;
}
Value::Value(const Value &other)
: m_type(other.m_type),
- m_definingItem(other.m_definingItem),
+ m_scope(other.m_scope),
m_next(other.m_next ? other.m_next->clone() : ValuePtr()),
- m_createdByPropertiesBlock(other.m_createdByPropertiesBlock)
+ m_flags(other.m_flags)
{
}
Value::~Value() = default;
-Item *Value::definingItem() const
+void Value::setScope(Item *scope, const QString &scopeName)
{
- return m_definingItem;
+ m_scope = scope;
+ m_scopeName = scopeName;
}
-void Value::setDefiningItem(Item *item)
+int Value::priority(const Item *productItem) const
{
- m_definingItem = item;
+ if (m_priority == -1)
+ m_priority = calculatePriority(productItem);
+ return m_priority;
+}
+
+int Value::calculatePriority(const Item *productItem) const
+{
+ if (setInternally())
+ return INT_MAX;
+ if (setByCommandLine())
+ return INT_MAX - 1;
+ if (setByProfile())
+ return 2;
+ if (!scope())
+ return 1;
+ if (scope()->type() == ItemType::Product)
+ return INT_MAX - 2;
+ if (!scope()->isPresentModule())
+ return 0;
+ const auto it = std::find_if(
+ productItem->modules().begin(), productItem->modules().end(),
+ [this](const Item::Module &m) { return m.item == scope(); });
+ QBS_CHECK(it != productItem->modules().end());
+ return INT_MAX - 3 - it->maxDependsChainLength;
+}
+
+void Value::resetPriority()
+{
+ m_priority = -1;
+ if (m_next)
+ m_next->resetPriority();
+ for (const ValuePtr &v : m_candidates)
+ v->resetPriority();
}
ValuePtr Value::next() const
@@ -85,6 +119,11 @@ void Value::setNext(const ValuePtr &next)
m_next = next;
}
+bool Value::setInternally() const
+{
+ return type() == VariantValueType && !setByProfile() && !setByCommandLine();
+}
+
JSSourceValue::JSSourceValue(bool createdByPropertiesBlock)
: Value(JSSourceValueType, createdByPropertiesBlock)
@@ -99,7 +138,6 @@ JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other)
m_line = other.m_line;
m_column = other.m_column;
m_file = other.m_file;
- m_flags = other.m_flags;
m_baseValue = other.m_baseValue
? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone())
: JSSourceValuePtr();
@@ -142,24 +180,45 @@ CodeLocation JSSourceValue::location() const
return CodeLocation(m_file->filePath(), m_line, m_column);
}
-void JSSourceValue::setHasFunctionForm(bool b)
+void JSSourceValue::clearAlternatives()
+{
+ m_alternatives.clear();
+}
+
+void JSSourceValue::setScope(Item *scope, const QString &scopeName)
{
- if (b)
- m_flags |= HasFunctionForm;
- else
- m_flags &= ~HasFunctionForm;
+ Value::setScope(scope, scopeName);
+ if (m_baseValue)
+ m_baseValue->setScope(scope, scopeName);
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->setScope(scope, scopeName);
}
-void JSSourceValue::clearAlternatives()
+void JSSourceValue::resetPriority()
{
- m_alternatives.clear();
+ Value::resetPriority();
+ if (m_baseValue)
+ m_baseValue->resetPriority();
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->resetPriority();
+}
+
+void JSSourceValue::addCandidate(const ValuePtr &v)
+{
+ Value::addCandidate(v);
+ if (m_baseValue)
+ m_baseValue->addCandidate(v);
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->addCandidate(v);
}
-void JSSourceValue::setDefiningItem(Item *item)
+void JSSourceValue::setCandidates(const std::vector<ValuePtr> &candidates)
{
- Value::setDefiningItem(item);
+ Value::setCandidates(candidates);
+ if (m_baseValue)
+ m_baseValue->setCandidates(candidates);
for (const JSSourceValue::Alternative &a : m_alternatives)
- a.value->setDefiningItem(item);
+ a.value->setCandidates(candidates);
}
ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock)
diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h
index 5f6b5ca16..262813841 100644
--- a/src/lib/corelib/language/value.h
+++ b/src/lib/corelib/language/value.h
@@ -42,6 +42,7 @@
#include "forward_decls.h"
#include <tools/codelocation.h>
+#include <QtCore/qstring.h>
#include <QtCore/qvariant.h>
#include <vector>
@@ -54,13 +55,26 @@ class ValueHandler;
class Value
{
public:
- enum Type
- {
+ enum Type {
JSSourceValueType,
ItemValueType,
VariantValueType
};
+ enum Flag {
+ NoFlags = 0x00,
+ SourceUsesBase = 0x01,
+ SourceUsesOuter = 0x02,
+ SourceUsesOriginal = 0x04,
+ HasFunctionForm = 0x08,
+ ExclusiveListValue = 0x10,
+ BuiltinDefaultValue = 0x20,
+ OriginPropertiesBlock = 0x40,
+ OriginProfile = 0x80,
+ OriginCommandLine = 0x100,
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
Value(Type t, bool createdByPropertiesBlock);
Value(const Value &other);
virtual ~Value();
@@ -70,21 +84,50 @@ public:
virtual ValuePtr clone() const = 0;
virtual CodeLocation location() const { return {}; }
- Item *definingItem() const;
- virtual void setDefiningItem(Item *item);
+ Item *scope() const { return m_scope; }
+ virtual void setScope(Item *scope, const QString &scopeName);
+ QString scopeName() const { return m_scopeName; }
+ int priority(const Item *productItem) const;
+ virtual void resetPriority();
ValuePtr next() const;
void setNext(const ValuePtr &next);
- bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; }
- void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; }
- void clearCreatedByPropertiesBlock() { m_createdByPropertiesBlock = false; }
+ virtual void addCandidate(const ValuePtr &v) { m_candidates.push_back(v); }
+ const std::vector<ValuePtr> &candidates() const { return m_candidates; }
+ virtual void setCandidates(const std::vector<ValuePtr> &candidates) { m_candidates = candidates; }
+
+ bool createdByPropertiesBlock() const { return m_flags & OriginPropertiesBlock; }
+ void markAsSetByProfile() { m_flags |= OriginProfile; }
+ bool setByProfile() const { return m_flags & OriginProfile; }
+ void markAsSetByCommandLine() { m_flags |= OriginCommandLine; }
+ bool setByCommandLine() const { return m_flags & OriginCommandLine; }
+ bool setInternally() const;
+ bool expired(const Item *productItem) const { return priority(productItem) == 0; }
+
+ void setSourceUsesBase() { m_flags |= SourceUsesBase; }
+ bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); }
+ void setSourceUsesOuter() { m_flags |= SourceUsesOuter; }
+ bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); }
+ void setSourceUsesOriginal() { m_flags |= SourceUsesOriginal; }
+ bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); }
+ void setHasFunctionForm() { m_flags |= HasFunctionForm; }
+ bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); }
+ void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; }
+ bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); }
+ void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; }
+ bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); }
private:
+ int calculatePriority(const Item *productItem) const;
+
Type m_type;
- Item *m_definingItem;
+ Item *m_scope = nullptr;
+ QString m_scopeName;
ValuePtr m_next;
- bool m_createdByPropertiesBlock;
+ std::vector<ValuePtr> m_candidates;
+ Flags m_flags;
+ mutable int m_priority = -1;
};
class ValueHandler
@@ -99,18 +142,6 @@ class JSSourceValue : public Value
{
friend class ItemReaderASTVisitor;
- enum Flag
- {
- NoFlags = 0x00,
- SourceUsesBase = 0x01,
- SourceUsesOuter = 0x02,
- SourceUsesOriginal = 0x04,
- HasFunctionForm = 0x08,
- ExclusiveListValue = 0x10,
- BuiltinDefaultValue = 0x20,
- };
- Q_DECLARE_FLAGS(Flags, Flag)
-
public:
explicit JSSourceValue(bool createdByPropertiesBlock);
JSSourceValue(const JSSourceValue &other);
@@ -133,17 +164,6 @@ public:
void setFile(const FileContextPtr &file) { m_file = file; }
const FileContextPtr &file() const { return m_file; }
- void setSourceUsesBaseFlag() { m_flags |= SourceUsesBase; }
- bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); }
- bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); }
- bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); }
- bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); }
- void setHasFunctionForm(bool b);
- void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; }
- bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); }
- void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; }
- bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); }
-
const JSSourceValuePtr &baseValue() const { return m_baseValue; }
void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; }
@@ -176,14 +196,16 @@ public:
void addAlternative(const Alternative &alternative) { m_alternatives.push_back(alternative); }
void clearAlternatives();
- void setDefiningItem(Item *item) override;
+ void setScope(Item *scope, const QString &scopeName) override;
+ void resetPriority() override;
+ void addCandidate(const ValuePtr &v) override;
+ void setCandidates(const std::vector<ValuePtr> &candidates) override;
private:
QStringView m_sourceCode;
int m_line;
int m_column;
FileContextPtr m_file;
- Flags m_flags;
JSSourceValuePtr m_baseValue;
std::vector<Alternative> m_alternatives;
};
diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp
index 1940b19de..cf5903349 100644
--- a/src/lib/corelib/tools/persistence.cpp
+++ b/src/lib/corelib/tools/persistence.cpp
@@ -48,7 +48,7 @@
namespace qbs {
namespace Internal {
-static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-131";
+static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-132";
NoBuildGraphError::NoBuildGraphError(const QString &filePath)
: ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.")
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 28bbe4638..d85b2502a 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -1434,7 +1434,7 @@ void TestApi::infiniteLoopResolving()
m_logSink, nullptr));
QTimer::singleShot(1000, setupJob.get(), &qbs::AbstractJob::cancel);
QVERIFY(waitForFinished(setupJob.get(), testTimeoutInMsecs()));
- QVERIFY2(setupJob->error().toString().toLower().contains("interrupted"),
+ QVERIFY2(setupJob->error().toString().toLower().contains("cancel"),
qPrintable(setupJob->error().toString()));
}
@@ -2711,12 +2711,13 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 2);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 3);
const auto beforeErrors = m_logSink->warnings;
for (const qbs::ErrorInfo &e : beforeErrors) {
const QString msg = e.toString();
QVERIFY2(msg.contains("Superfluous version")
- || msg.contains("Property 'blubb' is not declared"),
+ || msg.contains("Property 'blubb' is not declared")
+ || msg.contains("Product 'theProduct' had errors and was disabled"),
qPrintable(msg));
}
m_logSink->warnings.clear();
@@ -2726,7 +2727,7 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 2);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 3);
m_logSink->warnings.clear();
// Re-resolving with changes: Errors come from the re-resolving, stored ones must be suppressed.
@@ -2737,13 +2738,14 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 3); // One more for the additional group
+ QCOMPARE(toSet(m_logSink->warnings).size(), 4); // One more for the additional group
const auto afterErrors = m_logSink->warnings;
for (const qbs::ErrorInfo &e : afterErrors) {
const QString msg = e.toString();
QVERIFY2(msg.contains("Superfluous version")
|| msg.contains("Property 'blubb' is not declared")
- || msg.contains("blubb.cpp' does not exist"),
+ || msg.contains("blubb.cpp' does not exist")
+ || msg.contains("Product 'theProduct' had errors and was disabled"),
qPrintable(msg));
}
m_logSink->warnings.clear();
diff --git a/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs b/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs
new file mode 100644
index 000000000..23b6ee5a3
--- /dev/null
+++ b/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs
@@ -0,0 +1,41 @@
+Project {
+ Product {
+ name: "low"
+ Export { property string prop: "low"; property string prop2: "low" }
+ }
+ Product {
+ name: "higher1"
+ Export { Depends { name: "low" } low.prop: "higher1" }
+ }
+ Product {
+ name: "higher2"
+ Export { Depends { name: "low" } low.prop: "higher2" }
+ }
+ Product {
+ name: "highest1"
+ Export {
+ Depends { name: "low" }
+ Depends { name: "higher1" }
+ Depends { name: "higher2" }
+ low.prop: "highest1"
+ low.prop2: "highest"
+ }
+ }
+ Product {
+ name: "highest2"
+ Export {
+ Depends { name: "low" }
+ Depends { name: "higher1" }
+ Depends { name: "higher2" }
+ low.prop: "highest2"
+ low.prop2: "highest"
+ }
+ }
+ Product {
+ name: "toplevel"
+ Depends { name: "highest1" }
+ Depends { name: "highest2" }
+ low.prop: name
+ property bool dummy: { console.info("final prop value: " + low.prop); }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/exports-qbs/lib.qbs b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
index 951b0fa74..01f89a221 100644
--- a/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
+++ b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
@@ -52,7 +52,7 @@ DynamicLibrary {
condition: true
prefixMapping: [{
prefix: includeDir,
- replacement: FileInfo.joinPaths(qbs.installPrefix, exportingProduct.headersInstallDir)
+ replacement: FileInfo.joinPaths(exportingProduct.qbs.installPrefix, exportingProduct.headersInstallDir)
}]
}
}
diff --git a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs b/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
index a338a220d..c2fc58299 100644
--- a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
+++ b/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
@@ -4,12 +4,12 @@ Project {
Profile {
name: "profile1"
- qbs.sysroot: "sysroot1"
+ qbs.sysroot: "/sysroot1"
}
Profile {
name: "profile2"
- qbs.sysroot: "sysroot2"
+ qbs.sysroot: "/sysroot2"
}
Product {
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index fd81dd494..d7c15fab6 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -1961,6 +1961,42 @@ void TestBlackbox::conanfileProbe()
QCOMPARE(actualResults, expectedResults);
}
+void TestBlackbox::conflictingPropertyValues_data()
+{
+ QTest::addColumn<bool>("overrideInProduct");
+ QTest::newRow("don't override in product") << false;
+ QTest::newRow("override in product") << true;
+}
+
+void TestBlackbox::conflictingPropertyValues()
+{
+ QFETCH(bool, overrideInProduct);
+
+ QDir::setCurrent(testDataDir + "/conflicting-property-values");
+ if (overrideInProduct)
+ REPLACE_IN_FILE("conflicting-property-values.qbs", "// low.prop: name", "low.prop: name");
+ else
+ REPLACE_IN_FILE("conflicting-property-values.qbs", "low.prop: name", "// low.prop: name");
+ WAIT_FOR_NEW_TIMESTAMP();
+ QCOMPARE(runQbs(QString("resolve")), 0);
+ if (overrideInProduct) {
+ // Binding in product itself overrides everything else, module-level conflicts
+ // are irrelevant.
+ QVERIFY2(m_qbsStdout.contains("final prop value: toplevel"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
+ } else {
+ // Only the conflicts in the highest-level modules are reported, lower-level conflicts
+ // are irrelevant.
+ // prop2 does not cause a conflict, because the values are the same.
+ QVERIFY2(m_qbsStdout.contains("final prop value: highest"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStderr.contains("Conflicting scalar values for property 'prop'"),
+ m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.count("values.qbs") == 2, m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("values.qbs:20:23"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("values.qbs:30:23"), m_qbsStderr.constData());
+ }
+}
+
void TestBlackbox::cpuFeatures()
{
QDir::setCurrent(testDataDir + "/cpu-features");
@@ -3447,7 +3483,7 @@ void TestBlackbox::propertyAssignmentInFailedModule()
QVERIFY(runQbs(failParams) != 0);
QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:true"))), 0);
QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
- QEXPECT_FAIL(nullptr, "circular dependency between module merging and validation", Continue);
+ failParams.expectFailure = false;
QCOMPARE(runQbs(failParams), 0);
}
@@ -5574,11 +5610,7 @@ void TestBlackbox::propertyPrecedence()
switchProfileContents(profile.p, s.get(), false);
switchFileContents(nonleafFile, true);
QCOMPARE(runQbs(resolveParams), 0);
- QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
- && m_qbsStderr.contains("nonleaf.qbs:4:22")
- && m_qbsStderr.contains("dep.qbs:6:26"),
- m_qbsStderr.constData());
- QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); QCOMPARE(runQbs(params), 0);
QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
&& m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"leaf\"]\n"),
m_qbsStdout.constData());
@@ -5586,10 +5618,7 @@ void TestBlackbox::propertyPrecedence()
// Case 8: [cmdline=0,prod=0,export=1,nonleaf=1,profile=1]
switchProfileContents(profile.p, s.get(), true);
QCOMPARE(runQbs(resolveParams), 0);
- QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
- && m_qbsStderr.contains("nonleaf.qbs:4:22")
- && m_qbsStderr.contains("dep.qbs:6:26"),
- m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
QCOMPARE(runQbs(params), 0);
QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
&& m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"profile\"]\n"),
@@ -6177,11 +6206,11 @@ void TestBlackbox::qbsModulePropertiesInProviders()
QCOMPARE(m_qbsStdout.count("Running setup script for qbsmetatestmodule"), 2);
// Check that products get correct values from modules
- QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: sysroot1")), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: sysroot2")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: /sysroot1")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: /sysroot2")), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: sysroot1")), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: sysroot2")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: /sysroot1")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: /sysroot2")), m_qbsStdout);
}
// Tests whether it is possible to set qbsModuleProviders in Product and Project items
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 8f9bfc984..f4e001360 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -90,6 +90,8 @@ private slots:
void cxxLanguageVersion_data();
void conanfileProbe_data();
void conanfileProbe();
+ void conflictingPropertyValues_data();
+ void conflictingPropertyValues();
void cpuFeatures();
void dependenciesProperty();
void dependencyScanningLoop();
diff --git a/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs b/tests/auto/language/testdata/module-depends-on-product.qbs
index a7db9e036..a7db9e036 100644
--- a/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs
+++ b/tests/auto/language/testdata/module-depends-on-product.qbs
diff --git a/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs b/tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs
index 5781bd6de..5781bd6de 100644
--- a/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs
+++ b/tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index 689b3bd7f..5ab65457f 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -899,8 +899,6 @@ void TestLanguage::erroneousFiles_data()
QTest::newRow("conflicting-module-instances")
<< "There is more than one equally prioritized candidate for module "
"'conflicting-instances'.";
- QTest::newRow("module-depends-on-product")
- << "module-with-product-dependency.qbs:2:5.*Modules cannot depend on products.";
QTest::newRow("overwrite-inherited-readonly-property")
<< "overwrite-inherited-readonly-property.qbs"
":2:21.*Cannot set read-only property 'readOnlyString'.";
@@ -1929,7 +1927,7 @@ void TestLanguage::modulePropertiesInGroups()
QCOMPARE(g2Gmod1List1, QStringList() << "gmod1_list1_proto" << "g2");
const auto &g2Gmod1List2 = moduleProperty(g2Props, "gmod.gmod1", "gmod1_list2")
.toStringList();
- QCOMPARE(g2Gmod1List2, QStringList() << "grouptest" << "g2" << "gmod1_list2_proto");
+ QCOMPARE(g2Gmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" << "gmod1_list2_proto");
const int g2P0 = moduleProperty(g2Props, "gmod.gmod1", "p0").toInt();
QCOMPARE(g2P0, 6);
const int g2DepProp = moduleProperty(g2Props, "gmod.gmod1", "depProp").toInt();
@@ -2095,7 +2093,25 @@ void TestLanguage::moduleScope()
qDebug() << e.toString();
}
QCOMPARE(exceptionCaught, false);
+}
+void TestLanguage::moduleWithProductDependency()
+{
+ bool exceptionCaught = false;
+ try {
+ defaultParameters.setProjectFilePath(testProject("module-depends-on-product.qbs"));
+ TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ QVERIFY(project);
+ QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
+ QCOMPARE(products.size(), 2);
+ ResolvedProductPtr product = products.value("p1");
+ QVERIFY(product);
+ QCOMPARE(int(product->dependencies.size()), 1);
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
}
void TestLanguage::modules_data()
@@ -2821,8 +2837,8 @@ void TestLanguage::qbsPropertyConvenienceOverride()
QCOMPARE(project->products.size(), size_t(1));
QCOMPARE(project->products.front()->moduleProperties->qbsPropertyValue("installPrefix")
.toString(), QString("/opt"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
qDebug() << e.toString();
}
QCOMPARE(exceptionCaught, false);
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index 4fe2752e4..3689e0c61 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -137,6 +137,7 @@ private slots:
void modulePropertiesInGroups();
void modulePropertyOverridesPerProduct();
void moduleScope();
+ void moduleWithProductDependency();
void modules_data();
void modules();
void multiplexedExports();