From 61e53de1f777c84bc0e5d90ad2f18525b726129d Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 28 Apr 2023 15:08:11 +0200 Subject: Introduce loader/ subdirectory Project loading functionality is implemented in various source files these days, so it makes sense to group them together. Change-Id: Iba42b0246c40610d2a03bf6cc7ed7d3bec9d5536 Reviewed-by: Ivan Komissarov --- src/lib/corelib/CMakeLists.txt | 69 +- src/lib/corelib/api/internaljobs.cpp | 2 +- src/lib/corelib/api/project.cpp | 1 - src/lib/corelib/api/projectdata.cpp | 2 +- src/lib/corelib/buildgraph/buildgraphloader.cpp | 4 +- src/lib/corelib/corelib.qbs | 70 +- src/lib/corelib/language/astimportshandler.cpp | 305 --- src/lib/corelib/language/astimportshandler.h | 94 - .../corelib/language/astpropertiesitemhandler.cpp | 192 -- .../corelib/language/astpropertiesitemhandler.h | 63 - src/lib/corelib/language/groupshandler.cpp | 320 --- src/lib/corelib/language/groupshandler.h | 84 - src/lib/corelib/language/itemreader.cpp | 238 --- src/lib/corelib/language/itemreader.h | 121 -- src/lib/corelib/language/itemreaderastvisitor.cpp | 391 ---- src/lib/corelib/language/itemreaderastvisitor.h | 97 - .../corelib/language/itemreadervisitorstate.cpp | 195 -- src/lib/corelib/language/itemreadervisitorstate.h | 89 - src/lib/corelib/language/language.cpp | 2 +- src/lib/corelib/language/loader.cpp | 215 -- src/lib/corelib/language/loader.h | 88 - src/lib/corelib/language/localprofiles.cpp | 164 -- src/lib/corelib/language/localprofiles.h | 68 - src/lib/corelib/language/moduleinstantiator.cpp | 337 --- src/lib/corelib/language/moduleinstantiator.h | 101 - src/lib/corelib/language/moduleloader.cpp | 529 ----- src/lib/corelib/language/moduleloader.h | 99 - src/lib/corelib/language/modulepropertymerger.cpp | 304 --- src/lib/corelib/language/modulepropertymerger.h | 99 - src/lib/corelib/language/moduleproviderloader.cpp | 374 ---- src/lib/corelib/language/moduleproviderloader.h | 146 -- src/lib/corelib/language/probesresolver.cpp | 303 --- src/lib/corelib/language/probesresolver.h | 107 - .../corelib/language/productitemmultiplexer.cpp | 288 --- src/lib/corelib/language/productitemmultiplexer.h | 84 - src/lib/corelib/language/projectresolver.cpp | 1801 ---------------- src/lib/corelib/language/projectresolver.h | 202 -- src/lib/corelib/language/projecttreebuilder.cpp | 2213 -------------------- src/lib/corelib/language/projecttreebuilder.h | 103 - src/lib/corelib/loader/astimportshandler.cpp | 305 +++ src/lib/corelib/loader/astimportshandler.h | 94 + .../corelib/loader/astpropertiesitemhandler.cpp | 192 ++ src/lib/corelib/loader/astpropertiesitemhandler.h | 63 + src/lib/corelib/loader/groupshandler.cpp | 320 +++ src/lib/corelib/loader/groupshandler.h | 83 + src/lib/corelib/loader/itemreader.cpp | 238 +++ src/lib/corelib/loader/itemreader.h | 121 ++ src/lib/corelib/loader/itemreaderastvisitor.cpp | 391 ++++ src/lib/corelib/loader/itemreaderastvisitor.h | 97 + src/lib/corelib/loader/itemreadervisitorstate.cpp | 195 ++ src/lib/corelib/loader/itemreadervisitorstate.h | 89 + src/lib/corelib/loader/loader.cpp | 215 ++ src/lib/corelib/loader/loader.h | 88 + src/lib/corelib/loader/localprofiles.cpp | 163 ++ src/lib/corelib/loader/localprofiles.h | 68 + src/lib/corelib/loader/moduleinstantiator.cpp | 337 +++ src/lib/corelib/loader/moduleinstantiator.h | 101 + src/lib/corelib/loader/moduleloader.cpp | 529 +++++ src/lib/corelib/loader/moduleloader.h | 99 + src/lib/corelib/loader/modulepropertymerger.cpp | 303 +++ src/lib/corelib/loader/modulepropertymerger.h | 99 + src/lib/corelib/loader/moduleproviderloader.cpp | 372 ++++ src/lib/corelib/loader/moduleproviderloader.h | 146 ++ src/lib/corelib/loader/probesresolver.cpp | 302 +++ src/lib/corelib/loader/probesresolver.h | 107 + src/lib/corelib/loader/productitemmultiplexer.cpp | 287 +++ src/lib/corelib/loader/productitemmultiplexer.h | 84 + src/lib/corelib/loader/projectresolver.cpp | 1800 ++++++++++++++++ src/lib/corelib/loader/projectresolver.h | 202 ++ src/lib/corelib/loader/projecttreebuilder.cpp | 2213 ++++++++++++++++++++ src/lib/corelib/loader/projecttreebuilder.h | 104 + 71 files changed, 9887 insertions(+), 9884 deletions(-) delete mode 100644 src/lib/corelib/language/astimportshandler.cpp delete mode 100644 src/lib/corelib/language/astimportshandler.h delete mode 100644 src/lib/corelib/language/astpropertiesitemhandler.cpp delete mode 100644 src/lib/corelib/language/astpropertiesitemhandler.h delete mode 100644 src/lib/corelib/language/groupshandler.cpp delete mode 100644 src/lib/corelib/language/groupshandler.h delete mode 100644 src/lib/corelib/language/itemreader.cpp delete mode 100644 src/lib/corelib/language/itemreader.h delete mode 100644 src/lib/corelib/language/itemreaderastvisitor.cpp delete mode 100644 src/lib/corelib/language/itemreaderastvisitor.h delete mode 100644 src/lib/corelib/language/itemreadervisitorstate.cpp delete mode 100644 src/lib/corelib/language/itemreadervisitorstate.h delete mode 100644 src/lib/corelib/language/loader.cpp delete mode 100644 src/lib/corelib/language/loader.h delete mode 100644 src/lib/corelib/language/localprofiles.cpp delete mode 100644 src/lib/corelib/language/localprofiles.h delete mode 100644 src/lib/corelib/language/moduleinstantiator.cpp delete mode 100644 src/lib/corelib/language/moduleinstantiator.h delete mode 100644 src/lib/corelib/language/moduleloader.cpp delete mode 100644 src/lib/corelib/language/moduleloader.h delete mode 100644 src/lib/corelib/language/modulepropertymerger.cpp delete mode 100644 src/lib/corelib/language/modulepropertymerger.h delete mode 100644 src/lib/corelib/language/moduleproviderloader.cpp delete mode 100644 src/lib/corelib/language/moduleproviderloader.h delete mode 100644 src/lib/corelib/language/probesresolver.cpp delete mode 100644 src/lib/corelib/language/probesresolver.h delete mode 100644 src/lib/corelib/language/productitemmultiplexer.cpp delete mode 100644 src/lib/corelib/language/productitemmultiplexer.h delete mode 100644 src/lib/corelib/language/projectresolver.cpp delete mode 100644 src/lib/corelib/language/projectresolver.h delete mode 100644 src/lib/corelib/language/projecttreebuilder.cpp delete mode 100644 src/lib/corelib/language/projecttreebuilder.h create mode 100644 src/lib/corelib/loader/astimportshandler.cpp create mode 100644 src/lib/corelib/loader/astimportshandler.h create mode 100644 src/lib/corelib/loader/astpropertiesitemhandler.cpp create mode 100644 src/lib/corelib/loader/astpropertiesitemhandler.h create mode 100644 src/lib/corelib/loader/groupshandler.cpp create mode 100644 src/lib/corelib/loader/groupshandler.h create mode 100644 src/lib/corelib/loader/itemreader.cpp create mode 100644 src/lib/corelib/loader/itemreader.h create mode 100644 src/lib/corelib/loader/itemreaderastvisitor.cpp create mode 100644 src/lib/corelib/loader/itemreaderastvisitor.h create mode 100644 src/lib/corelib/loader/itemreadervisitorstate.cpp create mode 100644 src/lib/corelib/loader/itemreadervisitorstate.h create mode 100644 src/lib/corelib/loader/loader.cpp create mode 100644 src/lib/corelib/loader/loader.h create mode 100644 src/lib/corelib/loader/localprofiles.cpp create mode 100644 src/lib/corelib/loader/localprofiles.h create mode 100644 src/lib/corelib/loader/moduleinstantiator.cpp create mode 100644 src/lib/corelib/loader/moduleinstantiator.h create mode 100644 src/lib/corelib/loader/moduleloader.cpp create mode 100644 src/lib/corelib/loader/moduleloader.h create mode 100644 src/lib/corelib/loader/modulepropertymerger.cpp create mode 100644 src/lib/corelib/loader/modulepropertymerger.h create mode 100644 src/lib/corelib/loader/moduleproviderloader.cpp create mode 100644 src/lib/corelib/loader/moduleproviderloader.h create mode 100644 src/lib/corelib/loader/probesresolver.cpp create mode 100644 src/lib/corelib/loader/probesresolver.h create mode 100644 src/lib/corelib/loader/productitemmultiplexer.cpp create mode 100644 src/lib/corelib/loader/productitemmultiplexer.h create mode 100644 src/lib/corelib/loader/projectresolver.cpp create mode 100644 src/lib/corelib/loader/projectresolver.h create mode 100644 src/lib/corelib/loader/projecttreebuilder.cpp create mode 100644 src/lib/corelib/loader/projecttreebuilder.h (limited to 'src') diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt index 17d770666..ee551b2ca 100644 --- a/src/lib/corelib/CMakeLists.txt +++ b/src/lib/corelib/CMakeLists.txt @@ -183,10 +183,6 @@ list_transform_prepend(JS_EXTENSIONS_MACOS_SOURCES jsextensions/) set(LANGUAGE_SOURCES artifactproperties.cpp artifactproperties.h - astimportshandler.cpp - astimportshandler.h - astpropertiesitemhandler.cpp - astpropertiesitemhandler.h asttools.cpp asttools.h builtindeclarations.cpp @@ -200,8 +196,6 @@ set(LANGUAGE_SOURCES filecontextbase.h filetags.cpp filetags.h - groupshandler.cpp - groupshandler.h identifiersearch.cpp identifiersearch.h item.cpp @@ -211,39 +205,13 @@ set(LANGUAGE_SOURCES itemobserver.h itempool.cpp itempool.h - itemreader.cpp - itemreader.h - itemreaderastvisitor.cpp - itemreaderastvisitor.h - itemreadervisitorstate.cpp - itemreadervisitorstate.h itemtype.h jsimports.h language.cpp language.h - loader.cpp - loader.h - localprofiles.cpp - localprofiles.h - moduleinstantiator.cpp - moduleinstantiator.h - moduleloader.cpp - moduleloader.h - modulepropertymerger.cpp - modulepropertymerger.h moduleproviderinfo.h - moduleproviderloader.cpp - moduleproviderloader.h preparescriptobserver.cpp preparescriptobserver.h - probesresolver.cpp - probesresolver.h - productitemmultiplexer.cpp - productitemmultiplexer.h - projectresolver.cpp - projectresolver.h - projecttreebuilder.cpp - projecttreebuilder.h property.cpp property.h propertydeclaration.cpp @@ -267,6 +235,42 @@ list_transform_prepend(LANGUAGE_SOURCES language/) set(LANGUAGE_HEADERS language/forward_decls.h) +set(LOADER_SOURCES + astimportshandler.cpp + astimportshandler.h + astpropertiesitemhandler.cpp + astpropertiesitemhandler.h + groupshandler.cpp + groupshandler.h + itemreader.cpp + itemreader.h + itemreaderastvisitor.cpp + itemreaderastvisitor.h + itemreadervisitorstate.cpp + itemreadervisitorstate.h + loader.cpp + loader.h + localprofiles.cpp + localprofiles.h + moduleinstantiator.cpp + moduleinstantiator.h + moduleloader.cpp + moduleloader.h + modulepropertymerger.cpp + modulepropertymerger.h + moduleproviderloader.cpp + moduleproviderloader.h + probesresolver.cpp + probesresolver.h + productitemmultiplexer.cpp + productitemmultiplexer.h + projectresolver.cpp + projectresolver.h + projecttreebuilder.cpp + projecttreebuilder.h + ) +list_transform_prepend(LOADER_SOURCES loader/) + set(LOGGING_SOURCES categories.cpp categories.h @@ -459,6 +463,7 @@ add_qbs_library(qbscore ${JS_EXTENSIONS_MACOS_SOURCES} ${LANGUAGE_SOURCES} ${LANGUAGE_HEADERS} + ${LOADER_SOURCES} ${LOGGING_SOURCES} ${LOGGING_HEADERS} ${PARSER_SOURCES} diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp index 6c2987cd2..47990fb32 100644 --- a/src/lib/corelib/api/internaljobs.cpp +++ b/src/lib/corelib/api/internaljobs.cpp @@ -49,8 +49,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index 936a86fa6..f2bdd6d88 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -61,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp index a97491955..501a65fdd 100644 --- a/src/lib/corelib/api/projectdata.cpp +++ b/src/lib/corelib/api/projectdata.cpp @@ -41,8 +41,8 @@ #include "projectdata_p.h" #include "propertymap_p.h" #include -#include #include +#include #include #include #include diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp index 66e4e8578..f932da0b8 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.cpp +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -45,15 +45,15 @@ #include "projectbuilddata.h" #include "rulenode.h" #include "rulecommands.h" -#include "rulesevaluationcontext.h" #include "transformer.h" +#include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs index 358e6e353..1056970cf 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -266,10 +266,6 @@ QbsLibrary { files: [ "artifactproperties.cpp", "artifactproperties.h", - "astimportshandler.cpp", - "astimportshandler.h", - "astpropertiesitemhandler.cpp", - "astpropertiesitemhandler.h", "asttools.cpp", "asttools.h", "builtindeclarations.cpp", @@ -283,8 +279,6 @@ QbsLibrary { "filecontextbase.h", "filetags.cpp", "filetags.h", - "groupshandler.cpp", - "groupshandler.h", "identifiersearch.cpp", "identifiersearch.h", "item.cpp", @@ -294,39 +288,13 @@ QbsLibrary { "itemobserver.h", "itempool.cpp", "itempool.h", - "itemreader.cpp", - "itemreader.h", - "itemreaderastvisitor.cpp", - "itemreaderastvisitor.h", - "itemreadervisitorstate.cpp", - "itemreadervisitorstate.h", "itemtype.h", "jsimports.h", "language.cpp", "language.h", - "loader.cpp", - "loader.h", - "localprofiles.cpp", - "localprofiles.h", - "moduleinstantiator.cpp", - "moduleinstantiator.h", - "moduleloader.cpp", - "moduleloader.h", - "modulepropertymerger.cpp", - "modulepropertymerger.h", "moduleproviderinfo.h", - "moduleproviderloader.cpp", - "moduleproviderloader.h", "preparescriptobserver.cpp", "preparescriptobserver.h", - "probesresolver.cpp", - "probesresolver.h", - "productitemmultiplexer.cpp", - "productitemmultiplexer.h", - "projectresolver.cpp", - "projectresolver.h", - "projecttreebuilder.cpp", - "projecttreebuilder.h", "property.cpp", "property.h", "propertydeclaration.cpp", @@ -353,6 +321,44 @@ QbsLibrary { qbs.installDir: headerInstallPrefix + "/language" files: "language/forward_decls.h" } + Group { + name: "loader" + prefix: name + '/' + files: [ + "astimportshandler.cpp", + "astimportshandler.h", + "astpropertiesitemhandler.cpp", + "astpropertiesitemhandler.h", + "groupshandler.cpp", + "groupshandler.h", + "itemreader.cpp", + "itemreader.h", + "itemreaderastvisitor.cpp", + "itemreaderastvisitor.h", + "itemreadervisitorstate.cpp", + "itemreadervisitorstate.h", + "loader.cpp", + "loader.h", + "localprofiles.cpp", + "localprofiles.h", + "moduleinstantiator.cpp", + "moduleinstantiator.h", + "moduleloader.cpp", + "moduleloader.h", + "modulepropertymerger.cpp", + "modulepropertymerger.h", + "moduleproviderloader.cpp", + "moduleproviderloader.h", + "probesresolver.cpp", + "probesresolver.h", + "productitemmultiplexer.cpp", + "productitemmultiplexer.h", + "projectresolver.cpp", + "projectresolver.h", + "projecttreebuilder.cpp", + "projecttreebuilder.h", + ] + } Group { name: "logging" prefix: name + '/' diff --git a/src/lib/corelib/language/astimportshandler.cpp b/src/lib/corelib/language/astimportshandler.cpp deleted file mode 100644 index d634af7e4..000000000 --- a/src/lib/corelib/language/astimportshandler.cpp +++ /dev/null @@ -1,305 +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 "astimportshandler.h" - -#include "asttools.h" -#include "builtindeclarations.h" -#include "filecontext.h" -#include "itemreadervisitorstate.h" -#include "jsextensions/jsextensions.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace qbs { -namespace Internal { - -ASTImportsHandler::ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, - const FileContextPtr &file) - : m_visitorState(visitorState) - , m_logger(logger) - , m_file(file) - , m_directory(FileInfo::path(m_file->filePath())) -{ -} - -void ASTImportsHandler::handleImports(const QbsQmlJS::AST::UiImportList *uiImportList) -{ - const auto searchPaths = m_file->searchPaths(); - for (const QString &searchPath : searchPaths) - collectPrototypes(searchPath + QStringLiteral("/imports"), QString()); - - // files in the same directory are available as prototypes - collectPrototypes(m_directory, QString()); - - bool baseImported = false; - for (const auto *it = uiImportList; it; it = it->next) - handleImport(it->import, &baseImported); - if (!baseImported) { - QStringRef qbsref(&StringConstants::qbsModule()); - QbsQmlJS::AST::UiQualifiedId qbsURI(qbsref); - qbsURI.finish(); - QbsQmlJS::AST::UiImport imp(&qbsURI); - handleImport(&imp, &baseImported); - } - - for (auto it = m_jsImports.constBegin(); it != m_jsImports.constEnd(); ++it) - m_file->addJsImport(it.value()); -} - -void ASTImportsHandler::handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported) -{ - QStringList importUri; - bool isBase = false; - if (import->importUri) { - importUri = toStringList(import->importUri); - isBase = (importUri.size() == 1 && importUri.front() == StringConstants::qbsModule()) - || (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule() - && importUri.last() == StringConstants::baseVar()); - if (isBase) { - *baseImported = true; - checkImportVersion(import->versionToken); - } else if (import->versionToken.length) { - m_logger.printWarning(ErrorInfo(Tr::tr("Superfluous version specification."), - toCodeLocation(m_file->filePath(), import->versionToken))); - } - } - - QString as; - if (isBase) { - if (Q_UNLIKELY(!import->importId.isNull())) { - throw ErrorInfo(Tr::tr("Import of qbs.base must have no 'as '"), - toCodeLocation(m_file->filePath(), import->importIdToken)); - } - } else { - if (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule()) { - const QString extensionName = importUri.last(); - if (JsExtensions::hasExtension(extensionName)) { - if (Q_UNLIKELY(!import->importId.isNull())) { - throw ErrorInfo(Tr::tr("Import of built-in extension '%1' " - "must not have 'as' specifier.").arg(extensionName), - toCodeLocation(m_file->filePath(), import->asToken)); - } - if (Q_UNLIKELY(m_file->jsExtensions().contains(extensionName))) { - m_logger.printWarning(ErrorInfo(Tr::tr("Built-in extension '%1' already " - "imported.").arg(extensionName), - toCodeLocation(m_file->filePath(), - import->importToken))); - } else { - m_file->addJsExtension(extensionName); - } - return; - } - } - - if (import->importId.isNull()) { - if (!import->fileName.isNull()) { - throw ErrorInfo(Tr::tr("File imports require 'as '"), - toCodeLocation(m_file->filePath(), import->importToken)); - } - if (importUri.empty()) { - throw ErrorInfo(Tr::tr("Invalid import URI."), - toCodeLocation(m_file->filePath(), import->importToken)); - } - as = importUri.last(); - } else { - as = import->importId.toString(); - } - - if (Q_UNLIKELY(JsExtensions::hasExtension(as))) - throw ErrorInfo(Tr::tr("Cannot reuse the name of built-in extension '%1'.").arg(as), - toCodeLocation(m_file->filePath(), import->importIdToken)); - if (Q_UNLIKELY(!m_importAsNames.insert(as).second)) { - throw ErrorInfo(Tr::tr("Cannot import into the same name more than once."), - toCodeLocation(m_file->filePath(), import->importIdToken)); - } - } - - if (!import->fileName.isNull()) { - QString filePath = FileInfo::resolvePath(m_directory, import->fileName.toString()); - - QFileInfo fi(filePath); - if (Q_UNLIKELY(!fi.exists())) - throw ErrorInfo(Tr::tr("Cannot find imported file %0.") - .arg(QDir::toNativeSeparators(filePath)), - CodeLocation(m_file->filePath(), import->fileNameToken.startLine, - import->fileNameToken.startColumn)); - filePath = fi.canonicalFilePath(); - if (fi.isDir()) { - collectPrototypesAndJsCollections(filePath, as, - toCodeLocation(m_file->filePath(), import->fileNameToken)); - } else { - if (filePath.endsWith(QStringLiteral(".js"), Qt::CaseInsensitive)) { - JsImport &jsImport = m_jsImports[as]; - jsImport.scopeName = as; - jsImport.filePaths.push_back(filePath); - jsImport.location - = toCodeLocation(m_file->filePath(), import->firstSourceLocation()); - } else if (filePath.endsWith(QStringLiteral(".qbs"), Qt::CaseInsensitive)) { - m_typeNameToFile.insert(QStringList(as), filePath); - } else { - throw ErrorInfo(Tr::tr("Can only import .qbs and .js files"), - CodeLocation(m_file->filePath(), import->fileNameToken.startLine, - import->fileNameToken.startColumn)); - } - } - } else if (!importUri.empty()) { - const QString importPath = isBase - ? QStringLiteral("qbs/base") : importUri.join(QDir::separator()); - bool found = m_typeNameToFile.contains(importUri); - if (!found) { - const auto searchPaths = m_file->searchPaths(); - for (const QString &searchPath : searchPaths) { - const QFileInfo fi(FileInfo::resolvePath( - FileInfo::resolvePath(searchPath, - StringConstants::importsDir()), - importPath)); - if (fi.isDir()) { - // ### versioning, qbsdir file, etc. - const QString &resultPath = fi.absoluteFilePath(); - collectPrototypesAndJsCollections(resultPath, as, - toCodeLocation(m_file->filePath(), import->fileNameToken)); - found = true; - break; - } - } - } - if (Q_UNLIKELY(!found)) { - throw ErrorInfo(Tr::tr("import %1 not found") - .arg(importUri.join(QLatin1Char('.'))), - toCodeLocation(m_file->filePath(), import->fileNameToken)); - } - } -} - -Version ASTImportsHandler::readImportVersion(const QString &str, const CodeLocation &location) -{ - const Version v = Version::fromString(str); - if (Q_UNLIKELY(!v.isValid())) - throw ErrorInfo(Tr::tr("Cannot parse version number in import statement."), location); - if (Q_UNLIKELY(v.patchLevel() != 0)) { - throw ErrorInfo(Tr::tr("Version number in import statement cannot have more than " - "two components."), location); - } - return v; -} - -bool ASTImportsHandler::addPrototype(const QString &fileName, const QString &filePath, - const QString &as, bool needsCheck) -{ - if (needsCheck && fileName.size() <= 4) - return false; - - const QString componentName = fileName.left(fileName.size() - 4); - // ### validate componentName - - if (needsCheck && !componentName.at(0).isUpper()) - return false; - - QStringList prototypeName; - if (!as.isEmpty()) - prototypeName.push_back(as); - prototypeName.push_back(componentName); - if (!m_typeNameToFile.contains(prototypeName)) - m_typeNameToFile.insert(prototypeName, filePath); - return true; -} - -void ASTImportsHandler::checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const -{ - if (!versionToken.length) - return; - const QString importVersionString - = m_file->content().mid(versionToken.offset, versionToken.length); - const Version importVersion = readImportVersion(importVersionString, - toCodeLocation(m_file->filePath(), versionToken)); - if (Q_UNLIKELY(importVersion != BuiltinDeclarations::instance().languageVersion())) - throw ErrorInfo(Tr::tr("Incompatible qbs language version %1. This is version %2.").arg( - importVersionString, - BuiltinDeclarations::instance().languageVersion().toString()), - toCodeLocation(m_file->filePath(), versionToken)); - -} - -void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as) -{ - QStringList fileNames; // Yes, file *names*. - if (m_visitorState.findDirectoryEntries(path, &fileNames)) { - for (const QString &fileName : qAsConst(fileNames)) - addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); - return; - } - - QDirIterator dirIter(path, StringConstants::qbsFileWildcards()); - while (dirIter.hasNext()) { - const QString filePath = dirIter.next(); - const QString fileName = dirIter.fileName(); - if (addPrototype(fileName, filePath, as, true)) - fileNames << fileName; - } - m_visitorState.cacheDirectoryEntries(path, fileNames); - -} - -void ASTImportsHandler::collectPrototypesAndJsCollections(const QString &path, const QString &as, - const CodeLocation &location) -{ - collectPrototypes(path, as); - QDirIterator dirIter(path, StringConstants::jsFileWildcards()); - while (dirIter.hasNext()) { - dirIter.next(); - JsImport &jsImport = m_jsImports[as]; - if (jsImport.scopeName.isNull()) { - jsImport.scopeName = as; - jsImport.location = location; - } - jsImport.filePaths.push_back(dirIter.filePath()); - } -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/astimportshandler.h b/src/lib/corelib/language/astimportshandler.h deleted file mode 100644 index e9c2b6c27..000000000 --- a/src/lib/corelib/language/astimportshandler.h +++ /dev/null @@ -1,94 +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$ -** -****************************************************************************/ -#ifndef QBS_ASTIMPORTSHANDLER_H -#define QBS_ASTIMPORTSHANDLER_H - -#include "forward_decls.h" - -#include -#include - -#include -#include - -namespace qbs { -class CodeLocation; -class Version; - -namespace Internal { -class ItemReaderVisitorState; -class JsImport; -class Logger; - -class ASTImportsHandler -{ -public: - ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, - const FileContextPtr &file); - - void handleImports(const QbsQmlJS::AST::UiImportList *uiImportList); - - QHash typeNameFileMap() const { return m_typeNameToFile; } - -private: - static Version readImportVersion(const QString &str, const CodeLocation &location); - - bool addPrototype(const QString &fileName, const QString &filePath, const QString &as, - bool needsCheck); - void checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const; - void collectPrototypes(const QString &path, const QString &as); - void collectPrototypesAndJsCollections(const QString &path, const QString &as, - const CodeLocation &location); - void handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported); - - ItemReaderVisitorState &m_visitorState; - Logger &m_logger; - const FileContextPtr &m_file; - const QString m_directory; - QHash m_typeNameToFile; - Set m_importAsNames; - - using JsImportsHash = QHash; - JsImportsHash m_jsImports; -}; - -} // namespace Internal -} // namespace qbs - -#endif // Include guard diff --git a/src/lib/corelib/language/astpropertiesitemhandler.cpp b/src/lib/corelib/language/astpropertiesitemhandler.cpp deleted file mode 100644 index cc4c02232..000000000 --- a/src/lib/corelib/language/astpropertiesitemhandler.cpp +++ /dev/null @@ -1,192 +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 "astpropertiesitemhandler.h" - -#include "item.h" -#include "value.h" - -#include -#include -#include -#include - -namespace qbs { -namespace Internal { - -ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem) : m_parentItem(parentItem) -{ -} - -void ASTPropertiesItemHandler::handlePropertiesItems() -{ - // TODO: Simply forbid Properties items to have child items and get rid of this check. - if (m_parentItem->type() != ItemType::Properties) - setupAlternatives(); -} - -void ASTPropertiesItemHandler::setupAlternatives() -{ - auto it = m_parentItem->m_children.begin(); - while (it != m_parentItem->m_children.end()) { - Item * const child = *it; - bool remove = false; - if (child->type() == ItemType::Properties) { - handlePropertiesBlock(child); - remove = m_parentItem->type() != ItemType::Export; - } - if (remove) - it = m_parentItem->m_children.erase(it); - else - ++it; - } -} - -class PropertiesBlockConverter -{ -public: - PropertiesBlockConverter(const JSSourceValue::AltProperty &condition, - const JSSourceValue::AltProperty &overrideListProperties, - Item *propertiesBlockContainer, const Item *propertiesBlock) - : m_propertiesBlockContainer(propertiesBlockContainer) - , m_propertiesBlock(propertiesBlock) - { - m_alternative.condition = condition; - m_alternative.overrideListProperties = overrideListProperties; - } - - void apply() - { - doApply(m_propertiesBlockContainer, m_propertiesBlock); - } - -private: - JSSourceValue::Alternative m_alternative; - Item * const m_propertiesBlockContainer; - const Item * const m_propertiesBlock; - - void doApply(Item *outer, const Item *inner) - { - for (auto it = inner->properties().constBegin(); - it != inner->properties().constEnd(); ++it) { - if (inner == m_propertiesBlock - && (it.key() == StringConstants::conditionProperty() - || it.key() == StringConstants::overrideListPropertiesProperty())) { - continue; - } - if (it.value()->type() == Value::ItemValueType) { - Item * const innerVal = std::static_pointer_cast(it.value())->item(); - ItemValuePtr outerVal = outer->itemProperty(it.key()); - if (!outerVal) { - outerVal = ItemValue::create(Item::create(outer->pool(), innerVal->type()), - true); - outer->setProperty(it.key(), outerVal); - } - doApply(outerVal->item(), innerVal); - } else if (it.value()->type() == Value::JSSourceValueType) { - const ValuePtr outerVal = outer->property(it.key()); - if (Q_UNLIKELY(outerVal && outerVal->type() != Value::JSSourceValueType)) { - throw ErrorInfo(Tr::tr("Incompatible value type in unconditional value at %1.") - .arg(outerVal->location().toString())); - } - doApply(it.key(), outer, std::static_pointer_cast(outerVal), - std::static_pointer_cast(it.value())); - } else { - QBS_CHECK(!"Unexpected value type in conditional value."); - } - } - } - - void doApply(const QString &propertyName, Item *item, JSSourceValuePtr value, - const JSSourceValuePtr &conditionalValue) - { - if (!value) { - value = JSSourceValue::create(true); - value->setFile(conditionalValue->file()); - item->setProperty(propertyName, value); - value->setSourceCode(StringConstants::baseVar()); - value->setSourceUsesBase(); - } - m_alternative.value = conditionalValue; - value->addAlternative(m_alternative); - } -}; - -static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, const QString &name) -{ - const ValuePtr value = propertiesItem->property(name); - if (!value) { - if (name == StringConstants::conditionProperty()) { - throw ErrorInfo(Tr::tr("Properties.condition must be provided."), - propertiesItem->location()); - } - return JSSourceValue::AltProperty(StringConstants::falseValue(), - propertiesItem->location()); - } - if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) { - throw ErrorInfo(Tr::tr("Properties.%1 must be a value binding.").arg(name), - propertiesItem->location()); - } - if (name == StringConstants::overrideListPropertiesProperty()) { - const Item *parent = propertiesItem->parent(); - while (parent) { - if (parent->type() == ItemType::Product) - break; - parent = parent->parent(); - } - if (!parent) { - throw ErrorInfo(Tr::tr("Properties.overrideListProperties can only be set " - "in a Product item.")); - } - - } - const JSSourceValuePtr srcval = std::static_pointer_cast(value); - return JSSourceValue::AltProperty(srcval->sourceCodeForEvaluation(), srcval->location()); -} - -void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem) -{ - const auto condition = getPropertyData(propertiesItem, StringConstants::conditionProperty()); - const auto overrideListProperties = getPropertyData(propertiesItem, - StringConstants::overrideListPropertiesProperty()); - PropertiesBlockConverter(condition, overrideListProperties, m_parentItem, - propertiesItem).apply(); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/astpropertiesitemhandler.h b/src/lib/corelib/language/astpropertiesitemhandler.h deleted file mode 100644 index 413512ee5..000000000 --- a/src/lib/corelib/language/astpropertiesitemhandler.h +++ /dev/null @@ -1,63 +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$ -** -****************************************************************************/ -#ifndef QBS_ASTPROPERTIESITEMHANDLER_H -#define QBS_ASTPROPERTIESITEMHANDLER_H - -namespace qbs { -namespace Internal { -class Item; - -class ASTPropertiesItemHandler -{ -public: - ASTPropertiesItemHandler(Item *parentItem); - - void handlePropertiesItems(); - -private: - void setupAlternatives(); - void handlePropertiesBlock(const Item *propertiesItem); - - Item * const m_parentItem; -}; - -} // namespace Internal -} // namespace qbs - -#endif // Include guard. diff --git a/src/lib/corelib/language/groupshandler.cpp b/src/lib/corelib/language/groupshandler.cpp deleted file mode 100644 index b9acfc553..000000000 --- a/src/lib/corelib/language/groupshandler.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include - -namespace qbs::Internal { -class GroupsHandler::Private -{ -public: - Private(const SetupProjectParameters ¶meters, 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 ¶meters; - ModuleInstantiator &moduleInstantiator; - Evaluator &evaluator; - Logger &logger; - std::unordered_map modulePropsSetInGroups; - Set disabledGroups; - qint64 elapsedTime = 0; -}; - -GroupsHandler::GroupsHandler(const SetupProjectParameters ¶meters, - 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 GroupsHandler::modulePropertiesSetInGroups() const -{ - return d->modulePropsSetInGroups; -} - -Set 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(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 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(it.value()).get(), - QualifiedId(it.key()), propsSetInGroup); - } - } - return propsSetInGroup; -} - -} // namespace qbs::Internal diff --git a/src/lib/corelib/language/groupshandler.h b/src/lib/corelib/language/groupshandler.h deleted file mode 100644 index d3948cbef..000000000 --- a/src/lib/corelib/language/groupshandler.h +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************************** -** -** 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 "qualifiedid.h" - -#include - -#include -#include - -namespace qbs { -class SetupProjectParameters; -namespace Internal { -class Evaluator; -class Item; -class Logger; -class ModuleInstantiator; - -// 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: - GroupsHandler(const SetupProjectParameters ¶meters, ModuleInstantiator &instantiator, - Evaluator &evaluator, Logger &logger); - ~GroupsHandler(); - - void setupGroups(Item *product, Item *productScope); - std::unordered_map modulePropertiesSetInGroups() const; - Set disabledGroups() const; - - void printProfilingInfo(int indent); - -private: - class Private; - Private * const d; -}; - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp deleted file mode 100644 index e71eba43d..000000000 --- a/src/lib/corelib/language/itemreader.cpp +++ /dev/null @@ -1,238 +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 "itemreader.h" - -#include "deprecationinfo.h" -#include "evaluator.h" -#include "item.h" -#include "itemreadervisitorstate.h" -#include "value.h" - -#include -#include -#include - -#include - -#include - -namespace qbs { -namespace Internal { - -static void makePathsCanonical(QStringList &paths) -{ - Internal::removeIf(paths, [](QString &p) { - p = QFileInfo(p).canonicalFilePath(); - return p.isEmpty(); - }); -} - -ItemReader::ItemReader(Logger &logger) - : m_visitorState(std::make_unique(logger)) -{ -} - -ItemReader::~ItemReader() = default; - -void ItemReader::setSearchPaths(const QStringList &searchPaths) -{ - m_searchPaths = searchPaths; - makePathsCanonical(m_searchPaths); - m_allSearchPaths.clear(); -} - -void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) -{ - m_extraSearchPaths.push_back(extraSearchPaths); - makePathsCanonical(m_extraSearchPaths.back()); - m_allSearchPaths.clear(); -} - -void ItemReader::popExtraSearchPaths() -{ - m_extraSearchPaths.pop_back(); - m_allSearchPaths.clear(); -} - -const std::vector &ItemReader::extraSearchPathsStack() const -{ - return m_extraSearchPaths; -} - -void ItemReader::setExtraSearchPathsStack(const std::vector &s) -{ - m_extraSearchPaths = s; - m_allSearchPaths.clear(); -} - -void ItemReader::clearExtraSearchPathsStack() -{ - m_extraSearchPaths.clear(); - m_allSearchPaths.clear(); -} - -const QStringList &ItemReader::allSearchPaths() const -{ - if (m_allSearchPaths.empty()) { - std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(), - [this] (const QStringList &paths) { - m_allSearchPaths += paths; - }); - m_allSearchPaths += m_searchPaths; - m_allSearchPaths.removeDuplicates(); - } - return m_allSearchPaths; -} - -Item *ItemReader::readFile(const QString &filePath) -{ - AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr); - return m_visitorState->readFile(filePath, allSearchPaths(), m_pool); -} - -Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation) -{ - try { - return readFile(filePath); - } catch (const ErrorInfo &e) { - if (e.hasLocation()) - throw; - throw ErrorInfo(e.toString(), referencingLocation); - } -} - -Set ItemReader::filesRead() const -{ - return m_visitorState->filesRead(); -} - -void ItemReader::setEnableTiming(bool on) -{ - m_elapsedTime = on ? 0 : -1; -} - -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 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; -} - -SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths) - : m_itemReader(itemReader), - m_oldSize(itemReader.extraSearchPathsStack().size()) -{ - if (!extraSearchPaths.isEmpty()) - m_itemReader.pushExtraSearchPaths(extraSearchPaths); -} - -SearchPathsManager::~SearchPathsManager() -{ - while (m_itemReader.extraSearchPathsStack().size() > m_oldSize) - m_itemReader.popExtraSearchPaths(); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h deleted file mode 100644 index 751c9b1b6..000000000 --- a/src/lib/corelib/language/itemreader.h +++ /dev/null @@ -1,121 +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$ -** -****************************************************************************/ - -#ifndef QBS_ITEMREADER_H -#define QBS_ITEMREADER_H - -#include -#include -#include - -#include - -#include - -namespace qbs { -namespace Internal { -class Evaluator; -class Item; -class ItemPool; -class ItemReaderVisitorState; - -/* - * Reads a qbs file and creates a tree of Item objects. - * - * In this stage the following steps are performed: - * - The QML/JS parser creates the AST. - * - The AST is converted to a tree of Item objects. - * - * This class is also responsible for the QMLish inheritance semantics. - */ -class ItemReader -{ -public: - ItemReader(Logger &logger); - ~ItemReader(); - - void setPool(ItemPool *pool) { m_pool = pool; } - void setSearchPaths(const QStringList &searchPaths); - void pushExtraSearchPaths(const QStringList &extraSearchPaths); - void popExtraSearchPaths(); - const std::vector &extraSearchPathsStack() const; - void setExtraSearchPathsStack(const std::vector &s); - void clearExtraSearchPathsStack(); - const QStringList &allSearchPaths() const; - - // 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 filesRead() const; - - void setEnableTiming(bool on); - qint64 elapsedTime() const { return m_elapsedTime; } - - 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 m_extraSearchPaths; - mutable QStringList m_allSearchPaths; - const std::unique_ptr m_visitorState; - qint64 m_elapsedTime = -1; -}; - -class SearchPathsManager { -public: - SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths = {}); - ~SearchPathsManager(); - -private: - ItemReader &m_itemReader; - size_t m_oldSize{0}; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_ITEMREADER_H diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp deleted file mode 100644 index f43104836..000000000 --- a/src/lib/corelib/language/itemreaderastvisitor.cpp +++ /dev/null @@ -1,391 +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 "itemreaderastvisitor.h" - -#include "astimportshandler.h" -#include "astpropertiesitemhandler.h" -#include "asttools.h" -#include "builtindeclarations.h" -#include "filecontext.h" -#include "identifiersearch.h" -#include "item.h" -#include "itemreadervisitorstate.h" -#include "value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace QbsQmlJS; - -namespace qbs { -namespace Internal { - -ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, - FileContextPtr file, ItemPool *itemPool, Logger &logger) - : m_visitorState(visitorState) - , m_file(std::move(file)) - , m_itemPool(itemPool) - , m_logger(logger) -{ -} - -bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram) -{ - ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file); - importsHandler.handleImports(uiProgram->imports); - m_typeNameToFile = importsHandler.typeNameFileMap(); - return true; -} - -static ItemValuePtr findItemProperty(const Item *container, const Item *item) -{ - ItemValuePtr itemValue; - const auto &srcprops = container->properties(); - auto it = std::find_if(srcprops.begin(), srcprops.end(), [item] (const ValuePtr &v) { - return v->type() == Value::ItemValueType - && std::static_pointer_cast(v)->item() == item; - }); - if (it != srcprops.end()) - itemValue = std::static_pointer_cast(it.value()); - return itemValue; -} - -bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) -{ - const QString typeName = ast->qualifiedTypeNameId->name.toString(); - const CodeLocation itemLocation = toCodeLocation(ast->qualifiedTypeNameId->identifierToken); - const Item *baseItem = nullptr; - Item *mostDerivingItem = nullptr; - - Item *item = Item::create(m_itemPool, ItemType::Unknown); - item->setFile(m_file); - item->setLocation(itemLocation); - - // Inheritance resolving, part 1: Find out our actual type name (needed for setting - // up children and alternatives). - const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId); - const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName); - ItemType itemType; - if (!baseTypeFileName.isEmpty()) { - const bool isMostDerivingItem = (m_visitorState.mostDerivingItem() == nullptr); - if (isMostDerivingItem) - m_visitorState.setMostDerivingItem(item); - mostDerivingItem = m_visitorState.mostDerivingItem(); - baseItem = m_visitorState.readFile(baseTypeFileName, m_file->searchPaths(), m_itemPool); - if (isMostDerivingItem) - m_visitorState.setMostDerivingItem(nullptr); - QBS_CHECK(baseItem->type() <= ItemType::LastActualItem); - itemType = baseItem->type(); - } else { - if (fullTypeName.size() > 1) { - throw ErrorInfo(Tr::tr("Invalid item '%1'. Did you mean to set a module property?") - .arg(fullTypeName.join(QLatin1Char('.'))), itemLocation); - } - itemType = BuiltinDeclarations::instance().typeForName(typeName, itemLocation); - checkDeprecationStatus(itemType, typeName, itemLocation); - if (itemType == ItemType::Properties && m_item && m_item->type() == ItemType::SubProject) - itemType = ItemType::PropertiesInSubProject; - } - - item->m_type = itemType; - - if (m_item) - Item::addChild(m_item, item); // Add this item to the children of the parent item. - else - m_item = item; // This is the root item. - - if (ast->initializer) { - Item *mdi = m_visitorState.mostDerivingItem(); - m_visitorState.setMostDerivingItem(nullptr); - qSwap(m_item, item); - const ItemType oldInstanceItemType = m_instanceItemType; - if (itemType == ItemType::Parameters || itemType == ItemType::Depends) - m_instanceItemType = ItemType::ModuleParameters; - ast->initializer->accept(this); - m_instanceItemType = oldInstanceItemType; - qSwap(m_item, item); - m_visitorState.setMostDerivingItem(mdi); - } - - ASTPropertiesItemHandler(item).handlePropertiesItems(); - - // Inheritance resolving, part 2 (depends on alternatives having been set up). - if (baseItem) { - inheritItem(item, baseItem); - if (baseItem->file()->idScope()) { - // Make ids from the derived file visible in the base file. - // ### Do we want to turn off this feature? It's QMLish but kind of strange. - item->file()->ensureIdScope(m_itemPool); - baseItem->file()->idScope()->setPrototype(item->file()->idScope()); - - // Replace the base item with the most deriving item. - ItemValuePtr baseItemIdValue = findItemProperty(baseItem->file()->idScope(), baseItem); - if (baseItemIdValue) - baseItemIdValue->setItem(mostDerivingItem); - } - } else { - // Only the item at the top of the inheritance chain is a built-in item. - // We cannot do this in "part 1", because then the visitor would complain about duplicate - // bindings. - item->setupForBuiltinType(m_visitorState.deprecationWarningMode(), m_logger); - } - - return false; -} - -void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName, - const AST::SourceLocation &sourceLocation) -{ - if (Q_UNLIKELY(item->hasOwnProperty(bindingName.last()))) { - QString msg = Tr::tr("Duplicate binding for '%1'"); - throw ErrorInfo(msg.arg(bindingName.join(QLatin1Char('.'))), - toCodeLocation(sourceLocation)); - } -} - -bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast) -{ - PropertyDeclaration p; - if (Q_UNLIKELY(ast->name.isEmpty())) - throw ErrorInfo(Tr::tr("public member without name")); - if (Q_UNLIKELY(ast->memberType.isEmpty())) - throw ErrorInfo(Tr::tr("public member without type")); - if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal)) - throw ErrorInfo(Tr::tr("public member with signal type not supported")); - p.setName(ast->name.toString()); - p.setType(PropertyDeclaration::propertyTypeFromString(ast->memberType.toString())); - if (p.type() == PropertyDeclaration::UnknownType) { - throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.") - .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken)); - } - if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) { - throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg( - ast->typeModifier.toString())); - } - if (ast->isReadonlyMember) - p.setFlags(PropertyDeclaration::ReadOnlyFlag); - - m_item->m_propertyDeclarations.insert(p.name(), p); - - const JSSourceValuePtr value = JSSourceValue::create(); - value->setFile(m_file); - if (ast->statement) { - handleBindingRhs(ast->statement, value); - const QStringList bindingName(p.name()); - checkDuplicateBinding(m_item, bindingName, ast->colonToken); - } - - m_item->setProperty(p.name(), value); - return false; -} - -bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) -{ - QBS_CHECK(ast->qualifiedId); - QBS_CHECK(!ast->qualifiedId->name.isEmpty()); - - const QStringList bindingName = toStringList(ast->qualifiedId); - - if (bindingName.length() == 1 && bindingName.front() == QStringLiteral("id")) { - const auto * const expStmt = AST::cast(ast->statement); - if (Q_UNLIKELY(!expStmt)) - throw ErrorInfo(Tr::tr("id: must be followed by identifier")); - const auto * const idExp = AST::cast(expStmt->expression); - if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) - throw ErrorInfo(Tr::tr("id: must be followed by identifier")); - m_item->m_id = idExp->name.toString(); - m_file->ensureIdScope(m_itemPool); - ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id()); - if (existingId) { - ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id())); - e.append(Tr::tr("First occurrence is here."), existingId->item()->location()); - e.append(Tr::tr("Next occurrence is here."), m_item->location()); - throw e; - } - m_file->idScope()->setProperty(m_item->id(), ItemValue::create(m_item)); - return false; - } - - const JSSourceValuePtr value = JSSourceValue::create(); - handleBindingRhs(ast->statement, value); - - Item * const targetItem = targetItemForBinding(bindingName, value); - checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken); - targetItem->setProperty(bindingName.last(), value); - return false; -} - -bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement, - const JSSourceValuePtr &value) -{ - QBS_CHECK(statement); - QBS_CHECK(value); - - if (AST::cast(statement)) - value->setHasFunctionForm(); - - value->setFile(m_file); - value->setSourceCode(textViewOf(m_file->content(), statement)); - value->setLocation(statement->firstSourceLocation().startLine, - statement->firstSourceLocation().startColumn); - - bool usesBase, usesOuter, usesOriginal; - IdentifierSearch idsearch; - idsearch.add(StringConstants::baseVar(), &usesBase); - idsearch.add(StringConstants::outerVar(), &usesOuter); - idsearch.add(StringConstants::originalVar(), &usesOriginal); - idsearch.start(statement); - if (usesBase) - value->setSourceUsesBase(); - if (usesOuter) - value->setSourceUsesOuter(); - if (usesOriginal) - value->setSourceUsesOriginal(); - return false; -} - -CodeLocation ItemReaderASTVisitor::toCodeLocation(const AST::SourceLocation &location) const -{ - return CodeLocation(m_file->filePath(), location.startLine, location.startColumn); -} - -Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName, - const JSSourceValueConstPtr &value) -{ - Item *targetItem = m_item; - const int c = bindingName.size() - 1; - for (int i = 0; i < c; ++i) { - ValuePtr v = targetItem->ownProperty(bindingName.at(i)); - if (!v) { - const ItemType itemType = i < c - 1 ? ItemType::ModulePrefix : m_instanceItemType; - Item *newItem = Item::create(m_itemPool, itemType); - newItem->setLocation(value->location()); - v = ItemValue::create(newItem); - targetItem->setProperty(bindingName.at(i), v); - } - if (Q_UNLIKELY(v->type() != Value::ItemValueType)) { - QString msg = Tr::tr("Binding to non-item property."); - throw ErrorInfo(msg, value->location()); - } - targetItem = std::static_pointer_cast(v)->item(); - } - return targetItem; -} - -void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src) -{ - int insertPos = 0; - for (Item *child : qAsConst(src->m_children)) { - dst->m_children.insert(insertPos++, child); - child->m_parent = dst; - } - - for (const PropertyDeclaration &pd : src->propertyDeclarations()) { - if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag) - && dst->hasOwnProperty(pd.name())) { - throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), - dst->property(pd.name())->location()); - } - dst->setPropertyDeclaration(pd.name(), pd); - } - - for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) { - ValuePtr &v = dst->m_properties[it.key()]; - if (!v) { - v = it.value(); - continue; - } - if (v->type() == Value::ItemValueType && it.value()->type() != Value::ItemValueType) - throw ErrorInfo(Tr::tr("Binding to non-item property."), v->location()); - if (v->type() != it.value()->type()) - continue; - switch (v->type()) { - case Value::JSSourceValueType: { - JSSourceValuePtr sv = std::static_pointer_cast(v); - QBS_CHECK(!sv->baseValue()); - const JSSourceValuePtr baseValue = std::static_pointer_cast(it.value()); - sv->setBaseValue(baseValue); - for (const JSSourceValue::Alternative &alt : sv->m_alternatives) - alt.value->setBaseValue(baseValue); - break; - } - case Value::ItemValueType: - inheritItem(std::static_pointer_cast(v)->item(), - std::static_pointer_cast(it.value())->item()); - break; - default: - QBS_CHECK(!"unexpected value type"); - } - } -} - -void ItemReaderASTVisitor::checkDeprecationStatus(ItemType itemType, const QString &itemName, - const CodeLocation &itemLocation) -{ - const ItemDeclaration itemDecl = BuiltinDeclarations::instance().declarationsForType(itemType); - const ErrorInfo error = itemDecl.checkForDeprecation(m_visitorState.deprecationWarningMode(), - itemName, itemLocation, m_logger); - if (error.hasError()) - throw error; -} - -void ItemReaderASTVisitor::doCheckItemTypes(const Item *item) -{ - const ItemDeclaration decl = BuiltinDeclarations::instance().declarationsForType(item->type()); - for (const Item * const child : item->children()) { - if (!decl.isChildTypeAllowed(child->type())) { - throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") - .arg(item->typeName(), child->typeName()), child->location()); - } - doCheckItemTypes(child); - } -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h deleted file mode 100644 index e8522efd6..000000000 --- a/src/lib/corelib/language/itemreaderastvisitor.h +++ /dev/null @@ -1,97 +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$ -** -****************************************************************************/ - -#ifndef QBS_ITEMREADERASTVISITOR_H -#define QBS_ITEMREADERASTVISITOR_H - -#include "forward_decls.h" -#include "itemtype.h" - -#include -#include - -#include -#include - -namespace qbs { -class CodeLocation; - -namespace Internal { -class Item; -class ItemPool; -class ItemReaderVisitorState; - -class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor -{ -public: - ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, FileContextPtr file, - ItemPool *itemPool, Logger &logger); - void checkItemTypes() { doCheckItemTypes(rootItem()); } - - Item *rootItem() const { return m_item; } - -private: - bool visit(QbsQmlJS::AST::UiProgram *uiProgram) override; - bool visit(QbsQmlJS::AST::UiObjectDefinition *ast) override; - bool visit(QbsQmlJS::AST::UiPublicMember *ast) override; - bool visit(QbsQmlJS::AST::UiScriptBinding *ast) override; - - bool handleBindingRhs(QbsQmlJS::AST::Statement *statement, const JSSourceValuePtr &value); - CodeLocation toCodeLocation(const QbsQmlJS::AST::SourceLocation &location) const; - void checkDuplicateBinding(Item *item, const QStringList &bindingName, - const QbsQmlJS::AST::SourceLocation &sourceLocation); - Item *targetItemForBinding(const QStringList &binding, const JSSourceValueConstPtr &value); - static void inheritItem(Item *dst, const Item *src); - void checkDeprecationStatus(ItemType itemType, const QString &itemName, - const CodeLocation &itemLocation); - void doCheckItemTypes(const Item *item); - - ItemReaderVisitorState &m_visitorState; - const FileContextPtr m_file; - ItemPool * const m_itemPool; - Logger &m_logger; - QHash m_typeNameToFile; - Item *m_item = nullptr; - ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_ITEMREADERASTVISITOR_H diff --git a/src/lib/corelib/language/itemreadervisitorstate.cpp b/src/lib/corelib/language/itemreadervisitorstate.cpp deleted file mode 100644 index a51b7eab4..000000000 --- a/src/lib/corelib/language/itemreadervisitorstate.cpp +++ /dev/null @@ -1,195 +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 "itemreadervisitorstate.h" - -#include "asttools.h" -#include "filecontext.h" -#include "itemreaderastvisitor.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace qbs { -namespace Internal { - -class ASTCacheValueData : public QSharedData -{ - Q_DISABLE_COPY(ASTCacheValueData) -public: - ASTCacheValueData() - : ast(nullptr) - , processing(false) - { - } - - QString code; - QbsQmlJS::Engine engine; - QbsQmlJS::AST::UiProgram *ast; - bool processing; -}; - -class ASTCacheValue -{ -public: - ASTCacheValue() - : d(new ASTCacheValueData) - { - } - - ASTCacheValue(const ASTCacheValue &other) = default; - - void setProcessingFlag(bool b) { d->processing = b; } - bool isProcessing() const { return d->processing; } - - void setCode(const QString &code) { d->code = code; } - QString code() const { return d->code; } - - QbsQmlJS::Engine *engine() const { return &d->engine; } - - void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; } - QbsQmlJS::AST::UiProgram *ast() const { return d->ast; } - bool isValid() const { return d->ast; } - -private: - QExplicitlySharedDataPointer d; -}; - -class ItemReaderVisitorState::ASTCache : public std::unordered_map {}; - - -ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger) - : m_logger(logger) - , m_astCache(std::make_unique()) -{ - -} - -ItemReaderVisitorState::~ItemReaderVisitorState() = default; - -Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringList &searchPaths, - ItemPool *itemPool) -{ - ASTCacheValue &cacheValue = (*m_astCache)[filePath]; - if (cacheValue.isValid()) { - if (Q_UNLIKELY(cacheValue.isProcessing())) - throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); - } else { - QFile file(filePath); - if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) - throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); - - m_filesRead.insert(filePath); - QTextStream stream(&file); - setupDefaultCodec(stream); - const QString &code = stream.readAll(); - QbsQmlJS::Lexer lexer(cacheValue.engine()); - lexer.setCode(code, 1); - QbsQmlJS::Parser parser(cacheValue.engine()); - - file.close(); - if (!parser.parse()) { - const QList &parserMessages = parser.diagnosticMessages(); - if (Q_UNLIKELY(!parserMessages.empty())) { - ErrorInfo err; - for (const QbsQmlJS::DiagnosticMessage &msg : parserMessages) - err.append(msg.message, toCodeLocation(filePath, msg.loc)); - throw err; - } - } - - cacheValue.setCode(code); - cacheValue.setAst(parser.ast()); - } - - const FileContextPtr file = FileContext::create(); - file->setFilePath(QFileInfo(filePath).absoluteFilePath()); - file->setContent(cacheValue.code()); - file->setSearchPaths(searchPaths); - - ItemReaderASTVisitor astVisitor(*this, file, itemPool, m_logger); - { - class ProcessingFlagManager { - public: - ProcessingFlagManager(ASTCacheValue &v) : m_cacheValue(v) { v.setProcessingFlag(true); } - ~ProcessingFlagManager() { m_cacheValue.setProcessingFlag(false); } - private: - ASTCacheValue &m_cacheValue; - } processingFlagManager(cacheValue); - cacheValue.ast()->accept(&astVisitor); - } - astVisitor.checkItemTypes(); - return astVisitor.rootItem(); -} - -void ItemReaderVisitorState::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) -{ - m_directoryEntries.insert(dirPath, entries); -} - -bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const -{ - const auto it = m_directoryEntries.constFind(dirPath); - if (it == m_directoryEntries.constEnd()) - return false; - *entries = it.value(); - return true; -} - -Item *ItemReaderVisitorState::mostDerivingItem() const -{ - return m_mostDerivingItem; -} - -void ItemReaderVisitorState::setMostDerivingItem(Item *item) -{ - m_mostDerivingItem = item; -} - - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/itemreadervisitorstate.h b/src/lib/corelib/language/itemreadervisitorstate.h deleted file mode 100644 index dc22cfb42..000000000 --- a/src/lib/corelib/language/itemreadervisitorstate.h +++ /dev/null @@ -1,89 +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$ -** -****************************************************************************/ -#ifndef QBS_ITEMREADERVISITORSTATE_H -#define QBS_ITEMREADERVISITORSTATE_H - -#include -#include - -#include - -#include - -namespace qbs { -namespace Internal { -class Item; -class ItemPool; -class Logger; - -class ItemReaderVisitorState -{ -public: - ItemReaderVisitorState(Logger &logger); - ~ItemReaderVisitorState(); - - Logger &logger() { return m_logger; } - Set filesRead() const { return m_filesRead; } - - Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool); - - void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); - bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; - - Item *mostDerivingItem() const; - void setMostDerivingItem(Item *item); - - void setDeprecationWarningMode(DeprecationWarningMode mode) { m_deprecationWarningMode = mode; } - DeprecationWarningMode deprecationWarningMode() const { return m_deprecationWarningMode; } - -private: - DeprecationWarningMode m_deprecationWarningMode = defaultDeprecationWarningMode(); - Logger &m_logger; - Set m_filesRead; - QHash m_directoryEntries; - Item *m_mostDerivingItem = nullptr; - - class ASTCache; - const std::unique_ptr m_astCache; -}; - -} // namespace Internal -} // namespace qbs - -#endif // Include guard. diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index fe92caba9..58ca987e9 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -41,7 +41,6 @@ #include "artifactproperties.h" #include "builtindeclarations.h" -#include "productitemmultiplexer.h" #include "propertymapinternal.h" #include "scriptengine.h" @@ -52,6 +51,7 @@ #include // TODO: Move to language? #include #include +#include #include #include #include diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp deleted file mode 100644 index 00d944e5f..000000000 --- a/src/lib/corelib/language/loader.cpp +++ /dev/null @@ -1,215 +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 "loader.h" - -#include "evaluator.h" -#include "itempool.h" -#include "language.h" -#include "projectresolver.h" -#include "projecttreebuilder.h" -#include "scriptengine.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace qbs { -namespace Internal { - -Loader::Loader(ScriptEngine *engine, Logger logger) - : m_logger(std::move(logger)) - , m_progressObserver(nullptr) - , m_engine(engine) -{ - m_logger.storeWarnings(); -} - -void Loader::setProgressObserver(ProgressObserver *observer) -{ - m_progressObserver = observer; -} - -void Loader::setSearchPaths(const QStringList &_searchPaths) -{ - QStringList searchPaths; - for (const QString &searchPath : _searchPaths) { - if (!FileInfo::exists(searchPath)) { - m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.") - .arg(QDir::toNativeSeparators(searchPath)); - } else { - searchPaths += searchPath; - } - } - - m_searchPaths = searchPaths; -} - -void Loader::setOldProjectProbes(const std::vector &oldProbes) -{ - m_oldProjectProbes = oldProbes; -} - -void Loader::setOldProductProbes(const QHash> &oldProbes) -{ - m_oldProductProbes = oldProbes; -} - -void Loader::setStoredProfiles(const QVariantMap &profiles) -{ - m_storedProfiles = profiles; -} - -void Loader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo) -{ - m_storedModuleProviderInfo = providerInfo; -} - -TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters) -{ - SetupProjectParameters parameters = _parameters; - - if (parameters.topLevelProfile().isEmpty()) { - Settings settings(parameters.settingsDirectory()); - QString profileName = settings.defaultProfile(); - if (profileName.isEmpty()) { - m_logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. " - "Using default property values."); - profileName = Profile::fallbackName(); - } - parameters.setTopLevelProfile(profileName); - parameters.expandBuildConfiguration(); - } - - setupProjectFilePath(parameters); - QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); - m_logger.qbsDebug() << "Using project file '" - << QDir::toNativeSeparators(parameters.projectFilePath()) << "'."; - - m_engine->setEnvironment(parameters.adjustedEnvironment()); - m_engine->checkAndClearException({}); - m_engine->clearImportsCache(); - m_engine->clearRequestedProperties(); - m_engine->enableProfiling(parameters.logElapsedTime()); - m_logger.clearWarnings(); - EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation); - - // At this point, we cannot set a sensible total effort, because we know nothing about - // the project yet. That's why we use a placeholder here, so the user at least - // sees that an operation is starting. The real total effort will be set later when - // we have enough information. - if (m_progressObserver) { - m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1") - .arg(TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())), 1); - m_progressObserver->setScriptEngine(m_engine); - } - - const FileTime resolveTime = FileTime::currentTime(); - Evaluator evaluator(m_engine); - 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(); - project->lastStartResolveTime = resolveTime; - project->lastEndResolveTime = FileTime::currentTime(); - - // E.g. if the top-level project is disabled. - if (m_progressObserver) - m_progressObserver->setFinished(); - - return project; -} - -void Loader::setupProjectFilePath(SetupProjectParameters ¶meters) -{ - QString projectFilePath = parameters.projectFilePath(); - if (projectFilePath.isEmpty()) - projectFilePath = QDir::currentPath(); - const QFileInfo projectFileInfo(projectFilePath); - if (!projectFileInfo.exists()) - throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath)); - if (projectFileInfo.isRelative()) - projectFilePath = projectFileInfo.absoluteFilePath(); - if (projectFileInfo.isFile()) { - parameters.setProjectFilePath(projectFilePath); - return; - } - if (!projectFileInfo.isDir()) - throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath)); - - const QStringList &actualFileNames - = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); - if (actualFileNames.empty()) { - QString error; - if (parameters.projectFilePath().isEmpty()) - error = Tr::tr("No project file given and none found in current directory.\n"); - else - error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath); - throw ErrorInfo(error); - } - if (actualFileNames.size() > 1) { - throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") - .arg(projectFilePath)); - } - projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front()); - - projectFilePath = QDir::current().filePath(projectFilePath); - projectFilePath = QDir::cleanPath(projectFilePath); - parameters.setProjectFilePath(projectFilePath); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/language/loader.h deleted file mode 100644 index 2c8b08446..000000000 --- a/src/lib/corelib/language/loader.h +++ /dev/null @@ -1,88 +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$ -** -****************************************************************************/ -#ifndef QBS_LOADER_H -#define QBS_LOADER_H - -#include "forward_decls.h" -#include "moduleproviderinfo.h" -#include -#include - -#include - -namespace qbs { -class Settings; -class SetupProjectParameters; -namespace Internal { -class Logger; -class ProgressObserver; -class ScriptEngine; - -class QBS_AUTOTEST_EXPORT Loader -{ -public: - Loader(ScriptEngine *engine, Logger logger); - - void setProgressObserver(ProgressObserver *observer); - void setSearchPaths(const QStringList &searchPaths); - void setOldProjectProbes(const std::vector &oldProbes); - void setOldProductProbes(const QHash> &oldProbes); - void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } - void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo); - TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); - - static void setupProjectFilePath(SetupProjectParameters ¶meters); - -private: - Logger m_logger; - ProgressObserver *m_progressObserver; - ScriptEngine * const m_engine; - QStringList m_searchPaths; - std::vector m_oldProjectProbes; - QHash> m_oldProductProbes; - StoredModuleProviderInfo m_storedModuleProviderInfo; - QVariantMap m_storedProfiles; - FileTime m_lastResolveTime; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_LOADER_H diff --git a/src/lib/corelib/language/localprofiles.cpp b/src/lib/corelib/language/localprofiles.cpp deleted file mode 100644 index 1571ee15e..000000000 --- a/src/lib/corelib/language/localprofiles.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include - -namespace qbs::Internal { -class LocalProfiles::Private -{ -public: - Private(const SetupProjectParameters ¶meters, 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 ¶meters; - Evaluator &evaluator; - Logger &logger; - QVariantMap profiles; -}; - -LocalProfiles::LocalProfiles(const SetupProjectParameters ¶meters, 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(it.value())->item(), - profileItem, values); - break; - case Value::VariantValueType: - values.insert(name.join(QLatin1Char('.')), - std::static_pointer_cast(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 deleted file mode 100644 index 3e6b77f4d..000000000 --- a/src/lib/corelib/language/localprofiles.h +++ /dev/null @@ -1,68 +0,0 @@ -/**************************************************************************** -** -** 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 - -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 ¶meters, 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 deleted file mode 100644 index 91776213a..000000000 --- a/src/lib/corelib/language/moduleinstantiator.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include -#include - -#include - -namespace qbs::Internal { -class ModuleInstantiator::Private -{ -public: - Private(const SetupProjectParameters ¶meters, 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 - getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName, - const QString &id, bool replace); - - const SetupProjectParameters ¶meters; - ItemPool &itemPool; - ModulePropertyMerger &propertyMerger; - Logger &logger; - qint64 elapsedTime = 0; -}; - -ModuleInstantiator::ModuleInstantiator( - const SetupProjectParameters ¶meters, 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 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(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..: syntax. - // For simplicity and backwards compatibility, qbs properties can also be given without - // the "modules." prefix, i.e. just qbs.:. - // In addition, users can override module properties just for certain products - // using the products...: 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 deleted file mode 100644 index f235b83fa..000000000 --- a/src/lib/corelib/language/moduleinstantiator.h +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** -** -** 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 - -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 ¶meters, 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 deleted file mode 100644 index 1772ee808..000000000 --- a/src/lib/corelib/language/moduleloader.cpp +++ /dev/null @@ -1,529 +0,0 @@ -/**************************************************************************** -** -** 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 "moduleloader.h" - -#include "evaluator.h" -#include "itemreader.h" -#include "moduleproviderloader.h" -#include "productitemmultiplexer.h" -#include "value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -namespace qbs::Internal { - -class ModuleLoader::Private -{ -public: - Private(const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader, - ItemReader &itemReader, Evaluator &evaluator, Logger &logger) - : setupParameters(setupParameters), providerLoader(providerLoader), - itemReader(itemReader), evaluator(evaluator), logger(logger) {} - - std::pair loadModuleFile(const ProductContext &product, - const QString &moduleName, const QString &filePath); - std::pair 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; - ModuleProviderLoader &providerLoader; - ItemReader &itemReader; - Evaluator &evaluator; - Logger &logger; - - // The keys are file paths, the values are module prototype items accompanied by a profile. - std::unordered_map>> modulePrototypes; - - // The keys are module prototypes and products, the values specify whether the module's - // condition is true for that product. - std::unordered_map, bool> modulePrototypeEnabledInfo; - - std::unordered_map> unknownProfilePropertyErrors; - std::unordered_map parameterDeclarations; - std::unordered_map> providerConfigsPerProduct; - QHash, std::optional> existingModulePathCache; - std::map moduleDirListCache; - - qint64 elapsedTimeModuleProviders = 0; -}; - -ModuleLoader::ModuleLoader( - const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader, - ItemReader &itemReader, Evaluator &evaluator, Logger &logger) - : d(new Private(setupParameters, providerLoader, itemReader, evaluator, logger)) { } - -ModuleLoader::~ModuleLoader() { delete d; } - -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 &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; -} - -ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile( - const 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 = d->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 = d->itemReader.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; - }; - - SearchPathsManager searchPathsManager(d->itemReader); - - Result loadResult; - auto existingPaths = findExistingModulePaths(); - if (existingPaths.isEmpty()) { // no suitable names found, try to use providers - AccumulatingTimer providersTimer( - d->setupParameters.logElapsedTime() ? &d->elapsedTimeModuleProviders : nullptr); - std::optional &providerConfig - = d->providerConfigsPerProduct[productContext.productItem]; - auto result = d->providerLoader.executeModuleProviders( - {productContext.productItem, productContext.projectItem, productContext.name, - productContext.uniqueName, productContext.moduleProperties, providerConfig}, - dependsItemLocation, - moduleName, - fallbackMode); - loadResult.providerProbes << result.probes; - if (!providerConfig) - providerConfig = result.providerConfig; - if (result.searchPaths) { - qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() - << "with newly added search paths from module provider"; - d->itemReader.pushExtraSearchPaths(*result.searchPaths); - existingPaths = findExistingModulePaths(); - } - } - - const auto getModuleFileNames = [&](const QString &dirPath) -> QStringList & { - QStringList &moduleFileNames = d->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 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] = d->loadModuleFile(productContext, 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) { - loadResult.moduleItem = createNonPresentModule( - *productContext.projectItem->pool(), fullName, QStringLiteral("not found"), - nullptr); - return loadResult; - } - if (Q_UNLIKELY(triedToLoadModule)) { - throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), - dependsItemLocation); - } - return loadResult; - } - - if (candidates.size() == 1) { - loadResult.moduleItem = candidates.at(0).item; - } else { - for (auto &candidate : candidates) { - candidate.priority = d->evaluator.intValue(candidate.item, - StringConstants::priorityProperty(), - candidate.priority); - } - loadResult.moduleItem = chooseModuleCandidate(candidates, fullName); - } - - const QString fullProductName = ProductItemMultiplexer::fullProductDisplayName( - productContext.name, productContext.multiplexId); - const auto it = d->unknownProfilePropertyErrors.find(loadResult.moduleItem); - 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.toString(), fullProductName, productContext.profile)); - for (const ErrorInfo &e : it->second) - error.append(e.toString()); - handlePropertyError(error, d->setupParameters, d->logger); - } - - return loadResult; -} - -std::pair ModuleLoader::Private::loadModuleFile( - const ProductContext &product, const QString &moduleName, const QString &filePath) -{ - qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath; - - const auto [module, triedToLoad] = getModulePrototype(product, moduleName, filePath); - if (!module) - return {nullptr, triedToLoad}; - - const auto key = std::make_pair(module, product.productItem); - const auto it = modulePrototypeEnabledInfo.find(key); - if (it != modulePrototypeEnabledInfo.end()) { - qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; - return {it->second ? module : nullptr, triedToLoad}; - } - - if (!evaluateModuleCondition(product, module, moduleName)) { - qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false"; - modulePrototypeEnabledInfo.insert({key, false}); - return {nullptr, triedToLoad}; - } - - 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(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 { - Item::PropertyDeclarationMap decls; - const auto &moduleChildren = module->children(); - for (Item *param : moduleChildren) { - if (param->type() != ItemType::Parameter) - continue; - const auto ¶mDecls = param->propertyDeclarations(); - for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) - decls.insert(it.key(), it.value()); - } - parameterDeclarations.insert({module, decls}); - } - - modulePrototypeEnabledInfo.insert({key, true}); - return {module, triedToLoad}; -} - -std::pair ModuleLoader::Private::getModulePrototype( - const ProductContext &product, const QString &moduleName, const QString &filePath) -{ - 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}; - } - } - - 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}; - } - prototypeList.emplace_back(module, product.profile); - - // 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 {module, true}; -} - -bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product, - Item *module, const QString &fullModuleName) -{ - // Evaluator reqires module name to be set. - module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName)); - - // 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.productItem->property(StringConstants::qbsModule())); - } - } - ~TempQbsModuleProvider() - { - if (!m_needsQbsItem) - return; - if (m_prevQbsItemValue) - m_module->setProperty(StringConstants::qbsModule(), m_prevQbsItemValue); - else - m_module->removeProperty(StringConstants::qbsModule()); - } - private: - Item * const m_module; - ValuePtr m_prevQbsItemValue; - const bool m_needsQbsItem; - }; - - const TempQbsModuleProvider tempQbs(product, module, fullModuleName); - return evaluator.boolValue(module, StringConstants::conditionProperty()); -} - -class DependencyParameterDeclarationCheck -{ -public: - DependencyParameterDeclarationCheck( - const QString &productName, const Item *productItem, - const std::unordered_map &decls) - : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls) - {} - - void operator()(const QVariantMap ¶meters) const { check(parameters, QualifiedId()); } - -private: - void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const - { - for (auto it = parameters.begin(); it != parameters.end(); ++it) { - if (it.value().userType() == QMetaType::QVariantMap) { - check(it.value().toMap(), QualifiedId(moduleName) << it.key()); - } else { - const auto &deps = m_productItem->modules(); - auto m = std::find_if(deps.begin(), deps.end(), - [&moduleName] (const Item::Module &module) { - 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()), - m_productItem->location()); - } - - 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()); - } - } - } - } - - bool moduleExists(const QualifiedId &name) const - { - const auto &deps = m_productItem->modules(); - return any_of(deps, [&name](const Item::Module &module) { - return module.name == name; - }); - } - - const QString &m_productName; - const Item * const m_productItem; - const std::unordered_map &m_parameterDeclarations; -}; - -void ModuleLoader::checkDependencyParameterDeclarations(const Item *productItem, - const QString &productName) const -{ - DependencyParameterDeclarationCheck dpdc(productName, productItem, d->parameterDeclarations); - for (const Item::Module &dep : productItem->modules()) { - if (!dep.parameters.empty()) - dpdc(dep.parameters); - } -} - -void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem, - const Item::Modules &modules) -{ - for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - d->forwardParameterDeclarations(it.key(), - std::static_pointer_cast(it.value())->item(), - 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(parameterDeclarations[it->item->rootPrototype()]); - } else { - for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - forwardParameterDeclarations(QualifiedId(moduleName) << it.key(), - std::static_pointer_cast(it.value())->item(), - modules); - } - } -} - -void ModuleLoader::printProfilingInfo(int indent) -{ - if (!d->setupParameters.logElapsedTime()) - return; - d->logger.qbsLog(LoggerInfo, true) - << QByteArray(indent, ' ') - << Tr::tr("Running module providers took %1.") - .arg(elapsedTimeString(d->elapsedTimeModuleProviders)); -} - -} // namespace qbs::Internal diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h deleted file mode 100644 index 37ed34c08..000000000 --- a/src/lib/corelib/language/moduleloader.h +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************** -** -** 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 "item.h" - -#include -#include - -#include - -namespace qbs { -class CodeLocation; -class SetupProjectParameters; -namespace Internal { -class Evaluator; -enum class FallbackMode; -class ItemReader; -class Logger; -class ModuleProviderLoader; - -class ModuleLoader -{ -public: - ModuleLoader(const SetupProjectParameters &setupParameters, - ModuleProviderLoader &providerLoader, ItemReader &itemReader, - Evaluator &evaluator, Logger &logger); - ~ModuleLoader(); - - struct ProductContext { - Item * const productItem; - const Item * const projectItem; - const QString &name; - const QString &uniqueName; - const QString &profile; - const QString &multiplexId; - const QVariantMap &moduleProperties; - const QVariantMap &profileModuleProperties; - }; - struct Result { - Item *moduleItem = nullptr; - std::vector providerProbes; - }; - Result searchAndLoadModuleFile(const ProductContext &productContext, - const CodeLocation &dependsItemLocation, - const QualifiedId &moduleName, - FallbackMode fallbackMode, bool isRequired); - - void checkDependencyParameterDeclarations(const Item *productItem, - const QString &productName) const; - void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules); - void printProfilingInfo(int indent); - -private: - class Private; - Private * const d; -}; - -} // namespace Internal -} // namespace qbs - diff --git a/src/lib/corelib/language/modulepropertymerger.cpp b/src/lib/corelib/language/modulepropertymerger.cpp deleted file mode 100644 index d45329d12..000000000 --- a/src/lib/corelib/language/modulepropertymerger.cpp +++ /dev/null @@ -1,304 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include - -namespace qbs::Internal { -class ModulePropertyMerger::Private -{ -public: - Private(const SetupProjectParameters ¶meters, 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 ¤tHead, 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 ¶meters; - 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 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 ¶meters, 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 ¤tHead, 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> 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( - 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(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 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 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 deleted file mode 100644 index fc388cfbf..000000000 --- a/src/lib/corelib/language/modulepropertymerger.h +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************** -** -** 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 - -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 ¶meters, 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 deleted file mode 100644 index 22605bf9f..000000000 --- a/src/lib/corelib/language/moduleproviderloader.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) -** 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 "moduleproviderloader.h" - -#include "builtindeclarations.h" -#include "evaluator.h" -#include "item.h" -#include "itemreader.h" -#include "probesresolver.h" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -namespace qbs { -namespace Internal { - -ModuleProviderLoader::ModuleProviderLoader( - const SetupProjectParameters ¶meters, ItemReader &reader, Evaluator &evaluator, - ProbesResolver &probesResolver, Logger &logger) - : m_parameters(parameters) - , m_reader(reader) - , m_evaluator(evaluator) - , m_probesResolver(probesResolver) - , m_logger(logger) -{ -} - -ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders( - const ProductContext &productContext, - const CodeLocation &dependsItemLocation, - const QualifiedId &moduleName, - FallbackMode fallbackMode) -{ - ModuleProviderLoader::ModuleProviderResult result; - std::vector providersToRun; - qCDebug(lcModuleLoader) << "Module" << moduleName.toString() - << "not found, checking for module providers"; - const auto providerNames = getModuleProviders(productContext.productItem); - if (providerNames) { - providersToRun = transformed>(*providerNames, [](const auto &name) { - return Provider{name, ModuleProviderLookup::Named}; }); - } else { - for (QualifiedId providerName = moduleName; !providerName.empty(); - providerName.pop_back()) { - providersToRun.push_back({providerName, ModuleProviderLookup::Scoped}); - } - } - result = executeModuleProvidersHelper(productContext, dependsItemLocation, providersToRun); - - if (fallbackMode == FallbackMode::Enabled - && !result.providerFound - && !providerNames) { - qCDebug(lcModuleLoader) << "Specific module provider not found for" - << moduleName.toString() << ", setting up fallback."; - result = executeModuleProvidersHelper( - productContext, - dependsItemLocation, - {{moduleName, ModuleProviderLookup::Fallback}}); - } - - return result; -} - -ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper( - const ProductContext &product, - const CodeLocation &dependsItemLocation, - const std::vector &providers) -{ - if (providers.empty()) - return {}; - QStringList allSearchPaths; - ModuleProviderResult result; - result.providerConfig = product.providerConfig ? *product.providerConfig - : getModuleProviderConfig(product); - const auto qbsModule = evaluateQbsModule(product); - for (const auto &[name, lookupType] : providers) { - const QVariantMap config = result.providerConfig.value(name.toString()).toMap(); - ModuleProviderInfo &info = m_storedModuleProviderInfo.providers[ - {name.toString(), config, qbsModule, int(lookupType)}]; - const bool fromCache = !info.name.isEmpty(); - if (!fromCache) { - info.name = name; - info.config = config; - info.providerFile = findModuleProviderFile(name, lookupType); - if (!info.providerFile.isEmpty()) { - qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile; - const auto evalResult = evaluateModuleProvider( - product, dependsItemLocation, name, info.providerFile, config, qbsModule); - info.searchPaths = evalResult.first; - result.probes << evalResult.second; - info.transientOutput = m_parameters.dryRun(); - } - } - if (info.providerFile.isEmpty()) { - if (lookupType == ModuleProviderLookup::Named) - throw ErrorInfo(Tr::tr("Unknown provider '%1'").arg(name.toString())); - continue; - } - if (fromCache) - qCDebug(lcModuleLoader) << "Re-using provider" << name.toString() << "from cache"; - - result.providerFound = true; - if (info.searchPaths.empty()) { - qCDebug(lcModuleLoader) - << "Module provider did run, but did not set up any modules."; - continue; - } - qCDebug(lcModuleLoader) << "Module provider added" << info.searchPaths.size() - << "new search path(s)"; - - allSearchPaths << info.searchPaths; - } - if (allSearchPaths.isEmpty()) - return result; - - result.searchPaths = std::move(allSearchPaths); - - return result; -} - -QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product) -{ - QVariantMap providerConfig; - const ItemValueConstPtr configItemValue = - product.productItem->itemProperty(StringConstants::moduleProviders()); - if (configItemValue) { - const std::function collectMap - = [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) { - const Item::PropertyMap &props = item->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QVariant value; - switch (it.value()->type()) { - case Value::ItemValueType: { - const auto childItem = static_cast(it.value().get())->item(); - childItem->setScope(item->scope()); - collectMap(childItem, QualifiedId(name) << it.key()); - continue; - } - case Value::JSSourceValueType: { - const ScopedJsValue sv(m_evaluator.engine()->context(), - m_evaluator.value(item, it.key())); - value = getJsVariant(m_evaluator.engine()->context(), sv); - break; - } - case Value::VariantValueType: - value = static_cast(it.value().get())->value(); - break; - } - QVariantMap m = providerConfig.value(name.toString()).toMap(); - m.insert(it.key(), value); - providerConfig.insert(name.toString(), m); - } - }; - configItemValue->item()->setScope(product.productItem); - collectMap(configItemValue->item(), QualifiedId()); - } - for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) { - if (!it.key().startsWith(QStringLiteral("moduleProviders."))) - continue; - const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size()); - const QVariantMap providerConfigFromBuildConfig = it.value().toMap(); - if (providerConfigFromBuildConfig.empty()) - continue; - QVariantMap currentMapForProvider = providerConfig.value(provider).toMap(); - for (auto propIt = providerConfigFromBuildConfig.begin(); - propIt != providerConfigFromBuildConfig.end(); ++propIt) { - currentMapForProvider.insert(propIt.key(), propIt.value()); - } - providerConfig.insert(provider, currentMapForProvider); - } - return providerConfig; -} - -std::optional> ModuleProviderLoader::getModuleProviders(Item *item) -{ - while (item) { - const auto providers = - m_evaluator.optionalStringListValue(item, StringConstants::qbsModuleProviders()); - if (providers) { - return transformed>(*providers, [](const auto &provider) { - return QualifiedId::fromString(provider); }); - } - item = item->parent(); - } - return std::nullopt; -} - -QString ModuleProviderLoader::findModuleProviderFile( - const QualifiedId &name, ModuleProviderLookup lookupType) -{ - for (const QString &path : m_reader.allSearchPaths()) { - QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers")); - switch (lookupType) { - case ModuleProviderLookup::Named: { - const auto result = - FileInfo::resolvePath(fullPath, name.toString() + QStringLiteral(".qbs")); - if (FileInfo::exists(result)) { - fullPath = result; - break; - } - [[fallthrough]]; - } - case ModuleProviderLookup::Scoped: - for (const QString &component : name) - fullPath = FileInfo::resolvePath(fullPath, component); - fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs")); - break; - case ModuleProviderLookup::Fallback: - fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback/provider.qbs")); - break; - } - if (!FileInfo::exists(fullPath)) { - qCDebug(lcModuleLoader) << "No module provider found at" << fullPath; - continue; - } - return fullPath; - } - return {}; -} - -QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const -{ - const QString properties[] = { - QStringLiteral("sysroot"), - QStringLiteral("toolchain"), - }; - const auto qbsItemValue = std::static_pointer_cast( - product.productItem->property(StringConstants::qbsModule())); - QVariantMap result; - for (const auto &property : properties) { - const ScopedJsValue val(m_evaluator.engine()->context(), - m_evaluator.value(qbsItemValue->item(), property)); - auto value = getJsVariant(m_evaluator.engine()->context(), val); - 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; -} - -Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule) -{ - const auto qbsItemValue = std::static_pointer_cast( - product.productItem->property(StringConstants::qbsModule())); - - Item *fakeQbsModule = Item::create(product.productItem->pool(), ItemType::Scope); - - for (auto it = qbsModule.begin(), end = qbsModule.end(); it != end; ++it) { - fakeQbsModule->setProperty(it.key(), VariantValue::create(it.value())); - } - - Item *scope = Item::create(product.productItem->pool(), ItemType::Scope); - scope->setFile(qbsItemValue->item()->file()); - scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule)); - return scope; -} - -std::pair > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product, - const CodeLocation &dependsItemLocation, - const QualifiedId &name, - const QString &providerFile, - const QVariantMap &moduleConfig, - const QVariantMap &qbsModule) -{ - QTemporaryFile dummyItemFile; - if (!dummyItemFile.open()) { - throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider " - "for dependency '%1': %2").arg(name.toString(), - dummyItemFile.errorString())); - } - m_tempQbsFiles << dummyItemFile.fileName(); - qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile; - const QString projectBuildDir = product.projectItem->variantProperty( - StringConstants::buildDirectoryProperty())->value().toString(); - const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); - - // include qbs module into hash - auto jsConfig = moduleConfig; - jsConfig[StringConstants::qbsModule()] = qbsModule; - - QTextStream stream(&dummyItemFile); - using Qt::endl; - setupDefaultCodec(stream); - stream << "import qbs.FileInfo" << endl; - stream << "import qbs.Utilities" << endl; - stream << "import '" << providerFile << "' as Provider" << endl; - stream << "Provider {" << endl; - stream << " name: " << toJSLiteral(name.toString()) << endl; - stream << " property var config: (" << toJSLiteral(jsConfig) << ')' << endl; - stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, " - " Utilities.getHash(JSON.stringify(config)))" << endl; - stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl; - stream << " property stringList searchPaths: (relativeSearchPaths || [])" - " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })" - << endl; - stream << "}" << endl; - stream.flush(); - 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.") - .arg(providerFile, providerItem->typeName(), - BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider))); - } - - providerItem->setScope(createProviderScope(product, qbsModule)); - providerItem->overrideProperties(moduleConfig, name, m_parameters, m_logger); - std::vector probes = m_probesResolver.resolveProbes( - {product.name, product.uniqueName}, providerItem); - - EvalContextSwitcher contextSwitcher(m_evaluator.engine(), EvalContext::ModuleProvider); - return std::make_pair(m_evaluator.stringListValue(providerItem, QStringLiteral("searchPaths")), - std::move(probes)); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleproviderloader.h b/src/lib/corelib/language/moduleproviderloader.h deleted file mode 100644 index b6240cb03..000000000 --- a/src/lib/corelib/language/moduleproviderloader.h +++ /dev/null @@ -1,146 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) -** 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$ -** -****************************************************************************/ - -#ifndef MODULEPROVIDERLOADER_H -#define MODULEPROVIDERLOADER_H - -#include "forward_decls.h" -#include "moduleproviderinfo.h" - -#include -#include - -#include -#include - -namespace qbs { -class SetupProjectParameters; -namespace Internal { -class Evaluator; -class Item; -class ItemReader; -class Logger; -class ProbesResolver; - -enum class FallbackMode { Enabled, Disabled }; - -class ModuleProviderLoader -{ -public: - explicit ModuleProviderLoader(const SetupProjectParameters ¶meters, ItemReader &itemReader, - Evaluator &evaluator, ProbesResolver &probesResolver, - Logger &logger); - - enum class ModuleProviderLookup { Scoped, Named, Fallback }; - - struct Provider - { - QualifiedId name; - ModuleProviderLookup lookup; - }; - - struct ModuleProviderResult - { - std::vector probes; - QVariantMap providerConfig; - bool providerFound = false; - std::optional searchPaths; - }; - - const StoredModuleProviderInfo &storedModuleProviderInfo() const - { - return m_storedModuleProviderInfo; - } - - void setStoredModuleProviderInfo(StoredModuleProviderInfo moduleProviderInfo) - { - m_storedModuleProviderInfo = std::move(moduleProviderInfo); - } - - const Set &tempQbsFiles() const { return m_tempQbsFiles; } - - struct ProductContext { - Item * const productItem; - const Item * const projectItem; - const QString &name; - const QString &uniqueName; - const QVariantMap &moduleProperties; - const std::optional providerConfig; - }; - ModuleProviderResult executeModuleProviders( - const ProductContext &productContext, - const CodeLocation &dependsItemLocation, - const QualifiedId &moduleName, - FallbackMode fallbackMode); - -private: - ModuleProviderResult executeModuleProvidersHelper( - const ProductContext &product, - const CodeLocation &dependsItemLocation, - const std::vector &providers); - QVariantMap getModuleProviderConfig(const ProductContext &product); - - std::optional> getModuleProviders(Item *item); - - QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType); - QVariantMap evaluateQbsModule(const ProductContext &product) const; - Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule); - std::pair> evaluateModuleProvider( - const ProductContext &product, - const CodeLocation &location, - const QualifiedId &name, - const QString &providerFile, - const QVariantMap &moduleConfig, - const QVariantMap &qbsModule); - -private: - const SetupProjectParameters &m_parameters; - ItemReader &m_reader; - Evaluator &m_evaluator; - ProbesResolver &m_probesResolver; - Logger &m_logger; - StoredModuleProviderInfo m_storedModuleProviderInfo; - Set m_tempQbsFiles; -}; - -} // namespace Internal -} // namespace qbs - -#endif // MODULEPROVIDERLOADER_H diff --git a/src/lib/corelib/language/probesresolver.cpp b/src/lib/corelib/language/probesresolver.cpp deleted file mode 100644 index 1462c706b..000000000 --- a/src/lib/corelib/language/probesresolver.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Copyright (C) 2022 Raphaël Cotty -** 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 "probesresolver.h" - -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "itemreader.h" -#include "language.h" -#include "scriptengine.h" -#include "value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace qbs { -namespace Internal { - -static QString probeGlobalId(Item *probe) -{ - QString id; - - for (Item *obj = probe; obj; obj = obj->prototype()) { - if (!obj->id().isEmpty()) { - id = obj->id(); - break; - } - } - - if (id.isEmpty()) - return {}; - - QBS_CHECK(probe->file()); - return id + QLatin1Char('_') + probe->file()->filePath(); -} - -ProbesResolver::ProbesResolver(const SetupProjectParameters ¶meters, Evaluator &evaluator, - Logger &logger) - : m_parameters(parameters), m_evaluator(evaluator), m_logger(logger) -{ -} - -void ProbesResolver::setOldProjectProbes(const std::vector &oldProbes) -{ - m_oldProjectProbes.clear(); - for (const ProbeConstPtr& probe : oldProbes) - m_oldProjectProbes[probe->globalId()] << probe; -} - -void ProbesResolver::setOldProductProbes( - const QHash> &oldProbes) -{ - m_oldProductProbes = oldProbes; -} - -std::vector ProbesResolver::resolveProbes(const ProductContext &productContext, Item *item) -{ - AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr); - EvalContextSwitcher evalContextSwitcher(m_evaluator.engine(), EvalContext::ProbeExecution); - std::vector probes; - for (Item * const child : item->children()) - if (child->type() == ItemType::Probe) - probes.push_back(resolveProbe(productContext, item, child)); - return probes; -} - -ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext, Item *parent, - Item *probe) -{ - qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString(); - ++m_probesEncountered; - const QString &probeId = probeGlobalId(probe); - if (Q_UNLIKELY(probeId.isEmpty())) - throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location()); - const JSSourceValueConstPtr configureScript - = probe->sourceProperty(StringConstants::configureProperty()); - QBS_CHECK(configureScript); - if (Q_UNLIKELY(configureScript->sourceCode() == StringConstants::undefinedValue())) - throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); - using ProbeProperty = std::pair; - std::vector probeBindings; - ScriptEngine * const engine = m_evaluator.engine(); - JSContext * const ctx = engine->context(); - QVariantMap initialProperties; - for (Item *obj = probe; obj; obj = obj->prototype()) { - const Item::PropertyMap &props = obj->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - const QString &name = it.key(); - if (name == StringConstants::configureProperty()) - continue; - const JSValue value = m_evaluator.value(probe, name); - probeBindings.emplace_back(name, ScopedJsValue(ctx, value)); - if (name != StringConstants::conditionProperty()) - initialProperties.insert(name, getJsVariant(ctx, value)); - } - } - const bool condition = m_evaluator.boolValue(probe, StringConstants::conditionProperty()); - const QString &sourceCode = configureScript->sourceCode().toString(); - ProbeConstPtr resolvedProbe; - if (parent->type() == ItemType::Project - || productContext.name.startsWith(StringConstants::shadowProductPrefix())) { - resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); - } else { - resolvedProbe = findOldProductProbe(productContext.uniqueName, condition, - initialProperties, sourceCode); - } - if (!resolvedProbe) { - resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties); - if (resolvedProbe) { - qCDebug(lcModuleLoader) << "probe results cached from current run"; - ++m_probesCachedCurrent; - } - } else { - qCDebug(lcModuleLoader) << "probe results cached from earlier run"; - ++m_probesCachedOld; - } - ScopedJsValue configureScope(ctx, JS_UNDEFINED); - std::vector importedFilesUsedInConfigure; - if (!condition) { - qCDebug(lcModuleLoader) << "Probe disabled; skipping"; - } else if (!resolvedProbe) { - ++m_probesRun; - qCDebug(lcModuleLoader) << "configure script needs to run"; - const Evaluator::FileContextScopes fileCtxScopes - = m_evaluator.fileContextScopes(configureScript->file()); - configureScope.setValue(engine->newObject()); - for (const ProbeProperty &b : probeBindings) - setJsProperty(ctx, configureScope, b.first, JS_DupValue(ctx, b.second)); - engine->clearRequestedProperties(); - ScopedJsValue sv(ctx, engine->evaluate(JsValueOwner::Caller, - configureScript->sourceCodeForEvaluation(), {}, 1, - {fileCtxScopes.fileScope, fileCtxScopes.importScope, configureScope})); - if (JsException ex = engine->checkAndClearException(configureScript->location())) - throw ex.toErrorInfo(); - importedFilesUsedInConfigure = engine->importedFilesUsedInScript(); - } else { - importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed(); - } - QVariantMap properties; - for (const ProbeProperty &b : probeBindings) { - QVariant newValue; - if (resolvedProbe) { - newValue = resolvedProbe->properties().value(b.first); - } else { - if (condition) { - JSValue v = getJsProperty(ctx, configureScope, b.first); - const JSValue saved = v; - ScopedJsValue valueMgr(ctx, saved); - const PropertyDeclaration decl = probe->propertyDeclaration(b.first); - m_evaluator.convertToPropertyType(decl, probe->location(), v); - - // If the value was converted from scalar to array as per our convenience - // functionality, then the original value is now the only element of a - // newly allocated array and thus gets deleted via that array. - // The array itself is owned by the script engine, so we must stay out of - // memory management here. - if (v != saved) - valueMgr.setValue(JS_UNDEFINED); - - if (JsException ex = engine->checkAndClearException({})) - throw ex.toErrorInfo(); - newValue = getJsVariant(ctx, v); - } else { - newValue = initialProperties.value(b.first); - } - } - if (newValue != getJsVariant(ctx, b.second)) - probe->setProperty(b.first, VariantValue::create(newValue)); - if (!resolvedProbe) - properties.insert(b.first, newValue); - } - if (!resolvedProbe) { - resolvedProbe = Probe::create(probeId, probe->location(), condition, - sourceCode, properties, initialProperties, - importedFilesUsedInConfigure); - m_currentProbes[probe->location()] << resolvedProbe; - } - return resolvedProbe; -} - -ProbeConstPtr ProbesResolver::findOldProjectProbe( - const QString &globalId, - bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const -{ - if (m_parameters.forceProbeExecution()) - return {}; - - for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) { - if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) - return oldProbe; - } - - return {}; -} - -ProbeConstPtr ProbesResolver::findOldProductProbe( - const QString &productName, - bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const -{ - if (m_parameters.forceProbeExecution()) - return {}; - - for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) { - if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) - return oldProbe; - } - - return {}; -} - -ProbeConstPtr ProbesResolver::findCurrentProbe( - const CodeLocation &location, - bool condition, - const QVariantMap &initialProperties) const -{ - const std::vector &cachedProbes = m_currentProbes.value(location); - for (const ProbeConstPtr &probe : cachedProbes) { - if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No)) - return probe; - } - return {}; -} - -bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition, - const QVariantMap &initialProperties, const QString &configureScript, - CompareScript compareScript) const -{ - return probe->condition() == condition - && probe->initialProperties() == initialProperties - && (compareScript == CompareScript::No - || (probe->configureScript() == configureScript - && !probe->needsReconfigure(m_lastResolveTime))); -} - -void ProbesResolver::printProfilingInfo(int indent) -{ - if (!m_parameters.logElapsedTime()) - return; - 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); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/probesresolver.h b/src/lib/corelib/language/probesresolver.h deleted file mode 100644 index 4aae0b887..000000000 --- a/src/lib/corelib/language/probesresolver.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Copyright (C) 2022 Raphaël Cotty -** 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$ -** -****************************************************************************/ - -#ifndef PROBESRESOLVER_H -#define PROBESRESOLVER_H - -#include "forward_decls.h" - -#include - -#include - -#include - -namespace qbs { -class SetupProjectParameters; -namespace Internal { -class Item; -class Evaluator; -class Logger; - -class ProbesResolver -{ -public: - explicit ProbesResolver(const SetupProjectParameters ¶meters, Evaluator &evaluator, - Logger &logger); - void setOldProjectProbes(const std::vector &oldProbes); - void setOldProductProbes(const QHash> &oldProbes); - void printProfilingInfo(int indent); - - struct ProductContext { - const QString &name; - const QString &uniqueName; - }; - std::vector resolveProbes(const ProductContext &productContext, Item *item); - -private: - ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const; - ProbeConstPtr findOldProductProbe(const QString &productName, bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const; - ProbeConstPtr findCurrentProbe(const CodeLocation &location, bool condition, - const QVariantMap &initialProperties) const; - enum class CompareScript { No, Yes }; - bool probeMatches(const ProbeConstPtr &probe, bool condition, - const QVariantMap &initialProperties, const QString &configureScript, - CompareScript compareScript) const; - ProbeConstPtr resolveProbe(const ProductContext &productContext, Item *parent, Item *probe); - - qint64 m_elapsedTimeProbes = 0; - quint64 m_probesEncountered = 0; - quint64 m_probesRun = 0; - quint64 m_probesCachedCurrent = 0; - quint64 m_probesCachedOld = 0; - - const SetupProjectParameters &m_parameters; - Evaluator &m_evaluator; - Logger &m_logger; - QHash> m_oldProjectProbes; - QHash> m_oldProductProbes; - FileTime m_lastResolveTime; - QHash> m_currentProbes; -}; - -} // namespace Internal -} // namespace qbs - -#endif // PROBESRESOLVER_H diff --git a/src/lib/corelib/language/productitemmultiplexer.cpp b/src/lib/corelib/language/productitemmultiplexer.cpp deleted file mode 100644 index c737be7f1..000000000 --- a/src/lib/corelib/language/productitemmultiplexer.cpp +++ /dev/null @@ -1,288 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include - -#include -#include - -#include - - -namespace qbs::Internal { -namespace { -using MultiplexConfigurationByIdTable = QThreadStorage>; -using MultiplexRow = std::vector; -using MultiplexTable = std::vector; -class MultiplexInfo -{ -public: - std::vector 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 ¶meters, 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 ¶meters; - Evaluator &evaluator; - Logger &logger; - const QbsItemRetriever qbsItemRetriever; -}; - -ProductItemMultiplexer::ProductItemMultiplexer( - const SetupProjectParameters ¶meters, Evaluator &evaluator, Logger &logger, - const QbsItemRetriever &qbsItemRetriever) - : d(new Private(parameters, evaluator, logger, qbsItemRetriever)) {} - -ProductItemMultiplexer::~ProductItemMultiplexer() { delete d; } - -QList ProductItemMultiplexer::multiplex( - const QString &productName, - Item *productItem, - Item *tempQbsModuleItem, - const std::function &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 additionalProductItems; - std::vector 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 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 deleted file mode 100644 index d99267336..000000000 --- a/src/lib/corelib/language/productitemmultiplexer.h +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************************** -** -** 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 -#include - -#include - -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; - ProductItemMultiplexer(const SetupProjectParameters ¶meters, 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 multiplex( - const QString &productName, - Item *productItem, - Item *tempQbsModuleItem, - const std::function &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 deleted file mode 100644 index 403fd35bc..000000000 --- a/src/lib/corelib/language/projectresolver.cpp +++ /dev/null @@ -1,1801 +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 "projectresolver.h" - -#include "artifactproperties.h" -#include "builtindeclarations.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "language.h" -#include "propertymapinternal.h" -#include "resolvedfilecontext.h" -#include "scriptengine.h" -#include "value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -namespace qbs { -namespace Internal { - -extern bool debugProperties; - -static const FileTag unknownFileTag() -{ - static const FileTag tag("unknown-file-tag"); - return tag; -} - -struct ProjectResolver::ProjectContext -{ - ProjectContext *parentContext = nullptr; - ResolvedProjectPtr project; - std::vector fileTaggers; - std::vector rules; - JobLimits jobLimits; - ResolvedModulePtr dummyModule; -}; - -struct ProjectResolver::ProductContext -{ - ResolvedProductPtr product; - QString buildDirectory; - Item *item = nullptr; - using ArtifactPropertiesInfo = std::pair>; - QHash artifactPropertiesPerFilter; - ProjectResolver::FileLocations sourceArtifactLocations; - GroupConstPtr currentGroup; -}; - -struct ProjectResolver::ModuleContext -{ - ResolvedModulePtr module; - JobLimits jobLimits; -}; - -class CancelException { }; - - -ProjectResolver::ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult, - SetupProjectParameters setupParameters, Logger &logger) - : m_evaluator(evaluator) - , m_logger(logger) - , m_engine(m_evaluator->engine()) - , m_progressObserver(nullptr) - , m_setupParams(std::move(setupParameters)) - , m_loadResult(std::move(loadResult)) -{ - QBS_CHECK(FileInfo::isAbsolute(m_setupParams.buildRoot())); -} - -ProjectResolver::~ProjectResolver() = default; - -void ProjectResolver::setProgressObserver(ProgressObserver *observer) -{ - m_progressObserver = observer; -} - -static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) -{ - const std::vector allProducts = project->allProducts(); - for (size_t i = 0; i < allProducts.size(); ++i) { - const ResolvedProductConstPtr product1 = allProducts.at(i); - const QString productName = product1->uniqueName(); - for (size_t j = i + 1; j < allProducts.size(); ++j) { - const ResolvedProductConstPtr product2 = allProducts.at(j); - if (product2->uniqueName() == productName) { - ErrorInfo error; - error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); - error.append(Tr::tr("First product defined here."), product1->location); - error.append(Tr::tr("Second product defined here."), product2->location); - throw error; - } - } - } -} - -TopLevelProjectPtr ProjectResolver::resolve() -{ - TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"), - m_setupParams.logElapsedTime()); - qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath(); - - m_productContext = nullptr; - m_moduleContext = nullptr; - m_elapsedTimeModPropEval = m_elapsedTimeAllPropEval = m_elapsedTimeGroups = 0; - TopLevelProjectPtr tlp; - try { - tlp = resolveTopLevelProject(); - printProfilingInfo(); - } catch (const CancelException &) { - throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.") - .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); - } - return tlp; -} - -void ProjectResolver::checkCancelation() const -{ - if (m_progressObserver && m_progressObserver->canceled()) - throw CancelException(); -} - -QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propertyWasSet) const -{ - QString result; - if (value && value->type() == Value::JSSourceValueType) { - const JSSourceValueConstPtr sourceValue = std::static_pointer_cast( - value); - result = sourceCodeForEvaluation(sourceValue); - if (propertyWasSet) - *propertyWasSet = !sourceValue->isBuiltinDefaultValue(); - } else { - if (propertyWasSet) - *propertyWasSet = false; - } - return result; -} - -QString ProjectResolver::verbatimValue(Item *item, const QString &name, bool *propertyWasSet) const -{ - return verbatimValue(item->property(name), propertyWasSet); -} - -void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) -{ - Q_UNUSED(item); - Q_UNUSED(projectContext); -} - -static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) -{ - Set subProjectNames; - Set projectsInNeedOfNameChange; - for (const ResolvedProjectPtr &p : qAsConst(parentProject->subProjects)) { - if (!subProjectNames.insert(p->name).second) - projectsInNeedOfNameChange << p; - makeSubProjectNamesUniqe(p); - } - while (!projectsInNeedOfNameChange.empty()) { - auto it = projectsInNeedOfNameChange.begin(); - while (it != projectsInNeedOfNameChange.end()) { - const ResolvedProjectPtr p = *it; - p->name += QLatin1Char('_'); - if (subProjectNames.insert(p->name).second) { - it = projectsInNeedOfNameChange.erase(it); - } else { - ++it; - } - } - } -} - -TopLevelProjectPtr ProjectResolver::resolveTopLevelProject() -{ - if (m_progressObserver) - m_progressObserver->setMaximum(int(m_loadResult.productInfos.size())); - TopLevelProjectPtr project = TopLevelProject::create(); - project->buildDirectory = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), - TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree())); - project->buildSystemFiles = m_loadResult.qbsFiles; - project->profileConfigs = m_loadResult.profileConfigs; - project->probes = m_loadResult.projectProbes; - project->moduleProviderInfo = m_loadResult.storedModuleProviderInfo; - ProjectContext projectContext; - projectContext.project = project; - - resolveProject(m_loadResult.root, &projectContext); - ErrorInfo accumulatedErrors; - for (const ErrorInfo &e : m_queuedErrors) - appendError(accumulatedErrors, e); - if (accumulatedErrors.hasError()) - throw accumulatedErrors; - - project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); - project->overriddenValues = m_setupParams.overriddenValues(); - project->canonicalFilePathResults = m_engine->canonicalFilePathResults(); - project->fileExistsResults = m_engine->fileExistsResults(); - project->directoryEntriesResults = m_engine->directoryEntriesResults(); - project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); - project->environment = m_engine->environment(); - project->buildSystemFiles.unite(m_engine->imports()); - makeSubProjectNamesUniqe(project); - resolveProductDependencies(); - collectExportedProductDependencies(); - checkForDuplicateProductNames(project); - - for (const ResolvedProductPtr &product : project->allProducts()) { - if (!product->enabled) - continue; - - applyFileTaggers(product); - matchArtifactProperties(product, product->allEnabledFiles()); - - // Let a positive value of qbs.install imply the file tag "installable". - for (const SourceArtifactPtr &artifact : product->allFiles()) { - if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) - artifact->fileTags += "installable"; - } - } - project->warningsEncountered = m_logger.warnings(); - return project; -} - -void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - - if (projectContext->parentContext) - projectContext->project->enabled = projectContext->parentContext->project->enabled; - projectContext->project->location = item->location(); - try { - resolveProjectFully(item, projectContext); - } catch (const ErrorInfo &error) { - if (!projectContext->project->enabled) { - qCDebug(lcProjectResolver) << "error resolving project" - << projectContext->project->location << error.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } -} - -void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - projectContext->project->enabled = projectContext->project->enabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - projectContext->project->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - if (projectContext->project->name.isEmpty()) - projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item? - QVariantMap projectProperties; - if (!projectContext->project->enabled) { - projectProperties.insert(StringConstants::profileProperty(), - m_evaluator->stringValue(item, - StringConstants::profileProperty())); - projectContext->project->setProjectProperties(projectProperties); - return; - } - - projectContext->dummyModule = ResolvedModule::create(); - - for (Item::PropertyDeclarationMap::const_iterator it - = item->propertyDeclarations().constBegin(); - it != item->propertyDeclarations().constEnd(); ++it) { - if (it.value().flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) - continue; - const ValueConstPtr v = item->property(it.key()); - QBS_ASSERT(v && v->type() != Value::ItemValueType, continue); - const ScopedJsValue sv(m_engine->context(), m_evaluator->value(item, it.key())); - projectProperties.insert(it.key(), getJsVariant(m_engine->context(), sv)); - } - projectContext->project->setProjectProperties(projectProperties); - - static const ItemFuncMap mapping = { - { ItemType::Project, &ProjectResolver::resolveProject }, - { ItemType::SubProject, &ProjectResolver::resolveSubProject }, - { ItemType::Product, &ProjectResolver::resolveProduct }, - { ItemType::Probe, &ProjectResolver::ignoreItem }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } - }; - - for (Item * const child : item->children()) { - try { - callItemFunction(mapping, child, projectContext); - } catch (const ErrorInfo &e) { - m_queuedErrors.push_back(e); - } - } - - for (const ResolvedProductPtr &product : projectContext->project->products) - postProcess(product, projectContext); -} - -void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - ProjectContext subProjectContext = createProjectContext(projectContext); - - Item * const projectItem = item->child(ItemType::Project); - if (projectItem) { - resolveProject(projectItem, &subProjectContext); - return; - } - - // No project item was found, which means the project was disabled. - subProjectContext.project->enabled = false; - Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject); - if (propertiesItem) { - subProjectContext.project->name - = m_evaluator->stringValue(propertiesItem, StringConstants::nameProperty()); - } -} - -class ProjectResolver::ProductContextSwitcher -{ -public: - ProductContextSwitcher(ProjectResolver *resolver, ProductContext *newContext, - ProgressObserver *progressObserver) - : m_resolver(resolver), m_progressObserver(progressObserver) - { - QBS_CHECK(!m_resolver->m_productContext); - m_resolver->m_productContext = newContext; - } - - ~ProductContextSwitcher() - { - if (m_progressObserver) - m_progressObserver->incrementProgressValue(); - m_resolver->m_productContext = nullptr; - } - -private: - ProjectResolver * const m_resolver; - ProgressObserver * const m_progressObserver; -}; - -void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - m_evaluator->clearPropertyDependencies(); - ProductContext productContext; - productContext.item = item; - ResolvedProductPtr product = ResolvedProduct::create(); - product->enabled = projectContext->project->enabled; - product->moduleProperties = PropertyMapInternal::create(); - product->project = projectContext->project; - 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 &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); - 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:"); - ErrorInfo fullError(mainErrorString, item->location()); - appendError(fullError, e); - if (!product->enabled) { - qCDebug(lcProjectResolver) << fullError.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw fullError; - m_logger.printWarning(fullError); - m_logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") - .arg(product->name), item->location())); - product->enabled = false; - } -} - -void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectContext) -{ - const ResolvedProductPtr product = m_productContext->product; - m_productItemMap.insert(product, item); - projectContext->project->products.push_back(product); - product->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - - // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. - m_productContext->buildDirectory - = m_evaluator->stringValue(item, StringConstants::buildDirectoryProperty()); - product->multiplexConfigurationId - = m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty()); - qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName(); - m_productsByItem.insert(item, product); - product->enabled = product->enabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - 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( - item, StringConstants::sourceDirectoryProperty()); - product->destinationDirectory = m_evaluator->stringValue( - item, StringConstants::destinationDirProperty()); - - if (product->destinationDirectory.isEmpty()) { - product->destinationDirectory = m_productContext->buildDirectory; - } else { - product->destinationDirectory = FileInfo::resolvePath( - product->topLevelProject()->buildDirectory, - product->destinationDirectory); - } - product->probes = pi.probes; - createProductConfig(product.get()); - product->productProperties.insert(StringConstants::destinationDirProperty(), - product->destinationDirectory); - ModuleProperties::init(m_evaluator->engine(), m_evaluator->scriptValue(item), product.get()); - - QList subItems = item->children(); - const ValuePtr filesProperty = item->property(StringConstants::filesProperty()); - if (filesProperty) { - Item *fakeGroup = Item::create(item->pool(), ItemType::Group); - fakeGroup->setFile(item->file()); - fakeGroup->setLocation(item->location()); - fakeGroup->setScope(item); - fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name)); - fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty); - fakeGroup->setProperty(StringConstants::excludeFilesProperty(), - item->property(StringConstants::excludeFilesProperty())); - fakeGroup->setProperty(StringConstants::overrideTagsProperty(), - VariantValue::falseValue()); - fakeGroup->setupForBuiltinType(m_setupParams.deprecationWarningMode(), m_logger); - subItems.prepend(fakeGroup); - } - - static const ItemFuncMap mapping = { - { ItemType::Depends, &ProjectResolver::ignoreItem }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Group, &ProjectResolver::resolveGroup }, - { ItemType::Product, &ProjectResolver::resolveShadowProduct }, - { ItemType::Export, &ProjectResolver::resolveExport }, - { ItemType::Probe, &ProjectResolver::ignoreItem }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } - }; - - for (Item * const child : qAsConst(subItems)) - callItemFunction(mapping, child, projectContext); - - for (const ProjectContext *p = projectContext; p; p = p->parentContext) { - JobLimits tempLimits = p->jobLimits; - product->jobLimits = tempLimits.update(product->jobLimits); - } - - resolveModules(item, projectContext); - - for (const FileTag &t : qAsConst(product->fileTags)) - m_productsByType[t].push_back(product); -} - -void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext) -{ - JobLimits jobLimits; - 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) - m_productContext->product->jobLimits.setJobLimit(moduleJobLimit); - } -} - -void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, - const QVariantMap ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext) -{ - checkCancelation(); - if (!item->isPresentModule()) - return; - - ModuleContext * const oldModuleContext = m_moduleContext; - ModuleContext moduleContext; - moduleContext.module = ResolvedModule::create(); - m_moduleContext = &moduleContext; - - const ResolvedModulePtr &module = moduleContext.module; - module->name = moduleName.toString(); - module->isProduct = isProduct; - module->product = m_productContext->product.get(); - module->setupBuildEnvironmentScript.initialize( - scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty())); - module->setupRunEnvironmentScript.initialize( - scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty())); - - for (const Item::Module &m : item->modules()) { - if (m.item->isPresentModule()) - module->moduleDependencies += m.name.toString(); - } - - m_productContext->product->modules.push_back(module); - if (!parameters.empty()) - m_productContext->product->moduleParameters[module] = parameters; - - static const ItemFuncMap mapping { - { ItemType::Group, &ProjectResolver::ignoreItem }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Scanner, &ProjectResolver::resolveScanner }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }, - { ItemType::Depends, &ProjectResolver::ignoreItem }, - { ItemType::Parameter, &ProjectResolver::ignoreItem }, - { ItemType::Properties, &ProjectResolver::ignoreItem }, - { ItemType::Probe, &ProjectResolver::ignoreItem } - }; - for (Item *child : item->children()) - callItemFunction(mapping, child, projectContext); - for (int i = 0; i < moduleContext.jobLimits.count(); ++i) { - const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i); - const int oldLimit = jobLimits.getLimit(newJobLimit.pool()); - if (oldLimit == -1 || oldLimit > newJobLimit.limit()) - jobLimits.setJobLimit(newJobLimit); - } - - m_moduleContext = oldModuleContext; -} - -void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item) -{ - const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty()); - if (type) - product->fileTags = FileTags::fromStringList(type->value().toStringList()); -} - -SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct, - const QString &fileName, const GroupPtr &group, bool wildcard, - const CodeLocation &filesLocation, FileLocations *fileLocations, - ErrorInfo *errorInfo) -{ - const QString &baseDir = FileInfo::path(group->location.filePath()); - const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName)); - if (!wildcard && !FileInfo(absFilePath).exists()) { - if (errorInfo) - errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation); - rproduct->missingSourceFiles << absFilePath; - return {}; - } - if (group->enabled && fileLocations) { - CodeLocation &loc = (*fileLocations)[std::make_pair(group->targetOfModule, absFilePath)]; - if (loc.isValid()) { - if (errorInfo) { - errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath)); - errorInfo->append(Tr::tr("First occurrence is here."), loc); - errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation); - } - return {}; - } - loc = filesLocation; - } - SourceArtifactPtr artifact = SourceArtifactInternal::create(); - artifact->absoluteFilePath = absFilePath; - artifact->fileTags = group->fileTags; - artifact->overrideFileTags = group->overrideTags; - artifact->properties = group->properties; - artifact->targetOfModule = group->targetOfModule; - (wildcard ? group->wildcards->files : group->files).push_back(artifact); - return artifact; -} - -static QualifiedIdSet propertiesToEvaluate(std::deque initialProps, - const PropertyDependencies &deps) -{ - std::deque remainingProps = std::move(initialProps); - QualifiedIdSet allProperties; - while (!remainingProps.empty()) { - const QualifiedId prop = remainingProps.front(); - remainingProps.pop_front(); - const auto insertResult = allProperties.insert(prop); - if (!insertResult.second) - continue; - transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; }); - } - return allProperties; -} - -QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group, - const QVariantMap ¤tValues) -{ - // Step 1: Retrieve the properties directly set in the group - const ModulePropertiesPerGroup &mp = mapValue( - m_loadResult.productInfos, m_productContext->item).modulePropertiesSetInGroups; - const auto it = mp.find(group); - if (it == mp.end()) - return {}; - const QualifiedIdSet &propsSetInGroup = it->second; - - // Step 2: Gather all properties that depend on these properties. - const QualifiedIdSet &propsToEval = propertiesToEvaluate( - rangeTo>(propsSetInGroup), - m_evaluator->propertyDependencies()); - - // Step 3: Evaluate all these properties and replace their values in the map - QVariantMap modulesMap = currentValues; - QHash propsPerModule; - for (auto fullPropName : propsToEval) { - const QString moduleName - = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString(); - propsPerModule[moduleName] << fullPropName.last(); - } - 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); - if (propsForModule.empty()) - continue; - QVariantMap reusableValues = modulesMap.value(fullModName).toMap(); - for (const QString &prop : qAsConst(propsForModule)) - reusableValues.remove(prop); - modulesMap.insert(fullModName, - evaluateProperties(module.item, module.item, reusableValues, true, true)); - } - return modulesMap; -} - -void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - const bool parentEnabled = m_productContext->currentGroup - ? m_productContext->currentGroup->enabled - : m_productContext->product->enabled; - const bool isEnabled = parentEnabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - try { - resolveGroupFully(item, projectContext, isEnabled); - } catch (const ErrorInfo &error) { - if (!isEnabled) { - qCDebug(lcProjectResolver) << "error resolving group at" << item->location() - << error.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } -} - -void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectContext *projectContext, - bool isEnabled) -{ - AccumulatingTimer groupTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeGroups : nullptr); - - const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) { - PropertyMapPtr moduleProperties; - bool newPropertyMapRequired = false; - if (existingProps) - moduleProperties = existingProps->propertyMap(); - if (!moduleProperties) { - newPropertyMapRequired = true; - moduleProperties = m_productContext->currentGroup - ? m_productContext->currentGroup->properties - : m_productContext->product->moduleProperties; - } - const QVariantMap newModuleProperties - = resolveAdditionalModuleProperties(item, moduleProperties->value()); - if (!newModuleProperties.empty()) { - if (newPropertyMapRequired) - moduleProperties = PropertyMapInternal::create(); - moduleProperties->setValue(newModuleProperties); - } - return moduleProperties; - }; - - QStringList files = m_evaluator->stringListValue(item, StringConstants::filesProperty()); - bool fileTagsSet; - const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty(), - &fileTagsSet); - const QStringList fileTagsFilter - = m_evaluator->stringListValue(item, StringConstants::fileTagsFilterProperty()); - if (!fileTagsFilter.empty()) { - if (Q_UNLIKELY(!files.empty())) - throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), - item->location()); - - if (!isEnabled) - return; - - ProductContext::ArtifactPropertiesInfo &apinfo - = m_productContext->artifactPropertiesPerFilter[fileTagsFilter]; - if (apinfo.first) { - const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(), - [item](const CodeLocation &loc) { - return item->location().filePath() == loc.filePath(); - }); - if (it != apinfo.second.cend()) { - ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); - error.append(Tr::tr("First item"), *it); - error.append(Tr::tr("Second item"), item->location()); - throw error; - } - } else { - apinfo.first = ArtifactProperties::create(); - apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); - m_productContext->product->artifactProperties.push_back(apinfo.first); - } - apinfo.second.push_back(item->location()); - apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get())); - apinfo.first->addExtraFileTags(fileTags); - return; - } - QStringList patterns; - for (int i = files.size(); --i >= 0;) { - if (FileInfo::isPattern(files[i])) - patterns.push_back(files.takeAt(i)); - } - GroupPtr group = ResolvedGroup::create(); - bool prefixWasSet = false; - group->prefix = m_evaluator->stringValue(item, StringConstants::prefixProperty(), QString(), - &prefixWasSet); - if (!prefixWasSet && m_productContext->currentGroup) - group->prefix = m_productContext->currentGroup->prefix; - if (!group->prefix.isEmpty()) { - for (auto it = files.rbegin(), end = files.rend(); it != end; ++it) - it->prepend(group->prefix); - } - group->location = item->location(); - group->enabled = isEnabled; - group->properties = getGroupPropertyMap(nullptr); - group->fileTags = fileTags; - group->overrideTags = m_evaluator->boolValue(item, StringConstants::overrideTagsProperty()); - if (group->overrideTags && fileTagsSet) { - if (group->fileTags.empty() ) - group->fileTags.insert(unknownFileTag()); - } else if (m_productContext->currentGroup) { - group->fileTags.unite(m_productContext->currentGroup->fileTags); - } - - const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location(); - const VariantValueConstPtr moduleProp = item->variantProperty( - StringConstants::modulePropertyInternal()); - if (moduleProp) - group->targetOfModule = moduleProp->value().toString(); - ErrorInfo fileError; - if (!patterns.empty()) { - group->wildcards = std::make_unique(); - SourceWildCards *wildcards = group->wildcards.get(); - wildcards->group = group.get(); - wildcards->excludePatterns = m_evaluator->stringListValue( - item, StringConstants::excludeFilesProperty()); - wildcards->patterns = patterns; - const Set files = wildcards->expandPatterns(group, - FileInfo::path(item->file()->filePath()), - projectContext->project->topLevelProject()->buildDirectory); - for (const QString &fileName : files) - createSourceArtifact(m_productContext->product, fileName, group, true, filesLocation, - &m_productContext->sourceArtifactLocations, &fileError); - } - - for (const QString &fileName : qAsConst(files)) { - createSourceArtifact(m_productContext->product, fileName, group, false, filesLocation, - &m_productContext->sourceArtifactLocations, &fileError); - } - if (fileError.hasError()) { - if (group->enabled) { - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw ErrorInfo(fileError); - m_logger.printWarning(fileError); - } else { - qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString(); - } - } - group->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - if (group->name.isEmpty()) - group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.size()); - m_productContext->product->groups.push_back(group); - - class GroupContextSwitcher { - public: - GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup) - : m_context(context), m_oldGroup(context.currentGroup) { - m_context.currentGroup = newGroup; - } - ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; } - private: - ProductContext &m_context; - const GroupConstPtr m_oldGroup; - }; - GroupContextSwitcher groupSwitcher(*m_productContext, group); - for (Item * const childItem : item->children()) - resolveGroup(childItem, projectContext); -} - -void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem) -{ - ExportedModule &m = m_productContext->product->exportedModule; - const QVariantList prefixList = m.propertyValues.take( - StringConstants::prefixMappingProperty()).toList(); - const QString shadowProductName = m_evaluator->stringValue( - shadowProductItem, StringConstants::nameProperty()); - const QString shadowProductBuildDir = m_evaluator->stringValue( - shadowProductItem, StringConstants::buildDirectoryProperty()); - QVariantMap prefixMap; - for (const QVariant &v : prefixList) { - const QVariantMap o = v.toMap(); - prefixMap.insert(o.value(QStringLiteral("prefix")).toString(), - o.value(QStringLiteral("replacement")).toString()); - } - const auto valueRefersToImportingProduct - = [shadowProductName, shadowProductBuildDir](const QString &value) { - return value.toLower().contains(shadowProductName.toLower()) - || value.contains(shadowProductBuildDir); - }; - static const auto stringMapper = [](const QVariantMap &mappings, const QString &value) - -> QString { - for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) { - if (value.startsWith(it.key())) - return it.value().toString() + value.mid(it.key().size()); - } - return value; - }; - const auto stringListMapper = [&valueRefersToImportingProduct]( - const QVariantMap &mappings, const QStringList &value) -> QStringList { - QStringList result; - result.reserve(value.size()); - for (const QString &s : value) { - if (!valueRefersToImportingProduct(s)) - result.push_back(stringMapper(mappings, s)); - } - return result; - }; - const std::function mapper - = [&stringListMapper, &mapper]( - const QVariantMap &mappings, const QVariant &value) -> QVariant { - switch (static_cast(value.userType())) { - case QMetaType::QString: - return stringMapper(mappings, value.toString()); - case QMetaType::QStringList: - return stringListMapper(mappings, value.toStringList()); - case QMetaType::QVariantMap: { - QVariantMap m = value.toMap(); - for (auto it = m.begin(); it != m.end(); ++it) - it.value() = mapper(mappings, it.value()); - return m; - } - default: - return value; - } - }; - for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - for (ExportedModuleDependency &dep : m.moduleDependencies) { - for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - } -} - -void ProjectResolver::collectExportedProductDependencies() -{ - ResolvedProductPtr dummyProduct = ResolvedProduct::create(); - dummyProduct->enabled = false; - for (const auto &exportingProductInfo : qAsConst(m_productExportInfo)) { - const ResolvedProductPtr exportingProduct = exportingProductInfo.first; - if (!exportingProduct->enabled) - continue; - Item * const importingProductItem = exportingProductInfo.second; - - std::vector> directDeps; - for (const Item::Module &m : importingProductItem->modules()) { - 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); - } - } - } - for (const auto &dep : directDeps) { - if (!contains(exportingProduct->exportedModule.productDependencies, - dep.first->uniqueName())) { - exportingProduct->exportedModule.productDependencies.push_back( - dep.first->uniqueName()); - } - if (!dep.second.isEmpty()) { - exportingProduct->exportedModule.dependencyParameters.insert(dep.first, - dep.second); - } - } - auto &productDeps = exportingProduct->exportedModule.productDependencies; - std::sort(productDeps.begin(), productDeps.end()); - } -} - -void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectContext *) -{ - if (!m_productContext->product->enabled) - return; - for (const auto &m : item->modules()) { - if (m.name.toString() != m_productContext->product->name) - continue; - collectPropertiesForExportItem(m.item); - for (const auto &dep : m.item->modules()) - collectPropertiesForModuleInExportItem(dep); - break; - } - try { - adaptExportedPropertyValues(item); - } catch (const ErrorInfo &) {} - m_productExportInfo.emplace_back(m_productContext->product, item); -} - -void ProjectResolver::setupExportedProperties(const Item *item, const QString &namePrefix, - std::vector &properties) -{ - const auto &props = item->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - const QString qualifiedName = namePrefix.isEmpty() - ? it.key() : namePrefix + QLatin1Char('.') + it.key(); - if ((item->type() == ItemType::Export || item->type() == ItemType::Properties) - && qualifiedName == StringConstants::prefixMappingProperty()) { - continue; - } - const ValuePtr &v = it.value(); - if (v->type() == Value::ItemValueType) { - setupExportedProperties(std::static_pointer_cast(v)->item(), - qualifiedName, properties); - continue; - } - ExportedProperty exportedProperty; - exportedProperty.fullName = qualifiedName; - exportedProperty.type = item->propertyDeclaration(it.key()).type(); - if (v->type() == Value::VariantValueType) { - exportedProperty.sourceCode = toJSLiteral( - std::static_pointer_cast(v)->value()); - } else { - QBS_CHECK(v->type() == Value::JSSourceValueType); - const JSSourceValue * const sv = static_cast(v.get()); - exportedProperty.sourceCode = sv->sourceCode().toString(); - } - const ItemDeclaration itemDecl - = BuiltinDeclarations::instance().declarationsForType(item->type()); - PropertyDeclaration propertyDecl; - const auto itemProperties = itemDecl.properties(); - for (const PropertyDeclaration &decl : itemProperties) { - if (decl.name() == it.key()) { - propertyDecl = decl; - exportedProperty.isBuiltin = true; - break; - } - } - - // Do not add built-in properties that were left at their default value. - if (!exportedProperty.isBuiltin || m_evaluator->isNonDefaultValue(item, it.key())) - properties.push_back(exportedProperty); - } - - // Order the list of properties, so the output won't look so random. - static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool { - const int p1ComponentCount = p1.fullName.count(QLatin1Char('.')); - const int p2ComponentCount = p2.fullName.count(QLatin1Char('.')); - if (p1.isBuiltin && !p2.isBuiltin) - return true; - if (!p1.isBuiltin && p2.isBuiltin) - return false; - if (p1ComponentCount < p2ComponentCount) - return true; - if (p1ComponentCount > p2ComponentCount) - return false; - return p1.fullName < p2.fullName; - }; - std::sort(properties.begin(), properties.end(), less); -} - -static bool usesImport(const ExportedProperty &prop, const QRegularExpression ®ex) -{ - return prop.sourceCode.indexOf(regex) != -1; -} - -static bool usesImport(const ExportedItem &item, const QRegularExpression ®ex) -{ - return any_of(item.properties, - [regex](const ExportedProperty &p) { return usesImport(p, regex); }) - || any_of(item.children, - [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); -} - -static bool usesImport(const ExportedModule &module, const QString &name) -{ - // Imports are used in three ways: - // (1) var f = new TextFile(...); - // (2) var path = FileInfo.joinPaths(...) - // (3) var obj = DataCollection; - const QString pattern = QStringLiteral("\\b%1\\b"); - - const QRegularExpression regex(pattern.arg(name)); // std::regex is much slower - return any_of(module.m_properties, - [regex](const ExportedProperty &p) { return usesImport(p, regex); }) - || any_of(module.children, - [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); -} - -static QString getLineAtLocation(const CodeLocation &loc, const QString &content) -{ - int pos = 0; - int currentLine = 1; - while (currentLine < loc.line()) { - while (content.at(pos++) != QLatin1Char('\n')) - ; - ++currentLine; - } - const int eolPos = content.indexOf(QLatin1Char('\n'), pos); - return content.mid(pos, eolPos - pos); -} - -void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *) -{ - ExportedModule &exportedModule = m_productContext->product->exportedModule; - setupExportedProperties(exportItem, QString(), exportedModule.m_properties); - static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) { - return p1.fullName < p2.fullName; - }; - std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc); - - transform(exportItem->children(), exportedModule.children, - [&exportedModule, this](const auto &child) { - return resolveExportChild(child, exportedModule); }); - - for (const JsImport &jsImport : exportItem->file()->jsImports()) { - if (usesImport(exportedModule, jsImport.scopeName)) { - exportedModule.importStatements << getLineAtLocation(jsImport.location, - exportItem->file()->content()); - } - } - const auto builtInImports = JsExtensions::extensionNames(); - for (const QString &builtinImport: builtInImports) { - if (usesImport(exportedModule, builtinImport)) - exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport; - } - exportedModule.importStatements.sort(); -} - -// TODO: This probably wouldn't be necessary if we had item serialization. -std::unique_ptr ProjectResolver::resolveExportChild(const Item *item, - const ExportedModule &module) -{ - std::unique_ptr exportedItem(new ExportedItem); - - // This is the type of the built-in base item. It may turn out that we need to support - // derived items under Export. In that case, we probably need a new Item member holding - // the original type name. - exportedItem->name = item->typeName(); - - transform(item->children(), exportedItem->children, [&module, this](const auto &child) { - return resolveExportChild(child, module); }); - - setupExportedProperties(item, QString(), exportedItem->properties); - return exportedItem; -} - -QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value, - const PropertyDeclaration &decl) const -{ - QString &scriptFunction = m_scriptFunctions[std::make_pair(value->sourceCode(), - decl.functionArgumentNames())]; - if (!scriptFunction.isNull()) - return scriptFunction; - const QString args = decl.functionArgumentNames().join(QLatin1Char(',')); - if (value->hasFunctionForm()) { - // Insert the argument list. - scriptFunction = value->sourceCodeForEvaluation(); - scriptFunction.insert(10, args); - // Remove the function application "()" that has been - // added in ItemReaderASTVisitor::visitStatement. - scriptFunction.chop(2); - } else { - scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ") - + value->sourceCode().toString() + QLatin1String(";})"); - } - return scriptFunction; -} - -QString ProjectResolver::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const -{ - QString &code = m_sourceCode[value->sourceCode()]; - if (!code.isNull()) - return code; - code = value->sourceCodeForEvaluation(); - return code; -} - -ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const -{ - JSSourceValuePtr value = item->sourceProperty(name); - ScriptFunctionPtr &script = m_scriptFunctionMap[value ? value->location() : CodeLocation()]; - if (!script.get()) { - script = ScriptFunction::create(); - const PropertyDeclaration decl = item->propertyDeclaration(name); - script->sourceCode = sourceCodeAsFunction(value, decl); - script->location = value->location(); - script->fileContext = resolvedFileContext(value->file()); - } - return script; -} - -ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const -{ - ResolvedFileContextPtr &result = m_fileContextMap[ctx]; - if (!result) - result = ResolvedFileContext::create(*ctx); - return result; -} - -void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - - RulePtr rule = Rule::create(); - - // read artifacts - bool hasArtifactChildren = false; - for (Item * const child : item->children()) { - if (Q_UNLIKELY(child->type() != ItemType::Artifact)) { - throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), - child->location()); - } - hasArtifactChildren = true; - resolveRuleArtifact(rule, child); - } - - rule->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - rule->prepareScript.initialize( - scriptFunctionValue(item, StringConstants::prepareProperty())); - rule->outputArtifactsScript.initialize( - scriptFunctionValue(item, StringConstants::outputArtifactsProperty())); - rule->outputFileTags = m_evaluator->fileTagsValue( - item, StringConstants::outputFileTagsProperty()); - if (rule->outputArtifactsScript.isValid()) { - if (hasArtifactChildren) - throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " - "that contain Artifact items."), - item->location()); - } - if (!hasArtifactChildren && rule->outputFileTags.empty()) { - throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty " - "outputFileTags property."), item->location()); - } - rule->multiplex = m_evaluator->boolValue(item, StringConstants::multiplexProperty()); - rule->alwaysRun = m_evaluator->boolValue(item, StringConstants::alwaysRunProperty()); - rule->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); - rule->inputsFromDependencies - = m_evaluator->fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); - bool requiresInputsSet = false; - rule->requiresInputs = m_evaluator->boolValue(item, StringConstants::requiresInputsProperty(), - &requiresInputsSet); - if (!requiresInputsSet) - rule->requiresInputs = rule->declaresInputs(); - rule->auxiliaryInputs - = m_evaluator->fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); - rule->excludedInputs - = m_evaluator->fileTagsValue(item, StringConstants::excludedInputsProperty()); - if (rule->excludedInputs.empty()) { - rule->excludedInputs = m_evaluator->fileTagsValue( - item, StringConstants::excludedAuxiliaryInputsProperty()); - } - rule->explicitlyDependsOn - = m_evaluator->fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); - rule->explicitlyDependsOnFromDependencies = m_evaluator->fileTagsValue( - item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); - rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; - if (!rule->multiplex && !rule->declaresInputs()) { - throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."), - item->location()); - } - if (!rule->multiplex && !rule->requiresInputs) { - throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."), - item->location()); - } - if (!rule->declaresInputs() && rule->requiresInputs) { - throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule " - "does not declare any input tags."), item->location()); - } - if (m_productContext) { - rule->product = m_productContext->product.get(); - m_productContext->product->rules.push_back(rule); - } else { - projectContext->rules.push_back(rule); - } -} - -void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item) -{ - RuleArtifactPtr artifact = RuleArtifact::create(); - rule->artifacts.push_back(artifact); - artifact->location = item->location(); - - if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty())) - artifact->filePathLocation = sourceProperty->location(); - - artifact->filePath = verbatimValue(item, StringConstants::filePathProperty()); - artifact->fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); - artifact->alwaysUpdated = m_evaluator->boolValue(item, - StringConstants::alwaysUpdatedProperty()); - - QualifiedIdSet seenBindings; - for (Item *obj = item; obj; obj = obj->prototype()) { - for (QMap::const_iterator it = obj->properties().constBegin(); - it != obj->properties().constEnd(); ++it) - { - if (it.value()->type() != Value::ItemValueType) - continue; - resolveRuleArtifactBinding(artifact, - std::static_pointer_cast(it.value())->item(), - QStringList(it.key()), &seenBindings); - } - } -} - -void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, - Item *item, - const QStringList &namePrefix, - QualifiedIdSet *seenBindings) -{ - for (QMap::const_iterator it = item->properties().constBegin(); - it != item->properties().constEnd(); ++it) - { - const QStringList name = QStringList(namePrefix) << it.key(); - if (it.value()->type() == Value::ItemValueType) { - resolveRuleArtifactBinding(ruleArtifact, - std::static_pointer_cast(it.value())->item(), name, - seenBindings); - } else if (it.value()->type() == Value::JSSourceValueType) { - const auto insertResult = seenBindings->insert(name); - if (!insertResult.second) - continue; - JSSourceValuePtr sourceValue = std::static_pointer_cast(it.value()); - RuleArtifact::Binding rab; - rab.name = name; - rab.code = sourceCodeForEvaluation(sourceValue); - rab.location = sourceValue->location(); - ruleArtifact->bindings.push_back(rab); - } else { - QBS_ASSERT(!"unexpected value type", continue); - } - } -} - -void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - std::vector &fileTaggers = m_productContext - ? m_productContext->product->fileTaggers - : projectContext->fileTaggers; - const QStringList patterns = m_evaluator->stringListValue(item, - StringConstants::patternsProperty()); - if (patterns.empty()) - throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); - - const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); - if (fileTags.empty()) - throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); - - for (const QString &pattern : patterns) { - if (pattern.isEmpty()) - throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); - } - - const int priority = m_evaluator->intValue(item, StringConstants::priorityProperty()); - fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); -} - -void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - const QString jobPool = m_evaluator->stringValue(item, StringConstants::jobPoolProperty()); - if (jobPool.isEmpty()) - throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.") - .arg(StringConstants::jobPoolProperty()), item->location()); - bool jobCountWasSet; - const int jobCount = m_evaluator->intValue(item, StringConstants::jobCountProperty(), -1, - &jobCountWasSet); - if (!jobCountWasSet) { - throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.") - .arg(StringConstants::jobCountProperty()), item->location()); - } - if (jobCount < 0) { - throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.") - .arg(StringConstants::jobCountProperty()), item->location()); - } - JobLimits &jobLimits = m_moduleContext - ? m_moduleContext->jobLimits - : m_productContext ? m_productContext->product->jobLimits - : projectContext->jobLimits; - JobLimit jobLimit(jobPool, jobCount); - const int oldLimit = jobLimits.getLimit(jobPool); - if (oldLimit == -1 || oldLimit > jobCount) - jobLimits.setJobLimit(jobLimit); -} - -void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - checkCancelation(); - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) { - qCDebug(lcProjectResolver) << "scanner condition is false"; - return; - } - - ResolvedScannerPtr scanner = ResolvedScanner::create(); - scanner->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; - scanner->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); - scanner->recursive = m_evaluator->boolValue(item, StringConstants::recursiveProperty()); - scanner->searchPathsScript.initialize( - scriptFunctionValue(item, StringConstants::searchPathsProperty())); - scanner->scanScript.initialize( - scriptFunctionValue(item, StringConstants::scanProperty())); - m_productContext->product->scanners.push_back(scanner); -} - -void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector &artifacts) -{ - for (const SourceArtifactPtr &artifact : artifacts) { - for (const auto &artifactProperties : product->artifactProperties) { - if (!artifact->isTargetOfModule() - && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) { - artifact->properties = artifactProperties->propertyMap(); - } - } - } -} - -void ProjectResolver::printProfilingInfo() -{ - if (!m_setupParams.logElapsedTime()) - return; - m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("All property evaluation took %1.") - .arg(elapsedTimeString(m_elapsedTimeAllPropEval)); - m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Module property evaluation took %1.") - .arg(elapsedTimeString(m_elapsedTimeModPropEval)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Resolving groups (without module property " - "evaluation) took %1.") - .arg(elapsedTimeString(m_elapsedTimeGroups)); -} - -class TempScopeSetter -{ -public: - TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope()) - { - value->setScope(newScope, {}); - } - ~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: - ValuePtr m_value; - Item *m_oldScope; -}; - -void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance) -{ - if (!productModuleInstance->isPresentModule()) - return; - Item * const exportItem = productModuleInstance->prototype(); - 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; - const auto &props = exportItem->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - const auto match - = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); }; - if (it.key() != StringConstants::prefixMappingProperty() && - std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) { - continue; - } - if (it.value()->type() == Value::ItemValueType) { - collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance, - exportedModule.modulePropertyValues); - } else { - TempScopeSetter tss(it.value(), productModuleInstance); - evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues, - false); - } - } -} - -// Collects module properties assigned to in other (higher-level) modules. -void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module &module) -{ - if (!module.item->isPresentModule()) - return; - ExportedModule &exportedModule = m_productContext->product->exportedModule; - if (module.productInfo || module.name.first() == StringConstants::qbsModule()) - return; - const auto checkName = [module](const ExportedModuleDependency &d) { - return module.name.toString() == d.name; - }; - if (any_of(exportedModule.moduleDependencies, checkName)) - return; - - Item *modulePrototype = module.item->prototype(); - while (modulePrototype && modulePrototype->type() != ItemType::Module) - modulePrototype = modulePrototype->prototype(); - if (!modulePrototype) // Can happen for broken products in relaxed mode. - return; - const Item::PropertyMap &props = modulePrototype->properties(); - ExportedModuleDependency dep; - dep.name = module.name.toString(); - for (auto it = props.begin(); it != props.end(); ++it) { - if (it.value()->type() == Value::ItemValueType) - collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties); - } - exportedModule.moduleDependencies.push_back(dep); - - for (const auto &dep : module.item->modules()) - collectPropertiesForModuleInExportItem(dep); -} - -void ProjectResolver::resolveProductDependencies() -{ - 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; - 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(); - }); - } -} - -void ProjectResolver::postProcess(const ResolvedProductPtr &product, - ProjectContext *projectContext) const -{ - product->fileTaggers << projectContext->fileTaggers; - std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers), - [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) { - return a->priority() > b->priority(); - }); - for (const RulePtr &rule : projectContext->rules) { - RulePtr clonedRule = rule->clone(); - clonedRule->product = product.get(); - product->rules.push_back(clonedRule); - } -} - -void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const -{ - for (const SourceArtifactPtr &artifact : product->allEnabledFiles()) - applyFileTaggers(artifact, product); -} - -void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product) -{ - if (!artifact->overrideFileTags || artifact->fileTags.empty()) { - const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); - const FileTags fileTags = product->fileTagsForFileName(fileName); - artifact->fileTags.unite(fileTags); - if (artifact->fileTags.empty()) - artifact->fileTags.insert(unknownFileTag()); - qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags - << "to" << fileName; - } -} - -QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototype) -{ - AccumulatingTimer modPropEvalTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeModPropEval : nullptr); - QVariantMap moduleValues; - for (const Item::Module &module : item->modules()) { - if (!module.item->isPresentModule()) - continue; - const QString fullName = module.name.toString(); - moduleValues[fullName] = evaluateProperties(module.item, lookupPrototype, true); - } - - return moduleValues; -} - -QVariantMap ProjectResolver::evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors) -{ - const QVariantMap tmplt; - return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors); -} - -QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *propertiesContainer, - const QVariantMap &tmplt, bool lookupPrototype, bool checkErrors) -{ - AccumulatingTimer propEvalTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeAllPropEval : nullptr); - QVariantMap result = tmplt; - for (QMap::const_iterator it = propertiesContainer->properties().begin(); - it != propertiesContainer->properties().end(); ++it) { - checkCancelation(); - evaluateProperty(item, it.key(), it.value(), result, checkErrors); - } - return lookupPrototype && propertiesContainer->prototype() - ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors) - : result; -} - -void ProjectResolver::evaluateProperty(const Item *item, const QString &propName, - const ValuePtr &propValue, QVariantMap &result, bool checkErrors) -{ - JSContext * const ctx = m_engine->context(); - switch (propValue->type()) { - case Value::ItemValueType: - { - // Ignore items. Those point to module instances - // and are handled in evaluateModuleValues(). - break; - } - case Value::JSSourceValueType: - { - if (result.contains(propName)) - break; - const PropertyDeclaration pd = item->propertyDeclaration(propName); - if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) { - break; - } - const ScopedJsValue scriptValue(ctx, m_evaluator->property(item, propName)); - if (JsException ex = m_evaluator->engine()->checkAndClearException(propValue->location())) { - if (checkErrors) - throw ex.toErrorInfo(); - } - - // NOTE: Loses type information if scriptValue.isUndefined == true, - // as such QScriptValues become invalid QVariants. - QVariant v; - if (JS_IsFunction(ctx, scriptValue)) { - v = getJsString(ctx, scriptValue); - } else { - v = getJsVariant(ctx, scriptValue); - QVariantMap m = v.toMap(); - if (m.contains(StringConstants::importScopeNamePropertyInternal())) { - QVariantMap tmp = m; - const ScopedJsValue proto(ctx, JS_GetPrototype(ctx, scriptValue)); - m = getJsVariant(ctx, proto).toMap(); - for (auto it = tmp.begin(); it != tmp.end(); ++it) - m.insert(it.key(), it.value()); - v = m; - } - } - - if (pd.type() == PropertyDeclaration::Path && v.isValid()) { - v = v.toString(); - } else if (pd.type() == PropertyDeclaration::PathList - || pd.type() == PropertyDeclaration::StringList) { - v = v.toStringList(); - } else if (pd.type() == PropertyDeclaration::VariantList) { - v = v.toList(); - } - checkAllowedValues(v, propValue->location(), pd, propName); - result[propName] = v; - break; - } - case Value::VariantValueType: - { - if (result.contains(propName)) - break; - VariantValuePtr vvp = std::static_pointer_cast(propValue); - QVariant v = vvp->value(); - - const PropertyDeclaration pd = item->propertyDeclaration(propName); - if (v.isNull() && !pd.isScalar()) // QTBUG-51237 - v = QStringList(); - - checkAllowedValues(v, propValue->location(), pd, propName); - result[propName] = v; - break; - } - } -} - -void ProjectResolver::checkAllowedValues( - const QVariant &value, const CodeLocation &loc, const PropertyDeclaration &decl, - const QString &key) const -{ - const auto type = decl.type(); - if (type != PropertyDeclaration::String && type != PropertyDeclaration::StringList) - return; - - if (value.isNull()) - return; - - const auto &allowedValues = decl.allowedValues(); - if (allowedValues.isEmpty()) - return; - - const auto checkValue = [this, &loc, &allowedValues, &key](const QString &value) - { - if (!allowedValues.contains(value)) { - const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.") - .arg(value, key); - ErrorInfo error(message, loc); - handlePropertyError(error, m_setupParams, m_logger); - } - }; - - if (type == PropertyDeclaration::StringList) { - const auto strings = value.toStringList(); - for (const auto &string: strings) { - checkValue(string); - } - } else if (type == PropertyDeclaration::String) { - checkValue(value.toString()); - } -} - -void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleName, - const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps) -{ - QBS_CHECK(value->type() == Value::ItemValueType); - Item * const itemValueItem = std::static_pointer_cast(value)->item(); - 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, const QualifiedId &moduleName) - : valueItem(valueItem), - hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) - { - if (!hadName) { - // Evaluator expects a name here. - valueItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(moduleName.toString())); - } - } - ~EvalPreparer() - { - if (!hadName) - valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr()); - } - Item * const valueItem; - const bool hadName; - }; - EvalPreparer ep(itemValueItem, moduleName); - std::vector tss; - for (const ValuePtr &v : itemValueItem->properties()) - tss.emplace_back(v, moduleInstance); - moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false)); - return; - } - QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix); - const Item::PropertyMap &props = itemValueItem->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QualifiedId fullModuleName = moduleName; - fullModuleName << it.key(); - collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps); - } -} - -void ProjectResolver::createProductConfig(ResolvedProduct *product) -{ - 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); -} - -void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, - ProjectContext *projectContext) -{ - const ItemFuncPtr f = mappings.value(item->type()); - QBS_CHECK(f); - if (item->type() == ItemType::Project) { - ProjectContext subProjectContext = createProjectContext(projectContext); - (this->*f)(item, &subProjectContext); - } else { - (this->*f)(item, projectContext); - } -} - -ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const -{ - ProjectContext subProjectContext; - subProjectContext.parentContext = parentProjectContext; - subProjectContext.project = ResolvedProject::create(); - parentProjectContext->project->subProjects.push_back(subProjectContext.project); - subProjectContext.project->parentProject = parentProjectContext->project; - return subProjectContext; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h deleted file mode 100644 index a8b06b6e6..000000000 --- a/src/lib/corelib/language/projectresolver.h +++ /dev/null @@ -1,202 +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$ -** -****************************************************************************/ - -#ifndef PROJECTRESOLVER_H -#define PROJECTRESOLVER_H - -#include "filetags.h" -#include "item.h" -#include "itemtype.h" -#include "projecttreebuilder.h" -#include "qualifiedid.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace qbs { -class JobLimits; -namespace Internal { - -class Evaluator; -class Item; -class ProgressObserver; -class ScriptEngine; - -class ProjectResolver -{ -public: - ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult, - SetupProjectParameters setupParameters, Logger &logger); - ~ProjectResolver(); - - void setProgressObserver(ProgressObserver *observer); - TopLevelProjectPtr resolve(); - - static void applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product); - - using FileLocations = QHash, CodeLocation>; - static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct, - const QString &fileName, const GroupPtr &group, bool wildcard, - const CodeLocation &filesLocation = CodeLocation(), - FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr); - -private: - struct ProjectContext; - struct ProductContext; - struct ModuleContext; - class ProductContextSwitcher; - - void checkCancelation() const; - QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const; - QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const; - ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; - ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; - void ignoreItem(Item *item, ProjectContext *projectContext); - TopLevelProjectPtr resolveTopLevelProject(); - void resolveProject(Item *item, ProjectContext *projectContext); - void resolveProjectFully(Item *item, ProjectContext *projectContext); - void resolveSubProject(Item *item, ProjectContext *projectContext); - void resolveProduct(Item *item, ProjectContext *projectContext); - void resolveProductFully(Item *item, ProjectContext *projectContext); - void resolveModules(const Item *item, ProjectContext *projectContext); - void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, - const QVariantMap ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext); - void gatherProductTypes(ResolvedProduct *product, Item *item); - QVariantMap resolveAdditionalModuleProperties(const Item *group, - const QVariantMap ¤tValues); - void resolveGroup(Item *item, ProjectContext *projectContext); - void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled); - void resolveShadowProduct(Item *item, ProjectContext *); - void resolveExport(Item *exportItem, ProjectContext *); - std::unique_ptr resolveExportChild(const Item *item, - const ExportedModule &module); - void resolveRule(Item *item, ProjectContext *projectContext); - void resolveRuleArtifact(const RulePtr &rule, Item *item); - void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, - const QStringList &namePrefix, - QualifiedIdSet *seenBindings); - void resolveFileTagger(Item *item, ProjectContext *projectContext); - void resolveJobLimit(Item *item, ProjectContext *projectContext); - void resolveScanner(Item *item, 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); - QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); - QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, - const QVariantMap &tmplt, bool lookupPrototype, - bool checkErrors); - void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, - QVariantMap &result, bool checkErrors); - void checkAllowedValues( - const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl, - const QString &key) const; - void createProductConfig(ResolvedProduct *product); - ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; - void adaptExportedPropertyValues(const Item *shadowProductItem); - void collectExportedProductDependencies(); - - struct ProductDependencyInfo - { - ProductDependencyInfo(ResolvedProductPtr product, - QVariantMap parameters = QVariantMap()) - : product(std::move(product)), parameters(std::move(parameters)) - { - } - - ResolvedProductPtr product; - QVariantMap parameters; - }; - - QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, - const PropertyDeclaration &decl) const; - QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const; - static void matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector &artifacts); - void printProfilingInfo(); - - void collectPropertiesForExportItem(Item *productModuleInstance); - void collectPropertiesForModuleInExportItem(const Item::Module &module); - - void collectPropertiesForExportItem(const QualifiedId &moduleName, - const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps); - void setupExportedProperties(const Item *item, const QString &namePrefix, - std::vector &properties); - - Evaluator *m_evaluator = nullptr; - Logger &m_logger; - ScriptEngine *m_engine = nullptr; - ProgressObserver *m_progressObserver = nullptr; - ProductContext *m_productContext = nullptr; - ModuleContext *m_moduleContext = nullptr; - QHash m_productsByItem; - QHash > m_productsByType; - QHash m_productItemMap; - mutable QHash m_fileContextMap; - mutable QHash m_scriptFunctionMap; - mutable QHash, QString> m_scriptFunctions; - mutable QHash m_sourceCode; - const SetupProjectParameters m_setupParams; - ProjectTreeBuilder::Result m_loadResult; - Set m_groupLocationWarnings; - std::vector> m_productExportInfo; - std::vector m_queuedErrors; - qint64 m_elapsedTimeModPropEval = 0; - qint64 m_elapsedTimeAllPropEval = 0; - qint64 m_elapsedTimeGroups = 0; - - typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); - using ItemFuncMap = QMap; - void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); -}; - -} // namespace Internal -} // namespace qbs - -#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/language/projecttreebuilder.cpp b/src/lib/corelib/language/projecttreebuilder.cpp deleted file mode 100644 index 4667e8a1b..000000000 --- a/src/lib/corelib/language/projecttreebuilder.cpp +++ /dev/null @@ -1,2213 +0,0 @@ -/**************************************************************************** -** -** 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 "filetags.h" -#include "groupshandler.h" -#include "itemreader.h" -#include "language.h" -#include "localprofiles.h" -#include "moduleinstantiator.h" -#include "moduleloader.h" -#include "modulepropertymerger.h" -#include "probesresolver.h" -#include "productitemmultiplexer.h" -#include "scriptengine.h" -#include "value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace qbs::Internal { - -namespace { - -class ContextBase -{ -public: - Item *item = nullptr; - Item *scope = nullptr; - QString name; -}; - -class ProjectContext; - -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; - - struct ResolvedDependsItem { - Item *item = nullptr; - QualifiedId name; - QStringList subModules; - FileTags productTypes; - QStringList multiplexIds; - std::optional 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, - QualifiedId name, QString profile, QString multiplexId) - : item(dependency.item), name(std::move(name)), profile(std::move(profile)), - multiplexId(std::move(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 pendingDependsItems; - std::optional currentDependsItem; - std::queue pendingResolvedDependencies; - bool requiredByLoadingItem = true; - }; - std::list resolveDependenciesState; - - QString uniqueName() const; -}; - -class TopLevelProjectContext -{ -public: - TopLevelProjectContext() = default; - TopLevelProjectContext(const TopLevelProjectContext &) = delete; - TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete; - ~TopLevelProjectContext() { qDeleteAll(projects); } - - std::vector projects; - std::list> productsToHandle; - std::vector probes; - QString buildDirectory; -}; - -class ProjectContext : public ContextBase -{ -public: - TopLevelProjectContext *topLevelProject = nullptr; - ProjectTreeBuilder::Result *result = nullptr; - std::vector products; - std::vector searchPathsStack; -}; - - -using ShadowProductInfo = std::pair; -enum class Deferral { Allowed, NotAllowed }; -enum class HandleDependency { Use, Ignore, Defer }; - -class TimingData { -public: - qint64 prepareProducts = 0; - qint64 productDependencies = 0; - qint64 handleProducts = 0; - qint64 propertyChecking = 0; -}; - -} // namespace - -class ProjectTreeBuilder::Private -{ -public: - Private(const SetupProjectParameters ¶meters, 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 &referencedFilePaths); - void handleProject(Result *loadResult, TopLevelProjectContext *topLevelProjectContext, - Item *projectItem, const Set &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 &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 multiplexProductItem(ProductContext &dummyContext, Item *productItem); - void checkCancelation() const; - QList loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set &referencedFilePaths, - ProductContext &dummyContext); - void copyProperties(const Item *sourceProject, Item *targetProject); - bool mergeExportItems(ProductContext &productContext); - bool checkExportItemCondition(Item *exportItem, const ProductContext &product); - void resolveProbes(ProductContext &product, Item *item); - - 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); - - std::optional - resolveDependsItem(const ProductContext &product, Item *dependsItem); - std::queue - multiplexDependency(const ProductContext &product, - const ProductContext::ResolvedDependsItem &dependency); - static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2); - QVariantMap extractParameters(Item *dependsItem) const; - - const SetupProjectParameters ¶meters; - ItemPool &itemPool; - Evaluator &evaluator; - Logger &logger; - ProgressObserver *progressObserver = nullptr; - TimingData timingData; - ItemReader reader{logger}; - ProbesResolver probesResolver{parameters, evaluator, logger}; - ModuleProviderLoader moduleProviderLoader{parameters, reader, evaluator, probesResolver, logger}; - ModuleLoader moduleLoader{parameters, moduleProviderLoader, 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 disabledItems; - std::unique_ptr settings; - Set projectNamesUsedInOverrides; - Set productNamesUsedInOverrides; - Set disabledProjects; - Set erroneousProducts; - std::multimap productsByName; - - // For fast look-up when resolving Depends.productTypes. - // The contract is that it contains fully handled, error-free, enabled products. - std::multimap productsByType; - - Version qbsVersion; - Item *tempScopeItem = nullptr; - -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 ¶meters, ItemPool &itemPool, - Evaluator &evaluator, Logger &logger) - : d(new Private(parameters, itemPool, evaluator, logger)) -{ - d->reader.setDeprecationWarningMode(parameters.deprecationWarningMode()); - d->reader.setEnableTiming(parameters.logElapsedTime()); - d->settings = std::make_unique(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 &oldProbes) -{ - d->probesResolver.setOldProjectProbes(oldProbes); -} - -void ProjectTreeBuilder::setOldProductProbes( - const QHash> &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..:value")); - e.append(QLatin1Char('\t') + Tr::tr("products..:value")); - e.append(QLatin1Char('\t') + Tr::tr("modules..:value")); - e.append(QLatin1Char('\t') + Tr::tr("products..." - ":value")); - e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.." - ":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 &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 &referencedFilePaths) -{ - auto p = std::make_unique(); - 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); - - resolveProbes(dummyProductContext, projectItem); - projectContext.topLevelProject->probes << dummyProductContext.info.probes; - - localProfiles.collectProfilesFromItems(projectItem, projectContext.scope); - - QList 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 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 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(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 { - 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; - } - } - 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 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.searchPaths); - - 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 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 ¤tSearchPaths = 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 &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(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)); - moduleLoader.printProfilingInfo(6); - 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 &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 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 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 ProjectTreeBuilder::Private::loadReferencedFile( - const QString &relativePath, const CodeLocation &referencingLocation, - const Set &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 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 builtinProjectProperties - = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties(); - Set 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(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(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(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( - baseValue->clone()); - jsValue->setBaseValue(jsBaseValue); - std::vector 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 exportItems; - QList 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 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::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); -} - -void ProjectTreeBuilder::Private::resolveProbes(ProductContext &product, Item *item) -{ - product.info.probes << probesResolver.resolveProbes({product.name, product.uniqueName()}, item); -} - -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 { - 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. - const ModuleLoader::ProductContext loaderContext{ - product.item, product.project->item, product.name, product.uniqueName(), - product.profileName, product.multiplexConfigurationId, product.moduleProperties, - product.profileModuleProperties}; - const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile( - loaderContext, dependency.location(), dependency.name, dependency.fallbackMode, - dependency.requiredGlobally); - moduleItem = loaderResult.moduleItem; - product.info.probes << loaderResult.providerProbes; - - 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.empty()) { - 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; -} - -std::optional -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 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 -ProjectTreeBuilder::Private::multiplexDependency( - const ProductContext &product, const ProductContext::ResolvedDependsItem &dependency) -{ - std::queue dependencies; - if (!dependency.productTypes.empty()) { - std::vector 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; -} - -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 deleted file mode 100644 index 5f7869978..000000000 --- a/src/lib/corelib/language/projecttreebuilder.h +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** -** -** 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 "moduleproviderinfo.h" -#include "moduleproviderloader.h" -#include "qualifiedid.h" - -#include -#include - -namespace qbs { -class SetupProjectParameters; -namespace Internal { -class Evaluator; -class FileTime; -class Item; -class ItemPool; -class ProgressObserver; - -using ModulePropertiesPerGroup = std::unordered_map; - -// 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 ¶meters, ItemPool &itemPool, - Evaluator &evaluator, Logger &logger); - ~ProjectTreeBuilder(); - - struct Result - { - struct ProductInfo - { - std::vector probes; - ModulePropertiesPerGroup modulePropertiesSetInGroups; - ErrorInfo delayedError; - }; - - Item *root = nullptr; - std::unordered_map productInfos; - std::vector projectProbes; - StoredModuleProviderInfo storedModuleProviderInfo; - Set qbsFiles; - QVariantMap profileConfigs; - }; - Result load(); - - void setProgressObserver(ProgressObserver *progressObserver); - void setSearchPaths(const QStringList &searchPaths); - void setOldProjectProbes(const std::vector &oldProbes); - void setOldProductProbes(const QHash> &oldProbes); - void setLastResolveTime(const FileTime &time); - void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo); - -private: - class Private; - Private * const d; -}; - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/loader/astimportshandler.cpp b/src/lib/corelib/loader/astimportshandler.cpp new file mode 100644 index 000000000..f31084f36 --- /dev/null +++ b/src/lib/corelib/loader/astimportshandler.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** 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 "astimportshandler.h" + +#include "itemreadervisitorstate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +ASTImportsHandler::ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, + const FileContextPtr &file) + : m_visitorState(visitorState) + , m_logger(logger) + , m_file(file) + , m_directory(FileInfo::path(m_file->filePath())) +{ +} + +void ASTImportsHandler::handleImports(const QbsQmlJS::AST::UiImportList *uiImportList) +{ + const auto searchPaths = m_file->searchPaths(); + for (const QString &searchPath : searchPaths) + collectPrototypes(searchPath + QStringLiteral("/imports"), QString()); + + // files in the same directory are available as prototypes + collectPrototypes(m_directory, QString()); + + bool baseImported = false; + for (const auto *it = uiImportList; it; it = it->next) + handleImport(it->import, &baseImported); + if (!baseImported) { + QStringRef qbsref(&StringConstants::qbsModule()); + QbsQmlJS::AST::UiQualifiedId qbsURI(qbsref); + qbsURI.finish(); + QbsQmlJS::AST::UiImport imp(&qbsURI); + handleImport(&imp, &baseImported); + } + + for (auto it = m_jsImports.constBegin(); it != m_jsImports.constEnd(); ++it) + m_file->addJsImport(it.value()); +} + +void ASTImportsHandler::handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported) +{ + QStringList importUri; + bool isBase = false; + if (import->importUri) { + importUri = toStringList(import->importUri); + isBase = (importUri.size() == 1 && importUri.front() == StringConstants::qbsModule()) + || (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule() + && importUri.last() == StringConstants::baseVar()); + if (isBase) { + *baseImported = true; + checkImportVersion(import->versionToken); + } else if (import->versionToken.length) { + m_logger.printWarning(ErrorInfo(Tr::tr("Superfluous version specification."), + toCodeLocation(m_file->filePath(), import->versionToken))); + } + } + + QString as; + if (isBase) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of qbs.base must have no 'as '"), + toCodeLocation(m_file->filePath(), import->importIdToken)); + } + } else { + if (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule()) { + const QString extensionName = importUri.last(); + if (JsExtensions::hasExtension(extensionName)) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of built-in extension '%1' " + "must not have 'as' specifier.").arg(extensionName), + toCodeLocation(m_file->filePath(), import->asToken)); + } + if (Q_UNLIKELY(m_file->jsExtensions().contains(extensionName))) { + m_logger.printWarning(ErrorInfo(Tr::tr("Built-in extension '%1' already " + "imported.").arg(extensionName), + toCodeLocation(m_file->filePath(), + import->importToken))); + } else { + m_file->addJsExtension(extensionName); + } + return; + } + } + + if (import->importId.isNull()) { + if (!import->fileName.isNull()) { + throw ErrorInfo(Tr::tr("File imports require 'as '"), + toCodeLocation(m_file->filePath(), import->importToken)); + } + if (importUri.empty()) { + throw ErrorInfo(Tr::tr("Invalid import URI."), + toCodeLocation(m_file->filePath(), import->importToken)); + } + as = importUri.last(); + } else { + as = import->importId.toString(); + } + + if (Q_UNLIKELY(JsExtensions::hasExtension(as))) + throw ErrorInfo(Tr::tr("Cannot reuse the name of built-in extension '%1'.").arg(as), + toCodeLocation(m_file->filePath(), import->importIdToken)); + if (Q_UNLIKELY(!m_importAsNames.insert(as).second)) { + throw ErrorInfo(Tr::tr("Cannot import into the same name more than once."), + toCodeLocation(m_file->filePath(), import->importIdToken)); + } + } + + if (!import->fileName.isNull()) { + QString filePath = FileInfo::resolvePath(m_directory, import->fileName.toString()); + + QFileInfo fi(filePath); + if (Q_UNLIKELY(!fi.exists())) + throw ErrorInfo(Tr::tr("Cannot find imported file %0.") + .arg(QDir::toNativeSeparators(filePath)), + CodeLocation(m_file->filePath(), import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + filePath = fi.canonicalFilePath(); + if (fi.isDir()) { + collectPrototypesAndJsCollections(filePath, as, + toCodeLocation(m_file->filePath(), import->fileNameToken)); + } else { + if (filePath.endsWith(QStringLiteral(".js"), Qt::CaseInsensitive)) { + JsImport &jsImport = m_jsImports[as]; + jsImport.scopeName = as; + jsImport.filePaths.push_back(filePath); + jsImport.location + = toCodeLocation(m_file->filePath(), import->firstSourceLocation()); + } else if (filePath.endsWith(QStringLiteral(".qbs"), Qt::CaseInsensitive)) { + m_typeNameToFile.insert(QStringList(as), filePath); + } else { + throw ErrorInfo(Tr::tr("Can only import .qbs and .js files"), + CodeLocation(m_file->filePath(), import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + } + } + } else if (!importUri.empty()) { + const QString importPath = isBase + ? QStringLiteral("qbs/base") : importUri.join(QDir::separator()); + bool found = m_typeNameToFile.contains(importUri); + if (!found) { + const auto searchPaths = m_file->searchPaths(); + for (const QString &searchPath : searchPaths) { + const QFileInfo fi(FileInfo::resolvePath( + FileInfo::resolvePath(searchPath, + StringConstants::importsDir()), + importPath)); + if (fi.isDir()) { + // ### versioning, qbsdir file, etc. + const QString &resultPath = fi.absoluteFilePath(); + collectPrototypesAndJsCollections(resultPath, as, + toCodeLocation(m_file->filePath(), import->fileNameToken)); + found = true; + break; + } + } + } + if (Q_UNLIKELY(!found)) { + throw ErrorInfo(Tr::tr("import %1 not found") + .arg(importUri.join(QLatin1Char('.'))), + toCodeLocation(m_file->filePath(), import->fileNameToken)); + } + } +} + +Version ASTImportsHandler::readImportVersion(const QString &str, const CodeLocation &location) +{ + const Version v = Version::fromString(str); + if (Q_UNLIKELY(!v.isValid())) + throw ErrorInfo(Tr::tr("Cannot parse version number in import statement."), location); + if (Q_UNLIKELY(v.patchLevel() != 0)) { + throw ErrorInfo(Tr::tr("Version number in import statement cannot have more than " + "two components."), location); + } + return v; +} + +bool ASTImportsHandler::addPrototype(const QString &fileName, const QString &filePath, + const QString &as, bool needsCheck) +{ + if (needsCheck && fileName.size() <= 4) + return false; + + const QString componentName = fileName.left(fileName.size() - 4); + // ### validate componentName + + if (needsCheck && !componentName.at(0).isUpper()) + return false; + + QStringList prototypeName; + if (!as.isEmpty()) + prototypeName.push_back(as); + prototypeName.push_back(componentName); + if (!m_typeNameToFile.contains(prototypeName)) + m_typeNameToFile.insert(prototypeName, filePath); + return true; +} + +void ASTImportsHandler::checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const +{ + if (!versionToken.length) + return; + const QString importVersionString + = m_file->content().mid(versionToken.offset, versionToken.length); + const Version importVersion = readImportVersion(importVersionString, + toCodeLocation(m_file->filePath(), versionToken)); + if (Q_UNLIKELY(importVersion != BuiltinDeclarations::instance().languageVersion())) + throw ErrorInfo(Tr::tr("Incompatible qbs language version %1. This is version %2.").arg( + importVersionString, + BuiltinDeclarations::instance().languageVersion().toString()), + toCodeLocation(m_file->filePath(), versionToken)); + +} + +void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as) +{ + QStringList fileNames; // Yes, file *names*. + if (m_visitorState.findDirectoryEntries(path, &fileNames)) { + for (const QString &fileName : qAsConst(fileNames)) + addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); + return; + } + + QDirIterator dirIter(path, StringConstants::qbsFileWildcards()); + while (dirIter.hasNext()) { + const QString filePath = dirIter.next(); + const QString fileName = dirIter.fileName(); + if (addPrototype(fileName, filePath, as, true)) + fileNames << fileName; + } + m_visitorState.cacheDirectoryEntries(path, fileNames); + +} + +void ASTImportsHandler::collectPrototypesAndJsCollections(const QString &path, const QString &as, + const CodeLocation &location) +{ + collectPrototypes(path, as); + QDirIterator dirIter(path, StringConstants::jsFileWildcards()); + while (dirIter.hasNext()) { + dirIter.next(); + JsImport &jsImport = m_jsImports[as]; + if (jsImport.scopeName.isNull()) { + jsImport.scopeName = as; + jsImport.location = location; + } + jsImport.filePaths.push_back(dirIter.filePath()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/astimportshandler.h b/src/lib/corelib/loader/astimportshandler.h new file mode 100644 index 000000000..582e1c698 --- /dev/null +++ b/src/lib/corelib/loader/astimportshandler.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_ASTIMPORTSHANDLER_H +#define QBS_ASTIMPORTSHANDLER_H + +#include + +#include +#include + +#include +#include + +namespace qbs { +class CodeLocation; +class Version; + +namespace Internal { +class ItemReaderVisitorState; +class JsImport; +class Logger; + +class ASTImportsHandler +{ +public: + ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, + const FileContextPtr &file); + + void handleImports(const QbsQmlJS::AST::UiImportList *uiImportList); + + QHash typeNameFileMap() const { return m_typeNameToFile; } + +private: + static Version readImportVersion(const QString &str, const CodeLocation &location); + + bool addPrototype(const QString &fileName, const QString &filePath, const QString &as, + bool needsCheck); + void checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const; + void collectPrototypes(const QString &path, const QString &as); + void collectPrototypesAndJsCollections(const QString &path, const QString &as, + const CodeLocation &location); + void handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported); + + ItemReaderVisitorState &m_visitorState; + Logger &m_logger; + const FileContextPtr &m_file; + const QString m_directory; + QHash m_typeNameToFile; + Set m_importAsNames; + + using JsImportsHash = QHash; + JsImportsHash m_jsImports; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/loader/astpropertiesitemhandler.cpp b/src/lib/corelib/loader/astpropertiesitemhandler.cpp new file mode 100644 index 000000000..fbb8761b5 --- /dev/null +++ b/src/lib/corelib/loader/astpropertiesitemhandler.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** 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 "astpropertiesitemhandler.h" + +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem) : m_parentItem(parentItem) +{ +} + +void ASTPropertiesItemHandler::handlePropertiesItems() +{ + // TODO: Simply forbid Properties items to have child items and get rid of this check. + if (m_parentItem->type() != ItemType::Properties) + setupAlternatives(); +} + +void ASTPropertiesItemHandler::setupAlternatives() +{ + auto it = m_parentItem->m_children.begin(); + while (it != m_parentItem->m_children.end()) { + Item * const child = *it; + bool remove = false; + if (child->type() == ItemType::Properties) { + handlePropertiesBlock(child); + remove = m_parentItem->type() != ItemType::Export; + } + if (remove) + it = m_parentItem->m_children.erase(it); + else + ++it; + } +} + +class PropertiesBlockConverter +{ +public: + PropertiesBlockConverter(const JSSourceValue::AltProperty &condition, + const JSSourceValue::AltProperty &overrideListProperties, + Item *propertiesBlockContainer, const Item *propertiesBlock) + : m_propertiesBlockContainer(propertiesBlockContainer) + , m_propertiesBlock(propertiesBlock) + { + m_alternative.condition = condition; + m_alternative.overrideListProperties = overrideListProperties; + } + + void apply() + { + doApply(m_propertiesBlockContainer, m_propertiesBlock); + } + +private: + JSSourceValue::Alternative m_alternative; + Item * const m_propertiesBlockContainer; + const Item * const m_propertiesBlock; + + void doApply(Item *outer, const Item *inner) + { + for (auto it = inner->properties().constBegin(); + it != inner->properties().constEnd(); ++it) { + if (inner == m_propertiesBlock + && (it.key() == StringConstants::conditionProperty() + || it.key() == StringConstants::overrideListPropertiesProperty())) { + continue; + } + if (it.value()->type() == Value::ItemValueType) { + Item * const innerVal = std::static_pointer_cast(it.value())->item(); + ItemValuePtr outerVal = outer->itemProperty(it.key()); + if (!outerVal) { + outerVal = ItemValue::create(Item::create(outer->pool(), innerVal->type()), + true); + outer->setProperty(it.key(), outerVal); + } + doApply(outerVal->item(), innerVal); + } else if (it.value()->type() == Value::JSSourceValueType) { + const ValuePtr outerVal = outer->property(it.key()); + if (Q_UNLIKELY(outerVal && outerVal->type() != Value::JSSourceValueType)) { + throw ErrorInfo(Tr::tr("Incompatible value type in unconditional value at %1.") + .arg(outerVal->location().toString())); + } + doApply(it.key(), outer, std::static_pointer_cast(outerVal), + std::static_pointer_cast(it.value())); + } else { + QBS_CHECK(!"Unexpected value type in conditional value."); + } + } + } + + void doApply(const QString &propertyName, Item *item, JSSourceValuePtr value, + const JSSourceValuePtr &conditionalValue) + { + if (!value) { + value = JSSourceValue::create(true); + value->setFile(conditionalValue->file()); + item->setProperty(propertyName, value); + value->setSourceCode(StringConstants::baseVar()); + value->setSourceUsesBase(); + } + m_alternative.value = conditionalValue; + value->addAlternative(m_alternative); + } +}; + +static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, const QString &name) +{ + const ValuePtr value = propertiesItem->property(name); + if (!value) { + if (name == StringConstants::conditionProperty()) { + throw ErrorInfo(Tr::tr("Properties.condition must be provided."), + propertiesItem->location()); + } + return JSSourceValue::AltProperty(StringConstants::falseValue(), + propertiesItem->location()); + } + if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) { + throw ErrorInfo(Tr::tr("Properties.%1 must be a value binding.").arg(name), + propertiesItem->location()); + } + if (name == StringConstants::overrideListPropertiesProperty()) { + const Item *parent = propertiesItem->parent(); + while (parent) { + if (parent->type() == ItemType::Product) + break; + parent = parent->parent(); + } + if (!parent) { + throw ErrorInfo(Tr::tr("Properties.overrideListProperties can only be set " + "in a Product item.")); + } + + } + const JSSourceValuePtr srcval = std::static_pointer_cast(value); + return JSSourceValue::AltProperty(srcval->sourceCodeForEvaluation(), srcval->location()); +} + +void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem) +{ + const auto condition = getPropertyData(propertiesItem, StringConstants::conditionProperty()); + const auto overrideListProperties = getPropertyData(propertiesItem, + StringConstants::overrideListPropertiesProperty()); + PropertiesBlockConverter(condition, overrideListProperties, m_parentItem, + propertiesItem).apply(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/astpropertiesitemhandler.h b/src/lib/corelib/loader/astpropertiesitemhandler.h new file mode 100644 index 000000000..413512ee5 --- /dev/null +++ b/src/lib/corelib/loader/astpropertiesitemhandler.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_ASTPROPERTIESITEMHANDLER_H +#define QBS_ASTPROPERTIESITEMHANDLER_H + +namespace qbs { +namespace Internal { +class Item; + +class ASTPropertiesItemHandler +{ +public: + ASTPropertiesItemHandler(Item *parentItem); + + void handlePropertiesItems(); + +private: + void setupAlternatives(); + void handlePropertiesBlock(const Item *propertiesItem); + + Item * const m_parentItem; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/loader/groupshandler.cpp b/src/lib/corelib/loader/groupshandler.cpp new file mode 100644 index 000000000..dd183eb16 --- /dev/null +++ b/src/lib/corelib/loader/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 "moduleinstantiator.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace qbs::Internal { +class GroupsHandler::Private +{ +public: + Private(const SetupProjectParameters ¶meters, 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 ¶meters; + ModuleInstantiator &moduleInstantiator; + Evaluator &evaluator; + Logger &logger; + std::unordered_map modulePropsSetInGroups; + Set disabledGroups; + qint64 elapsedTime = 0; +}; + +GroupsHandler::GroupsHandler(const SetupProjectParameters ¶meters, + 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 GroupsHandler::modulePropertiesSetInGroups() const +{ + return d->modulePropsSetInGroups; +} + +Set 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(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 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(it.value()).get(), + QualifiedId(it.key()), propsSetInGroup); + } + } + return propsSetInGroup; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/groupshandler.h b/src/lib/corelib/loader/groupshandler.h new file mode 100644 index 000000000..3f787903f --- /dev/null +++ b/src/lib/corelib/loader/groupshandler.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 +#include + +#include +#include + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class Evaluator; +class Item; +class Logger; +class ModuleInstantiator; + +// 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: + GroupsHandler(const SetupProjectParameters ¶meters, ModuleInstantiator &instantiator, + Evaluator &evaluator, Logger &logger); + ~GroupsHandler(); + + void setupGroups(Item *product, Item *productScope); + std::unordered_map modulePropertiesSetInGroups() const; + Set disabledGroups() const; + + void printProfilingInfo(int indent); + +private: + class Private; + Private * const d; +}; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp new file mode 100644 index 000000000..3af79df2e --- /dev/null +++ b/src/lib/corelib/loader/itemreader.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** 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 "itemreader.h" + +#include "itemreadervisitorstate.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +static void makePathsCanonical(QStringList &paths) +{ + Internal::removeIf(paths, [](QString &p) { + p = QFileInfo(p).canonicalFilePath(); + return p.isEmpty(); + }); +} + +ItemReader::ItemReader(Logger &logger) + : m_visitorState(std::make_unique(logger)) +{ +} + +ItemReader::~ItemReader() = default; + +void ItemReader::setSearchPaths(const QStringList &searchPaths) +{ + m_searchPaths = searchPaths; + makePathsCanonical(m_searchPaths); + m_allSearchPaths.clear(); +} + +void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) +{ + m_extraSearchPaths.push_back(extraSearchPaths); + makePathsCanonical(m_extraSearchPaths.back()); + m_allSearchPaths.clear(); +} + +void ItemReader::popExtraSearchPaths() +{ + m_extraSearchPaths.pop_back(); + m_allSearchPaths.clear(); +} + +const std::vector &ItemReader::extraSearchPathsStack() const +{ + return m_extraSearchPaths; +} + +void ItemReader::setExtraSearchPathsStack(const std::vector &s) +{ + m_extraSearchPaths = s; + m_allSearchPaths.clear(); +} + +void ItemReader::clearExtraSearchPathsStack() +{ + m_extraSearchPaths.clear(); + m_allSearchPaths.clear(); +} + +const QStringList &ItemReader::allSearchPaths() const +{ + if (m_allSearchPaths.empty()) { + std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(), + [this] (const QStringList &paths) { + m_allSearchPaths += paths; + }); + m_allSearchPaths += m_searchPaths; + m_allSearchPaths.removeDuplicates(); + } + return m_allSearchPaths; +} + +Item *ItemReader::readFile(const QString &filePath) +{ + AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr); + return m_visitorState->readFile(filePath, allSearchPaths(), m_pool); +} + +Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation) +{ + try { + return readFile(filePath); + } catch (const ErrorInfo &e) { + if (e.hasLocation()) + throw; + throw ErrorInfo(e.toString(), referencingLocation); + } +} + +Set ItemReader::filesRead() const +{ + return m_visitorState->filesRead(); +} + +void ItemReader::setEnableTiming(bool on) +{ + m_elapsedTime = on ? 0 : -1; +} + +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 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; +} + +SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths) + : m_itemReader(itemReader), + m_oldSize(itemReader.extraSearchPathsStack().size()) +{ + if (!extraSearchPaths.isEmpty()) + m_itemReader.pushExtraSearchPaths(extraSearchPaths); +} + +SearchPathsManager::~SearchPathsManager() +{ + while (m_itemReader.extraSearchPathsStack().size() > m_oldSize) + m_itemReader.popExtraSearchPaths(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/itemreader.h b/src/lib/corelib/loader/itemreader.h new file mode 100644 index 000000000..751c9b1b6 --- /dev/null +++ b/src/lib/corelib/loader/itemreader.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QBS_ITEMREADER_H +#define QBS_ITEMREADER_H + +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class Evaluator; +class Item; +class ItemPool; +class ItemReaderVisitorState; + +/* + * Reads a qbs file and creates a tree of Item objects. + * + * In this stage the following steps are performed: + * - The QML/JS parser creates the AST. + * - The AST is converted to a tree of Item objects. + * + * This class is also responsible for the QMLish inheritance semantics. + */ +class ItemReader +{ +public: + ItemReader(Logger &logger); + ~ItemReader(); + + void setPool(ItemPool *pool) { m_pool = pool; } + void setSearchPaths(const QStringList &searchPaths); + void pushExtraSearchPaths(const QStringList &extraSearchPaths); + void popExtraSearchPaths(); + const std::vector &extraSearchPathsStack() const; + void setExtraSearchPathsStack(const std::vector &s); + void clearExtraSearchPathsStack(); + const QStringList &allSearchPaths() const; + + // 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 filesRead() const; + + void setEnableTiming(bool on); + qint64 elapsedTime() const { return m_elapsedTime; } + + 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 m_extraSearchPaths; + mutable QStringList m_allSearchPaths; + const std::unique_ptr m_visitorState; + qint64 m_elapsedTime = -1; +}; + +class SearchPathsManager { +public: + SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths = {}); + ~SearchPathsManager(); + +private: + ItemReader &m_itemReader; + size_t m_oldSize{0}; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADER_H diff --git a/src/lib/corelib/loader/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp new file mode 100644 index 000000000..94be46bf3 --- /dev/null +++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** 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 "itemreaderastvisitor.h" + +#include "astimportshandler.h" +#include "astpropertiesitemhandler.h" +#include "itemreadervisitorstate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace QbsQmlJS; + +namespace qbs { +namespace Internal { + +ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, + FileContextPtr file, ItemPool *itemPool, Logger &logger) + : m_visitorState(visitorState) + , m_file(std::move(file)) + , m_itemPool(itemPool) + , m_logger(logger) +{ +} + +bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram) +{ + ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file); + importsHandler.handleImports(uiProgram->imports); + m_typeNameToFile = importsHandler.typeNameFileMap(); + return true; +} + +static ItemValuePtr findItemProperty(const Item *container, const Item *item) +{ + ItemValuePtr itemValue; + const auto &srcprops = container->properties(); + auto it = std::find_if(srcprops.begin(), srcprops.end(), [item] (const ValuePtr &v) { + return v->type() == Value::ItemValueType + && std::static_pointer_cast(v)->item() == item; + }); + if (it != srcprops.end()) + itemValue = std::static_pointer_cast(it.value()); + return itemValue; +} + +bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) +{ + const QString typeName = ast->qualifiedTypeNameId->name.toString(); + const CodeLocation itemLocation = toCodeLocation(ast->qualifiedTypeNameId->identifierToken); + const Item *baseItem = nullptr; + Item *mostDerivingItem = nullptr; + + Item *item = Item::create(m_itemPool, ItemType::Unknown); + item->setFile(m_file); + item->setLocation(itemLocation); + + // Inheritance resolving, part 1: Find out our actual type name (needed for setting + // up children and alternatives). + const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId); + const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName); + ItemType itemType; + if (!baseTypeFileName.isEmpty()) { + const bool isMostDerivingItem = (m_visitorState.mostDerivingItem() == nullptr); + if (isMostDerivingItem) + m_visitorState.setMostDerivingItem(item); + mostDerivingItem = m_visitorState.mostDerivingItem(); + baseItem = m_visitorState.readFile(baseTypeFileName, m_file->searchPaths(), m_itemPool); + if (isMostDerivingItem) + m_visitorState.setMostDerivingItem(nullptr); + QBS_CHECK(baseItem->type() <= ItemType::LastActualItem); + itemType = baseItem->type(); + } else { + if (fullTypeName.size() > 1) { + throw ErrorInfo(Tr::tr("Invalid item '%1'. Did you mean to set a module property?") + .arg(fullTypeName.join(QLatin1Char('.'))), itemLocation); + } + itemType = BuiltinDeclarations::instance().typeForName(typeName, itemLocation); + checkDeprecationStatus(itemType, typeName, itemLocation); + if (itemType == ItemType::Properties && m_item && m_item->type() == ItemType::SubProject) + itemType = ItemType::PropertiesInSubProject; + } + + item->m_type = itemType; + + if (m_item) + Item::addChild(m_item, item); // Add this item to the children of the parent item. + else + m_item = item; // This is the root item. + + if (ast->initializer) { + Item *mdi = m_visitorState.mostDerivingItem(); + m_visitorState.setMostDerivingItem(nullptr); + qSwap(m_item, item); + const ItemType oldInstanceItemType = m_instanceItemType; + if (itemType == ItemType::Parameters || itemType == ItemType::Depends) + m_instanceItemType = ItemType::ModuleParameters; + ast->initializer->accept(this); + m_instanceItemType = oldInstanceItemType; + qSwap(m_item, item); + m_visitorState.setMostDerivingItem(mdi); + } + + ASTPropertiesItemHandler(item).handlePropertiesItems(); + + // Inheritance resolving, part 2 (depends on alternatives having been set up). + if (baseItem) { + inheritItem(item, baseItem); + if (baseItem->file()->idScope()) { + // Make ids from the derived file visible in the base file. + // ### Do we want to turn off this feature? It's QMLish but kind of strange. + item->file()->ensureIdScope(m_itemPool); + baseItem->file()->idScope()->setPrototype(item->file()->idScope()); + + // Replace the base item with the most deriving item. + ItemValuePtr baseItemIdValue = findItemProperty(baseItem->file()->idScope(), baseItem); + if (baseItemIdValue) + baseItemIdValue->setItem(mostDerivingItem); + } + } else { + // Only the item at the top of the inheritance chain is a built-in item. + // We cannot do this in "part 1", because then the visitor would complain about duplicate + // bindings. + item->setupForBuiltinType(m_visitorState.deprecationWarningMode(), m_logger); + } + + return false; +} + +void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName, + const AST::SourceLocation &sourceLocation) +{ + if (Q_UNLIKELY(item->hasOwnProperty(bindingName.last()))) { + QString msg = Tr::tr("Duplicate binding for '%1'"); + throw ErrorInfo(msg.arg(bindingName.join(QLatin1Char('.'))), + toCodeLocation(sourceLocation)); + } +} + +bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast) +{ + PropertyDeclaration p; + if (Q_UNLIKELY(ast->name.isEmpty())) + throw ErrorInfo(Tr::tr("public member without name")); + if (Q_UNLIKELY(ast->memberType.isEmpty())) + throw ErrorInfo(Tr::tr("public member without type")); + if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal)) + throw ErrorInfo(Tr::tr("public member with signal type not supported")); + p.setName(ast->name.toString()); + p.setType(PropertyDeclaration::propertyTypeFromString(ast->memberType.toString())); + if (p.type() == PropertyDeclaration::UnknownType) { + throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.") + .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken)); + } + if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) { + throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg( + ast->typeModifier.toString())); + } + if (ast->isReadonlyMember) + p.setFlags(PropertyDeclaration::ReadOnlyFlag); + + m_item->m_propertyDeclarations.insert(p.name(), p); + + const JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + if (ast->statement) { + handleBindingRhs(ast->statement, value); + const QStringList bindingName(p.name()); + checkDuplicateBinding(m_item, bindingName, ast->colonToken); + } + + m_item->setProperty(p.name(), value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) +{ + QBS_CHECK(ast->qualifiedId); + QBS_CHECK(!ast->qualifiedId->name.isEmpty()); + + const QStringList bindingName = toStringList(ast->qualifiedId); + + if (bindingName.length() == 1 && bindingName.front() == QStringLiteral("id")) { + const auto * const expStmt = AST::cast(ast->statement); + if (Q_UNLIKELY(!expStmt)) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + const auto * const idExp = AST::cast(expStmt->expression); + if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + m_item->m_id = idExp->name.toString(); + m_file->ensureIdScope(m_itemPool); + ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id()); + if (existingId) { + ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id())); + e.append(Tr::tr("First occurrence is here."), existingId->item()->location()); + e.append(Tr::tr("Next occurrence is here."), m_item->location()); + throw e; + } + m_file->idScope()->setProperty(m_item->id(), ItemValue::create(m_item)); + return false; + } + + const JSSourceValuePtr value = JSSourceValue::create(); + handleBindingRhs(ast->statement, value); + + Item * const targetItem = targetItemForBinding(bindingName, value); + checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken); + targetItem->setProperty(bindingName.last(), value); + return false; +} + +bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement, + const JSSourceValuePtr &value) +{ + QBS_CHECK(statement); + QBS_CHECK(value); + + if (AST::cast(statement)) + value->setHasFunctionForm(); + + value->setFile(m_file); + value->setSourceCode(textViewOf(m_file->content(), statement)); + value->setLocation(statement->firstSourceLocation().startLine, + statement->firstSourceLocation().startColumn); + + bool usesBase, usesOuter, usesOriginal; + IdentifierSearch idsearch; + idsearch.add(StringConstants::baseVar(), &usesBase); + idsearch.add(StringConstants::outerVar(), &usesOuter); + idsearch.add(StringConstants::originalVar(), &usesOriginal); + idsearch.start(statement); + if (usesBase) + value->setSourceUsesBase(); + if (usesOuter) + value->setSourceUsesOuter(); + if (usesOriginal) + value->setSourceUsesOriginal(); + return false; +} + +CodeLocation ItemReaderASTVisitor::toCodeLocation(const AST::SourceLocation &location) const +{ + return CodeLocation(m_file->filePath(), location.startLine, location.startColumn); +} + +Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName, + const JSSourceValueConstPtr &value) +{ + Item *targetItem = m_item; + const int c = bindingName.size() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->ownProperty(bindingName.at(i)); + if (!v) { + const ItemType itemType = i < c - 1 ? ItemType::ModulePrefix : m_instanceItemType; + Item *newItem = Item::create(m_itemPool, itemType); + newItem->setLocation(value->location()); + v = ItemValue::create(newItem); + targetItem->setProperty(bindingName.at(i), v); + } + if (Q_UNLIKELY(v->type() != Value::ItemValueType)) { + QString msg = Tr::tr("Binding to non-item property."); + throw ErrorInfo(msg, value->location()); + } + targetItem = std::static_pointer_cast(v)->item(); + } + return targetItem; +} + +void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src) +{ + int insertPos = 0; + for (Item *child : qAsConst(src->m_children)) { + dst->m_children.insert(insertPos++, child); + child->m_parent = dst; + } + + for (const PropertyDeclaration &pd : src->propertyDeclarations()) { + if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag) + && dst->hasOwnProperty(pd.name())) { + throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), + dst->property(pd.name())->location()); + } + dst->setPropertyDeclaration(pd.name(), pd); + } + + for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) { + ValuePtr &v = dst->m_properties[it.key()]; + if (!v) { + v = it.value(); + continue; + } + if (v->type() == Value::ItemValueType && it.value()->type() != Value::ItemValueType) + throw ErrorInfo(Tr::tr("Binding to non-item property."), v->location()); + if (v->type() != it.value()->type()) + continue; + switch (v->type()) { + case Value::JSSourceValueType: { + JSSourceValuePtr sv = std::static_pointer_cast(v); + QBS_CHECK(!sv->baseValue()); + const JSSourceValuePtr baseValue = std::static_pointer_cast(it.value()); + sv->setBaseValue(baseValue); + for (const JSSourceValue::Alternative &alt : sv->m_alternatives) + alt.value->setBaseValue(baseValue); + break; + } + case Value::ItemValueType: + inheritItem(std::static_pointer_cast(v)->item(), + std::static_pointer_cast(it.value())->item()); + break; + default: + QBS_CHECK(!"unexpected value type"); + } + } +} + +void ItemReaderASTVisitor::checkDeprecationStatus(ItemType itemType, const QString &itemName, + const CodeLocation &itemLocation) +{ + const ItemDeclaration itemDecl = BuiltinDeclarations::instance().declarationsForType(itemType); + const ErrorInfo error = itemDecl.checkForDeprecation(m_visitorState.deprecationWarningMode(), + itemName, itemLocation, m_logger); + if (error.hasError()) + throw error; +} + +void ItemReaderASTVisitor::doCheckItemTypes(const Item *item) +{ + const ItemDeclaration decl = BuiltinDeclarations::instance().declarationsForType(item->type()); + for (const Item * const child : item->children()) { + if (!decl.isChildTypeAllowed(child->type())) { + throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") + .arg(item->typeName(), child->typeName()), child->location()); + } + doCheckItemTypes(child); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/itemreaderastvisitor.h b/src/lib/corelib/loader/itemreaderastvisitor.h new file mode 100644 index 000000000..a102b2821 --- /dev/null +++ b/src/lib/corelib/loader/itemreaderastvisitor.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QBS_ITEMREADERASTVISITOR_H +#define QBS_ITEMREADERASTVISITOR_H + +#include +#include + +#include +#include + +#include +#include + +namespace qbs { +class CodeLocation; + +namespace Internal { +class Item; +class ItemPool; +class ItemReaderVisitorState; + +class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor +{ +public: + ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, FileContextPtr file, + ItemPool *itemPool, Logger &logger); + void checkItemTypes() { doCheckItemTypes(rootItem()); } + + Item *rootItem() const { return m_item; } + +private: + bool visit(QbsQmlJS::AST::UiProgram *uiProgram) override; + bool visit(QbsQmlJS::AST::UiObjectDefinition *ast) override; + bool visit(QbsQmlJS::AST::UiPublicMember *ast) override; + bool visit(QbsQmlJS::AST::UiScriptBinding *ast) override; + + bool handleBindingRhs(QbsQmlJS::AST::Statement *statement, const JSSourceValuePtr &value); + CodeLocation toCodeLocation(const QbsQmlJS::AST::SourceLocation &location) const; + void checkDuplicateBinding(Item *item, const QStringList &bindingName, + const QbsQmlJS::AST::SourceLocation &sourceLocation); + Item *targetItemForBinding(const QStringList &binding, const JSSourceValueConstPtr &value); + static void inheritItem(Item *dst, const Item *src); + void checkDeprecationStatus(ItemType itemType, const QString &itemName, + const CodeLocation &itemLocation); + void doCheckItemTypes(const Item *item); + + ItemReaderVisitorState &m_visitorState; + const FileContextPtr m_file; + ItemPool * const m_itemPool; + Logger &m_logger; + QHash m_typeNameToFile; + Item *m_item = nullptr; + ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADERASTVISITOR_H diff --git a/src/lib/corelib/loader/itemreadervisitorstate.cpp b/src/lib/corelib/loader/itemreadervisitorstate.cpp new file mode 100644 index 000000000..57484043a --- /dev/null +++ b/src/lib/corelib/loader/itemreadervisitorstate.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** 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 "itemreadervisitorstate.h" + +#include "itemreaderastvisitor.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class ASTCacheValueData : public QSharedData +{ + Q_DISABLE_COPY(ASTCacheValueData) +public: + ASTCacheValueData() + : ast(nullptr) + , processing(false) + { + } + + QString code; + QbsQmlJS::Engine engine; + QbsQmlJS::AST::UiProgram *ast; + bool processing; +}; + +class ASTCacheValue +{ +public: + ASTCacheValue() + : d(new ASTCacheValueData) + { + } + + ASTCacheValue(const ASTCacheValue &other) = default; + + void setProcessingFlag(bool b) { d->processing = b; } + bool isProcessing() const { return d->processing; } + + void setCode(const QString &code) { d->code = code; } + QString code() const { return d->code; } + + QbsQmlJS::Engine *engine() const { return &d->engine; } + + void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; } + QbsQmlJS::AST::UiProgram *ast() const { return d->ast; } + bool isValid() const { return d->ast; } + +private: + QExplicitlySharedDataPointer d; +}; + +class ItemReaderVisitorState::ASTCache : public std::unordered_map {}; + + +ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger) + : m_logger(logger) + , m_astCache(std::make_unique()) +{ + +} + +ItemReaderVisitorState::~ItemReaderVisitorState() = default; + +Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringList &searchPaths, + ItemPool *itemPool) +{ + ASTCacheValue &cacheValue = (*m_astCache)[filePath]; + if (cacheValue.isValid()) { + if (Q_UNLIKELY(cacheValue.isProcessing())) + throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); + } else { + QFile file(filePath); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); + + m_filesRead.insert(filePath); + QTextStream stream(&file); + setupDefaultCodec(stream); + const QString &code = stream.readAll(); + QbsQmlJS::Lexer lexer(cacheValue.engine()); + lexer.setCode(code, 1); + QbsQmlJS::Parser parser(cacheValue.engine()); + + file.close(); + if (!parser.parse()) { + const QList &parserMessages = parser.diagnosticMessages(); + if (Q_UNLIKELY(!parserMessages.empty())) { + ErrorInfo err; + for (const QbsQmlJS::DiagnosticMessage &msg : parserMessages) + err.append(msg.message, toCodeLocation(filePath, msg.loc)); + throw err; + } + } + + cacheValue.setCode(code); + cacheValue.setAst(parser.ast()); + } + + const FileContextPtr file = FileContext::create(); + file->setFilePath(QFileInfo(filePath).absoluteFilePath()); + file->setContent(cacheValue.code()); + file->setSearchPaths(searchPaths); + + ItemReaderASTVisitor astVisitor(*this, file, itemPool, m_logger); + { + class ProcessingFlagManager { + public: + ProcessingFlagManager(ASTCacheValue &v) : m_cacheValue(v) { v.setProcessingFlag(true); } + ~ProcessingFlagManager() { m_cacheValue.setProcessingFlag(false); } + private: + ASTCacheValue &m_cacheValue; + } processingFlagManager(cacheValue); + cacheValue.ast()->accept(&astVisitor); + } + astVisitor.checkItemTypes(); + return astVisitor.rootItem(); +} + +void ItemReaderVisitorState::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) +{ + m_directoryEntries.insert(dirPath, entries); +} + +bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const +{ + const auto it = m_directoryEntries.constFind(dirPath); + if (it == m_directoryEntries.constEnd()) + return false; + *entries = it.value(); + return true; +} + +Item *ItemReaderVisitorState::mostDerivingItem() const +{ + return m_mostDerivingItem; +} + +void ItemReaderVisitorState::setMostDerivingItem(Item *item) +{ + m_mostDerivingItem = item; +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/itemreadervisitorstate.h b/src/lib/corelib/loader/itemreadervisitorstate.h new file mode 100644 index 000000000..dc22cfb42 --- /dev/null +++ b/src/lib/corelib/loader/itemreadervisitorstate.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_ITEMREADERVISITORSTATE_H +#define QBS_ITEMREADERVISITORSTATE_H + +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class Item; +class ItemPool; +class Logger; + +class ItemReaderVisitorState +{ +public: + ItemReaderVisitorState(Logger &logger); + ~ItemReaderVisitorState(); + + Logger &logger() { return m_logger; } + Set filesRead() const { return m_filesRead; } + + Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool); + + void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); + bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; + + Item *mostDerivingItem() const; + void setMostDerivingItem(Item *item); + + void setDeprecationWarningMode(DeprecationWarningMode mode) { m_deprecationWarningMode = mode; } + DeprecationWarningMode deprecationWarningMode() const { return m_deprecationWarningMode; } + +private: + DeprecationWarningMode m_deprecationWarningMode = defaultDeprecationWarningMode(); + Logger &m_logger; + Set m_filesRead; + QHash m_directoryEntries; + Item *m_mostDerivingItem = nullptr; + + class ASTCache; + const std::unique_ptr m_astCache; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/loader/loader.cpp b/src/lib/corelib/loader/loader.cpp new file mode 100644 index 000000000..82358c96d --- /dev/null +++ b/src/lib/corelib/loader/loader.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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 "loader.h" + +#include "projectresolver.h" +#include "projecttreebuilder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +Loader::Loader(ScriptEngine *engine, Logger logger) + : m_logger(std::move(logger)) + , m_progressObserver(nullptr) + , m_engine(engine) +{ + m_logger.storeWarnings(); +} + +void Loader::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +void Loader::setSearchPaths(const QStringList &_searchPaths) +{ + QStringList searchPaths; + for (const QString &searchPath : _searchPaths) { + if (!FileInfo::exists(searchPath)) { + m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.") + .arg(QDir::toNativeSeparators(searchPath)); + } else { + searchPaths += searchPath; + } + } + + m_searchPaths = searchPaths; +} + +void Loader::setOldProjectProbes(const std::vector &oldProbes) +{ + m_oldProjectProbes = oldProbes; +} + +void Loader::setOldProductProbes(const QHash> &oldProbes) +{ + m_oldProductProbes = oldProbes; +} + +void Loader::setStoredProfiles(const QVariantMap &profiles) +{ + m_storedProfiles = profiles; +} + +void Loader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo) +{ + m_storedModuleProviderInfo = providerInfo; +} + +TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters) +{ + SetupProjectParameters parameters = _parameters; + + if (parameters.topLevelProfile().isEmpty()) { + Settings settings(parameters.settingsDirectory()); + QString profileName = settings.defaultProfile(); + if (profileName.isEmpty()) { + m_logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. " + "Using default property values."); + profileName = Profile::fallbackName(); + } + parameters.setTopLevelProfile(profileName); + parameters.expandBuildConfiguration(); + } + + setupProjectFilePath(parameters); + QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); + m_logger.qbsDebug() << "Using project file '" + << QDir::toNativeSeparators(parameters.projectFilePath()) << "'."; + + m_engine->setEnvironment(parameters.adjustedEnvironment()); + m_engine->checkAndClearException({}); + m_engine->clearImportsCache(); + m_engine->clearRequestedProperties(); + m_engine->enableProfiling(parameters.logElapsedTime()); + m_logger.clearWarnings(); + EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation); + + // At this point, we cannot set a sensible total effort, because we know nothing about + // the project yet. That's why we use a placeholder here, so the user at least + // sees that an operation is starting. The real total effort will be set later when + // we have enough information. + if (m_progressObserver) { + m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1") + .arg(TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())), 1); + m_progressObserver->setScriptEngine(m_engine); + } + + const FileTime resolveTime = FileTime::currentTime(); + Evaluator evaluator(m_engine); + 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(); + project->lastStartResolveTime = resolveTime; + project->lastEndResolveTime = FileTime::currentTime(); + + // E.g. if the top-level project is disabled. + if (m_progressObserver) + m_progressObserver->setFinished(); + + return project; +} + +void Loader::setupProjectFilePath(SetupProjectParameters ¶meters) +{ + QString projectFilePath = parameters.projectFilePath(); + if (projectFilePath.isEmpty()) + projectFilePath = QDir::currentPath(); + const QFileInfo projectFileInfo(projectFilePath); + if (!projectFileInfo.exists()) + throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath)); + if (projectFileInfo.isRelative()) + projectFilePath = projectFileInfo.absoluteFilePath(); + if (projectFileInfo.isFile()) { + parameters.setProjectFilePath(projectFilePath); + return; + } + if (!projectFileInfo.isDir()) + throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath)); + + const QStringList &actualFileNames + = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); + if (actualFileNames.empty()) { + QString error; + if (parameters.projectFilePath().isEmpty()) + error = Tr::tr("No project file given and none found in current directory.\n"); + else + error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath); + throw ErrorInfo(error); + } + if (actualFileNames.size() > 1) { + throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") + .arg(projectFilePath)); + } + projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front()); + + projectFilePath = QDir::current().filePath(projectFilePath); + projectFilePath = QDir::cleanPath(projectFilePath); + parameters.setProjectFilePath(projectFilePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/loader.h b/src/lib/corelib/loader/loader.h new file mode 100644 index 000000000..377ab68d2 --- /dev/null +++ b/src/lib/corelib/loader/loader.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_LOADER_H +#define QBS_LOADER_H + +#include +#include +#include +#include + +#include + +namespace qbs { +class Settings; +class SetupProjectParameters; +namespace Internal { +class Logger; +class ProgressObserver; +class ScriptEngine; + +class QBS_AUTOTEST_EXPORT Loader +{ +public: + Loader(ScriptEngine *engine, Logger logger); + + void setProgressObserver(ProgressObserver *observer); + void setSearchPaths(const QStringList &searchPaths); + void setOldProjectProbes(const std::vector &oldProbes); + void setOldProductProbes(const QHash> &oldProbes); + void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } + void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo); + TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); + + static void setupProjectFilePath(SetupProjectParameters ¶meters); + +private: + Logger m_logger; + ProgressObserver *m_progressObserver; + ScriptEngine * const m_engine; + QStringList m_searchPaths; + std::vector m_oldProjectProbes; + QHash> m_oldProductProbes; + StoredModuleProviderInfo m_storedModuleProviderInfo; + QVariantMap m_storedProfiles; + FileTime m_lastResolveTime; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOADER_H diff --git a/src/lib/corelib/loader/localprofiles.cpp b/src/lib/corelib/loader/localprofiles.cpp new file mode 100644 index 000000000..0b73a6ab0 --- /dev/null +++ b/src/lib/corelib/loader/localprofiles.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qbs::Internal { +class LocalProfiles::Private +{ +public: + Private(const SetupProjectParameters ¶meters, 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 ¶meters; + Evaluator &evaluator; + Logger &logger; + QVariantMap profiles; +}; + +LocalProfiles::LocalProfiles(const SetupProjectParameters ¶meters, 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(it.value())->item(), + profileItem, values); + break; + case Value::VariantValueType: + values.insert(name.join(QLatin1Char('.')), + std::static_pointer_cast(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/loader/localprofiles.h b/src/lib/corelib/loader/localprofiles.h new file mode 100644 index 000000000..3e6b77f4d --- /dev/null +++ b/src/lib/corelib/loader/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 + +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 ¶meters, 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/loader/moduleinstantiator.cpp b/src/lib/corelib/loader/moduleinstantiator.cpp new file mode 100644 index 000000000..35f5332af --- /dev/null +++ b/src/lib/corelib/loader/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 "modulepropertymerger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs::Internal { +class ModuleInstantiator::Private +{ +public: + Private(const SetupProjectParameters ¶meters, 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 + getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName, + const QString &id, bool replace); + + const SetupProjectParameters ¶meters; + ItemPool &itemPool; + ModulePropertyMerger &propertyMerger; + Logger &logger; + qint64 elapsedTime = 0; +}; + +ModuleInstantiator::ModuleInstantiator( + const SetupProjectParameters ¶meters, 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 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(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..: syntax. + // For simplicity and backwards compatibility, qbs properties can also be given without + // the "modules." prefix, i.e. just qbs.:. + // In addition, users can override module properties just for certain products + // using the products...: 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/loader/moduleinstantiator.h b/src/lib/corelib/loader/moduleinstantiator.h new file mode 100644 index 000000000..f235b83fa --- /dev/null +++ b/src/lib/corelib/loader/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 + +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 ¶meters, 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/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp new file mode 100644 index 000000000..2ad3d8e71 --- /dev/null +++ b/src/lib/corelib/loader/moduleloader.cpp @@ -0,0 +1,529 @@ +/**************************************************************************** +** +** 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 "moduleloader.h" + +#include "itemreader.h" +#include "moduleproviderloader.h" +#include "productitemmultiplexer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace qbs::Internal { + +class ModuleLoader::Private +{ +public: + Private(const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader, + ItemReader &itemReader, Evaluator &evaluator, Logger &logger) + : setupParameters(setupParameters), providerLoader(providerLoader), + itemReader(itemReader), evaluator(evaluator), logger(logger) {} + + std::pair loadModuleFile(const ProductContext &product, + const QString &moduleName, const QString &filePath); + std::pair 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; + ModuleProviderLoader &providerLoader; + ItemReader &itemReader; + Evaluator &evaluator; + Logger &logger; + + // The keys are file paths, the values are module prototype items accompanied by a profile. + std::unordered_map>> modulePrototypes; + + // The keys are module prototypes and products, the values specify whether the module's + // condition is true for that product. + std::unordered_map, bool> modulePrototypeEnabledInfo; + + std::unordered_map> unknownProfilePropertyErrors; + std::unordered_map parameterDeclarations; + std::unordered_map> providerConfigsPerProduct; + QHash, std::optional> existingModulePathCache; + std::map moduleDirListCache; + + qint64 elapsedTimeModuleProviders = 0; +}; + +ModuleLoader::ModuleLoader( + const SetupProjectParameters &setupParameters, ModuleProviderLoader &providerLoader, + ItemReader &itemReader, Evaluator &evaluator, Logger &logger) + : d(new Private(setupParameters, providerLoader, itemReader, evaluator, logger)) { } + +ModuleLoader::~ModuleLoader() { delete d; } + +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 &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; +} + +ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile( + const 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 = d->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 = d->itemReader.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; + }; + + SearchPathsManager searchPathsManager(d->itemReader); + + Result loadResult; + auto existingPaths = findExistingModulePaths(); + if (existingPaths.isEmpty()) { // no suitable names found, try to use providers + AccumulatingTimer providersTimer( + d->setupParameters.logElapsedTime() ? &d->elapsedTimeModuleProviders : nullptr); + std::optional &providerConfig + = d->providerConfigsPerProduct[productContext.productItem]; + auto result = d->providerLoader.executeModuleProviders( + {productContext.productItem, productContext.projectItem, productContext.name, + productContext.uniqueName, productContext.moduleProperties, providerConfig}, + dependsItemLocation, + moduleName, + fallbackMode); + loadResult.providerProbes << result.probes; + if (!providerConfig) + providerConfig = result.providerConfig; + if (result.searchPaths) { + qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() + << "with newly added search paths from module provider"; + d->itemReader.pushExtraSearchPaths(*result.searchPaths); + existingPaths = findExistingModulePaths(); + } + } + + const auto getModuleFileNames = [&](const QString &dirPath) -> QStringList & { + QStringList &moduleFileNames = d->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 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] = d->loadModuleFile(productContext, 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) { + loadResult.moduleItem = createNonPresentModule( + *productContext.projectItem->pool(), fullName, QStringLiteral("not found"), + nullptr); + return loadResult; + } + if (Q_UNLIKELY(triedToLoadModule)) { + throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), + dependsItemLocation); + } + return loadResult; + } + + if (candidates.size() == 1) { + loadResult.moduleItem = candidates.at(0).item; + } else { + for (auto &candidate : candidates) { + candidate.priority = d->evaluator.intValue(candidate.item, + StringConstants::priorityProperty(), + candidate.priority); + } + loadResult.moduleItem = chooseModuleCandidate(candidates, fullName); + } + + const QString fullProductName = ProductItemMultiplexer::fullProductDisplayName( + productContext.name, productContext.multiplexId); + const auto it = d->unknownProfilePropertyErrors.find(loadResult.moduleItem); + 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.toString(), fullProductName, productContext.profile)); + for (const ErrorInfo &e : it->second) + error.append(e.toString()); + handlePropertyError(error, d->setupParameters, d->logger); + } + + return loadResult; +} + +std::pair ModuleLoader::Private::loadModuleFile( + const ProductContext &product, const QString &moduleName, const QString &filePath) +{ + qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath; + + const auto [module, triedToLoad] = getModulePrototype(product, moduleName, filePath); + if (!module) + return {nullptr, triedToLoad}; + + const auto key = std::make_pair(module, product.productItem); + const auto it = modulePrototypeEnabledInfo.find(key); + if (it != modulePrototypeEnabledInfo.end()) { + qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; + return {it->second ? module : nullptr, triedToLoad}; + } + + if (!evaluateModuleCondition(product, module, moduleName)) { + qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false"; + modulePrototypeEnabledInfo.insert({key, false}); + return {nullptr, triedToLoad}; + } + + 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(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 { + Item::PropertyDeclarationMap decls; + const auto &moduleChildren = module->children(); + for (Item *param : moduleChildren) { + if (param->type() != ItemType::Parameter) + continue; + const auto ¶mDecls = param->propertyDeclarations(); + for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) + decls.insert(it.key(), it.value()); + } + parameterDeclarations.insert({module, decls}); + } + + modulePrototypeEnabledInfo.insert({key, true}); + return {module, triedToLoad}; +} + +std::pair ModuleLoader::Private::getModulePrototype( + const ProductContext &product, const QString &moduleName, const QString &filePath) +{ + 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}; + } + } + + 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}; + } + prototypeList.emplace_back(module, product.profile); + + // 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 {module, true}; +} + +bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product, + Item *module, const QString &fullModuleName) +{ + // Evaluator reqires module name to be set. + module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName)); + + // 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.productItem->property(StringConstants::qbsModule())); + } + } + ~TempQbsModuleProvider() + { + if (!m_needsQbsItem) + return; + if (m_prevQbsItemValue) + m_module->setProperty(StringConstants::qbsModule(), m_prevQbsItemValue); + else + m_module->removeProperty(StringConstants::qbsModule()); + } + private: + Item * const m_module; + ValuePtr m_prevQbsItemValue; + const bool m_needsQbsItem; + }; + + const TempQbsModuleProvider tempQbs(product, module, fullModuleName); + return evaluator.boolValue(module, StringConstants::conditionProperty()); +} + +class DependencyParameterDeclarationCheck +{ +public: + DependencyParameterDeclarationCheck( + const QString &productName, const Item *productItem, + const std::unordered_map &decls) + : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls) + {} + + void operator()(const QVariantMap ¶meters) const { check(parameters, QualifiedId()); } + +private: + void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const + { + for (auto it = parameters.begin(); it != parameters.end(); ++it) { + if (it.value().userType() == QMetaType::QVariantMap) { + check(it.value().toMap(), QualifiedId(moduleName) << it.key()); + } else { + const auto &deps = m_productItem->modules(); + auto m = std::find_if(deps.begin(), deps.end(), + [&moduleName] (const Item::Module &module) { + 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()), + m_productItem->location()); + } + + 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()); + } + } + } + } + + bool moduleExists(const QualifiedId &name) const + { + const auto &deps = m_productItem->modules(); + return any_of(deps, [&name](const Item::Module &module) { + return module.name == name; + }); + } + + const QString &m_productName; + const Item * const m_productItem; + const std::unordered_map &m_parameterDeclarations; +}; + +void ModuleLoader::checkDependencyParameterDeclarations(const Item *productItem, + const QString &productName) const +{ + DependencyParameterDeclarationCheck dpdc(productName, productItem, d->parameterDeclarations); + for (const Item::Module &dep : productItem->modules()) { + if (!dep.parameters.empty()) + dpdc(dep.parameters); + } +} + +void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem, + const Item::Modules &modules) +{ + for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + d->forwardParameterDeclarations(it.key(), + std::static_pointer_cast(it.value())->item(), + 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(parameterDeclarations[it->item->rootPrototype()]); + } else { + for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + forwardParameterDeclarations(QualifiedId(moduleName) << it.key(), + std::static_pointer_cast(it.value())->item(), + modules); + } + } +} + +void ModuleLoader::printProfilingInfo(int indent) +{ + if (!d->setupParameters.logElapsedTime()) + return; + d->logger.qbsLog(LoggerInfo, true) + << QByteArray(indent, ' ') + << Tr::tr("Running module providers took %1.") + .arg(elapsedTimeString(d->elapsedTimeModuleProviders)); +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/moduleloader.h b/src/lib/corelib/loader/moduleloader.h new file mode 100644 index 000000000..6f55ee052 --- /dev/null +++ b/src/lib/corelib/loader/moduleloader.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 +#include + +#include +#include + +#include + +namespace qbs { +class CodeLocation; +class SetupProjectParameters; +namespace Internal { +class Evaluator; +enum class FallbackMode; +class ItemReader; +class Logger; +class ModuleProviderLoader; + +class ModuleLoader +{ +public: + ModuleLoader(const SetupProjectParameters &setupParameters, + ModuleProviderLoader &providerLoader, ItemReader &itemReader, + Evaluator &evaluator, Logger &logger); + ~ModuleLoader(); + + struct ProductContext { + Item * const productItem; + const Item * const projectItem; + const QString &name; + const QString &uniqueName; + const QString &profile; + const QString &multiplexId; + const QVariantMap &moduleProperties; + const QVariantMap &profileModuleProperties; + }; + struct Result { + Item *moduleItem = nullptr; + std::vector providerProbes; + }; + Result searchAndLoadModuleFile(const ProductContext &productContext, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + FallbackMode fallbackMode, bool isRequired); + + void checkDependencyParameterDeclarations(const Item *productItem, + const QString &productName) const; + void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules); + void printProfilingInfo(int indent); + +private: + class Private; + Private * const d; +}; + +} // namespace Internal +} // namespace qbs + diff --git a/src/lib/corelib/loader/modulepropertymerger.cpp b/src/lib/corelib/loader/modulepropertymerger.cpp new file mode 100644 index 000000000..e3cf1a633 --- /dev/null +++ b/src/lib/corelib/loader/modulepropertymerger.cpp @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include + +namespace qbs::Internal { +class ModulePropertyMerger::Private +{ +public: + Private(const SetupProjectParameters ¶meters, 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 ¤tHead, 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 ¶meters; + 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 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 ¶meters, 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 ¤tHead, 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> 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( + 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(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 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 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/loader/modulepropertymerger.h b/src/lib/corelib/loader/modulepropertymerger.h new file mode 100644 index 000000000..fc388cfbf --- /dev/null +++ b/src/lib/corelib/loader/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 + +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 ¶meters, 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/loader/moduleproviderloader.cpp b/src/lib/corelib/loader/moduleproviderloader.cpp new file mode 100644 index 000000000..584f43166 --- /dev/null +++ b/src/lib/corelib/loader/moduleproviderloader.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** 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 "moduleproviderloader.h" + +#include "itemreader.h" +#include "probesresolver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +ModuleProviderLoader::ModuleProviderLoader( + const SetupProjectParameters ¶meters, ItemReader &reader, Evaluator &evaluator, + ProbesResolver &probesResolver, Logger &logger) + : m_parameters(parameters) + , m_reader(reader) + , m_evaluator(evaluator) + , m_probesResolver(probesResolver) + , m_logger(logger) +{ +} + +ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders( + const ProductContext &productContext, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + FallbackMode fallbackMode) +{ + ModuleProviderLoader::ModuleProviderResult result; + std::vector providersToRun; + qCDebug(lcModuleLoader) << "Module" << moduleName.toString() + << "not found, checking for module providers"; + const auto providerNames = getModuleProviders(productContext.productItem); + if (providerNames) { + providersToRun = transformed>(*providerNames, [](const auto &name) { + return Provider{name, ModuleProviderLookup::Named}; }); + } else { + for (QualifiedId providerName = moduleName; !providerName.empty(); + providerName.pop_back()) { + providersToRun.push_back({providerName, ModuleProviderLookup::Scoped}); + } + } + result = executeModuleProvidersHelper(productContext, dependsItemLocation, providersToRun); + + if (fallbackMode == FallbackMode::Enabled + && !result.providerFound + && !providerNames) { + qCDebug(lcModuleLoader) << "Specific module provider not found for" + << moduleName.toString() << ", setting up fallback."; + result = executeModuleProvidersHelper( + productContext, + dependsItemLocation, + {{moduleName, ModuleProviderLookup::Fallback}}); + } + + return result; +} + +ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper( + const ProductContext &product, + const CodeLocation &dependsItemLocation, + const std::vector &providers) +{ + if (providers.empty()) + return {}; + QStringList allSearchPaths; + ModuleProviderResult result; + result.providerConfig = product.providerConfig ? *product.providerConfig + : getModuleProviderConfig(product); + const auto qbsModule = evaluateQbsModule(product); + for (const auto &[name, lookupType] : providers) { + const QVariantMap config = result.providerConfig.value(name.toString()).toMap(); + ModuleProviderInfo &info = m_storedModuleProviderInfo.providers[ + {name.toString(), config, qbsModule, int(lookupType)}]; + const bool fromCache = !info.name.isEmpty(); + if (!fromCache) { + info.name = name; + info.config = config; + info.providerFile = findModuleProviderFile(name, lookupType); + if (!info.providerFile.isEmpty()) { + qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile; + const auto evalResult = evaluateModuleProvider( + product, dependsItemLocation, name, info.providerFile, config, qbsModule); + info.searchPaths = evalResult.first; + result.probes << evalResult.second; + info.transientOutput = m_parameters.dryRun(); + } + } + if (info.providerFile.isEmpty()) { + if (lookupType == ModuleProviderLookup::Named) + throw ErrorInfo(Tr::tr("Unknown provider '%1'").arg(name.toString())); + continue; + } + if (fromCache) + qCDebug(lcModuleLoader) << "Re-using provider" << name.toString() << "from cache"; + + result.providerFound = true; + if (info.searchPaths.empty()) { + qCDebug(lcModuleLoader) + << "Module provider did run, but did not set up any modules."; + continue; + } + qCDebug(lcModuleLoader) << "Module provider added" << info.searchPaths.size() + << "new search path(s)"; + + allSearchPaths << info.searchPaths; + } + if (allSearchPaths.isEmpty()) + return result; + + result.searchPaths = std::move(allSearchPaths); + + return result; +} + +QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product) +{ + QVariantMap providerConfig; + const ItemValueConstPtr configItemValue = + product.productItem->itemProperty(StringConstants::moduleProviders()); + if (configItemValue) { + const std::function collectMap + = [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) { + const Item::PropertyMap &props = item->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QVariant value; + switch (it.value()->type()) { + case Value::ItemValueType: { + const auto childItem = static_cast(it.value().get())->item(); + childItem->setScope(item->scope()); + collectMap(childItem, QualifiedId(name) << it.key()); + continue; + } + case Value::JSSourceValueType: { + const ScopedJsValue sv(m_evaluator.engine()->context(), + m_evaluator.value(item, it.key())); + value = getJsVariant(m_evaluator.engine()->context(), sv); + break; + } + case Value::VariantValueType: + value = static_cast(it.value().get())->value(); + break; + } + QVariantMap m = providerConfig.value(name.toString()).toMap(); + m.insert(it.key(), value); + providerConfig.insert(name.toString(), m); + } + }; + configItemValue->item()->setScope(product.productItem); + collectMap(configItemValue->item(), QualifiedId()); + } + for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) { + if (!it.key().startsWith(QStringLiteral("moduleProviders."))) + continue; + const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size()); + const QVariantMap providerConfigFromBuildConfig = it.value().toMap(); + if (providerConfigFromBuildConfig.empty()) + continue; + QVariantMap currentMapForProvider = providerConfig.value(provider).toMap(); + for (auto propIt = providerConfigFromBuildConfig.begin(); + propIt != providerConfigFromBuildConfig.end(); ++propIt) { + currentMapForProvider.insert(propIt.key(), propIt.value()); + } + providerConfig.insert(provider, currentMapForProvider); + } + return providerConfig; +} + +std::optional> ModuleProviderLoader::getModuleProviders(Item *item) +{ + while (item) { + const auto providers = + m_evaluator.optionalStringListValue(item, StringConstants::qbsModuleProviders()); + if (providers) { + return transformed>(*providers, [](const auto &provider) { + return QualifiedId::fromString(provider); }); + } + item = item->parent(); + } + return std::nullopt; +} + +QString ModuleProviderLoader::findModuleProviderFile( + const QualifiedId &name, ModuleProviderLookup lookupType) +{ + for (const QString &path : m_reader.allSearchPaths()) { + QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers")); + switch (lookupType) { + case ModuleProviderLookup::Named: { + const auto result = + FileInfo::resolvePath(fullPath, name.toString() + QStringLiteral(".qbs")); + if (FileInfo::exists(result)) { + fullPath = result; + break; + } + [[fallthrough]]; + } + case ModuleProviderLookup::Scoped: + for (const QString &component : name) + fullPath = FileInfo::resolvePath(fullPath, component); + fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs")); + break; + case ModuleProviderLookup::Fallback: + fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback/provider.qbs")); + break; + } + if (!FileInfo::exists(fullPath)) { + qCDebug(lcModuleLoader) << "No module provider found at" << fullPath; + continue; + } + return fullPath; + } + return {}; +} + +QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const +{ + const QString properties[] = { + QStringLiteral("sysroot"), + QStringLiteral("toolchain"), + }; + const auto qbsItemValue = std::static_pointer_cast( + product.productItem->property(StringConstants::qbsModule())); + QVariantMap result; + for (const auto &property : properties) { + const ScopedJsValue val(m_evaluator.engine()->context(), + m_evaluator.value(qbsItemValue->item(), property)); + auto value = getJsVariant(m_evaluator.engine()->context(), val); + 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; +} + +Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule) +{ + const auto qbsItemValue = std::static_pointer_cast( + product.productItem->property(StringConstants::qbsModule())); + + Item *fakeQbsModule = Item::create(product.productItem->pool(), ItemType::Scope); + + for (auto it = qbsModule.begin(), end = qbsModule.end(); it != end; ++it) { + fakeQbsModule->setProperty(it.key(), VariantValue::create(it.value())); + } + + Item *scope = Item::create(product.productItem->pool(), ItemType::Scope); + scope->setFile(qbsItemValue->item()->file()); + scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule)); + return scope; +} + +std::pair > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &name, + const QString &providerFile, + const QVariantMap &moduleConfig, + const QVariantMap &qbsModule) +{ + QTemporaryFile dummyItemFile; + if (!dummyItemFile.open()) { + throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider " + "for dependency '%1': %2").arg(name.toString(), + dummyItemFile.errorString())); + } + m_tempQbsFiles << dummyItemFile.fileName(); + qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile; + const QString projectBuildDir = product.projectItem->variantProperty( + StringConstants::buildDirectoryProperty())->value().toString(); + const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); + + // include qbs module into hash + auto jsConfig = moduleConfig; + jsConfig[StringConstants::qbsModule()] = qbsModule; + + QTextStream stream(&dummyItemFile); + using Qt::endl; + setupDefaultCodec(stream); + stream << "import qbs.FileInfo" << endl; + stream << "import qbs.Utilities" << endl; + stream << "import '" << providerFile << "' as Provider" << endl; + stream << "Provider {" << endl; + stream << " name: " << toJSLiteral(name.toString()) << endl; + stream << " property var config: (" << toJSLiteral(jsConfig) << ')' << endl; + stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, " + " Utilities.getHash(JSON.stringify(config)))" << endl; + stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl; + stream << " property stringList searchPaths: (relativeSearchPaths || [])" + " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })" + << endl; + stream << "}" << endl; + stream.flush(); + 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.") + .arg(providerFile, providerItem->typeName(), + BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider))); + } + + providerItem->setScope(createProviderScope(product, qbsModule)); + providerItem->overrideProperties(moduleConfig, name, m_parameters, m_logger); + std::vector probes = m_probesResolver.resolveProbes( + {product.name, product.uniqueName}, providerItem); + + EvalContextSwitcher contextSwitcher(m_evaluator.engine(), EvalContext::ModuleProvider); + return std::make_pair(m_evaluator.stringListValue(providerItem, QStringLiteral("searchPaths")), + std::move(probes)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h new file mode 100644 index 000000000..eabb5ead5 --- /dev/null +++ b/src/lib/corelib/loader/moduleproviderloader.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** 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$ +** +****************************************************************************/ + +#ifndef MODULEPROVIDERLOADER_H +#define MODULEPROVIDERLOADER_H + +#include +#include + +#include +#include + +#include +#include + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class Evaluator; +class Item; +class ItemReader; +class Logger; +class ProbesResolver; + +enum class FallbackMode { Enabled, Disabled }; + +class ModuleProviderLoader +{ +public: + explicit ModuleProviderLoader(const SetupProjectParameters ¶meters, ItemReader &itemReader, + Evaluator &evaluator, ProbesResolver &probesResolver, + Logger &logger); + + enum class ModuleProviderLookup { Scoped, Named, Fallback }; + + struct Provider + { + QualifiedId name; + ModuleProviderLookup lookup; + }; + + struct ModuleProviderResult + { + std::vector probes; + QVariantMap providerConfig; + bool providerFound = false; + std::optional searchPaths; + }; + + const StoredModuleProviderInfo &storedModuleProviderInfo() const + { + return m_storedModuleProviderInfo; + } + + void setStoredModuleProviderInfo(StoredModuleProviderInfo moduleProviderInfo) + { + m_storedModuleProviderInfo = std::move(moduleProviderInfo); + } + + const Set &tempQbsFiles() const { return m_tempQbsFiles; } + + struct ProductContext { + Item * const productItem; + const Item * const projectItem; + const QString &name; + const QString &uniqueName; + const QVariantMap &moduleProperties; + const std::optional providerConfig; + }; + ModuleProviderResult executeModuleProviders( + const ProductContext &productContext, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + FallbackMode fallbackMode); + +private: + ModuleProviderResult executeModuleProvidersHelper( + const ProductContext &product, + const CodeLocation &dependsItemLocation, + const std::vector &providers); + QVariantMap getModuleProviderConfig(const ProductContext &product); + + std::optional> getModuleProviders(Item *item); + + QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType); + QVariantMap evaluateQbsModule(const ProductContext &product) const; + Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule); + std::pair> evaluateModuleProvider( + const ProductContext &product, + const CodeLocation &location, + const QualifiedId &name, + const QString &providerFile, + const QVariantMap &moduleConfig, + const QVariantMap &qbsModule); + +private: + const SetupProjectParameters &m_parameters; + ItemReader &m_reader; + Evaluator &m_evaluator; + ProbesResolver &m_probesResolver; + Logger &m_logger; + StoredModuleProviderInfo m_storedModuleProviderInfo; + Set m_tempQbsFiles; +}; + +} // namespace Internal +} // namespace qbs + +#endif // MODULEPROVIDERLOADER_H diff --git a/src/lib/corelib/loader/probesresolver.cpp b/src/lib/corelib/loader/probesresolver.cpp new file mode 100644 index 000000000..a1f2e1baa --- /dev/null +++ b/src/lib/corelib/loader/probesresolver.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2022 Raphaël Cotty +** 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 "probesresolver.h" + +#include "itemreader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +static QString probeGlobalId(Item *probe) +{ + QString id; + + for (Item *obj = probe; obj; obj = obj->prototype()) { + if (!obj->id().isEmpty()) { + id = obj->id(); + break; + } + } + + if (id.isEmpty()) + return {}; + + QBS_CHECK(probe->file()); + return id + QLatin1Char('_') + probe->file()->filePath(); +} + +ProbesResolver::ProbesResolver(const SetupProjectParameters ¶meters, Evaluator &evaluator, + Logger &logger) + : m_parameters(parameters), m_evaluator(evaluator), m_logger(logger) +{ +} + +void ProbesResolver::setOldProjectProbes(const std::vector &oldProbes) +{ + m_oldProjectProbes.clear(); + for (const ProbeConstPtr& probe : oldProbes) + m_oldProjectProbes[probe->globalId()] << probe; +} + +void ProbesResolver::setOldProductProbes( + const QHash> &oldProbes) +{ + m_oldProductProbes = oldProbes; +} + +std::vector ProbesResolver::resolveProbes(const ProductContext &productContext, Item *item) +{ + AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr); + EvalContextSwitcher evalContextSwitcher(m_evaluator.engine(), EvalContext::ProbeExecution); + std::vector probes; + for (Item * const child : item->children()) + if (child->type() == ItemType::Probe) + probes.push_back(resolveProbe(productContext, item, child)); + return probes; +} + +ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext, Item *parent, + Item *probe) +{ + qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString(); + ++m_probesEncountered; + const QString &probeId = probeGlobalId(probe); + if (Q_UNLIKELY(probeId.isEmpty())) + throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location()); + const JSSourceValueConstPtr configureScript + = probe->sourceProperty(StringConstants::configureProperty()); + QBS_CHECK(configureScript); + if (Q_UNLIKELY(configureScript->sourceCode() == StringConstants::undefinedValue())) + throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); + using ProbeProperty = std::pair; + std::vector probeBindings; + ScriptEngine * const engine = m_evaluator.engine(); + JSContext * const ctx = engine->context(); + QVariantMap initialProperties; + for (Item *obj = probe; obj; obj = obj->prototype()) { + const Item::PropertyMap &props = obj->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString &name = it.key(); + if (name == StringConstants::configureProperty()) + continue; + const JSValue value = m_evaluator.value(probe, name); + probeBindings.emplace_back(name, ScopedJsValue(ctx, value)); + if (name != StringConstants::conditionProperty()) + initialProperties.insert(name, getJsVariant(ctx, value)); + } + } + const bool condition = m_evaluator.boolValue(probe, StringConstants::conditionProperty()); + const QString &sourceCode = configureScript->sourceCode().toString(); + ProbeConstPtr resolvedProbe; + if (parent->type() == ItemType::Project + || productContext.name.startsWith(StringConstants::shadowProductPrefix())) { + resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); + } else { + resolvedProbe = findOldProductProbe(productContext.uniqueName, condition, + initialProperties, sourceCode); + } + if (!resolvedProbe) { + resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties); + if (resolvedProbe) { + qCDebug(lcModuleLoader) << "probe results cached from current run"; + ++m_probesCachedCurrent; + } + } else { + qCDebug(lcModuleLoader) << "probe results cached from earlier run"; + ++m_probesCachedOld; + } + ScopedJsValue configureScope(ctx, JS_UNDEFINED); + std::vector importedFilesUsedInConfigure; + if (!condition) { + qCDebug(lcModuleLoader) << "Probe disabled; skipping"; + } else if (!resolvedProbe) { + ++m_probesRun; + qCDebug(lcModuleLoader) << "configure script needs to run"; + const Evaluator::FileContextScopes fileCtxScopes + = m_evaluator.fileContextScopes(configureScript->file()); + configureScope.setValue(engine->newObject()); + for (const ProbeProperty &b : probeBindings) + setJsProperty(ctx, configureScope, b.first, JS_DupValue(ctx, b.second)); + engine->clearRequestedProperties(); + ScopedJsValue sv(ctx, engine->evaluate(JsValueOwner::Caller, + configureScript->sourceCodeForEvaluation(), {}, 1, + {fileCtxScopes.fileScope, fileCtxScopes.importScope, configureScope})); + if (JsException ex = engine->checkAndClearException(configureScript->location())) + throw ex.toErrorInfo(); + importedFilesUsedInConfigure = engine->importedFilesUsedInScript(); + } else { + importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed(); + } + QVariantMap properties; + for (const ProbeProperty &b : probeBindings) { + QVariant newValue; + if (resolvedProbe) { + newValue = resolvedProbe->properties().value(b.first); + } else { + if (condition) { + JSValue v = getJsProperty(ctx, configureScope, b.first); + const JSValue saved = v; + ScopedJsValue valueMgr(ctx, saved); + const PropertyDeclaration decl = probe->propertyDeclaration(b.first); + m_evaluator.convertToPropertyType(decl, probe->location(), v); + + // If the value was converted from scalar to array as per our convenience + // functionality, then the original value is now the only element of a + // newly allocated array and thus gets deleted via that array. + // The array itself is owned by the script engine, so we must stay out of + // memory management here. + if (v != saved) + valueMgr.setValue(JS_UNDEFINED); + + if (JsException ex = engine->checkAndClearException({})) + throw ex.toErrorInfo(); + newValue = getJsVariant(ctx, v); + } else { + newValue = initialProperties.value(b.first); + } + } + if (newValue != getJsVariant(ctx, b.second)) + probe->setProperty(b.first, VariantValue::create(newValue)); + if (!resolvedProbe) + properties.insert(b.first, newValue); + } + if (!resolvedProbe) { + resolvedProbe = Probe::create(probeId, probe->location(), condition, + sourceCode, properties, initialProperties, + importedFilesUsedInConfigure); + m_currentProbes[probe->location()] << resolvedProbe; + } + return resolvedProbe; +} + +ProbeConstPtr ProbesResolver::findOldProjectProbe( + const QString &globalId, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_parameters.forceProbeExecution()) + return {}; + + for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) { + if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) + return oldProbe; + } + + return {}; +} + +ProbeConstPtr ProbesResolver::findOldProductProbe( + const QString &productName, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_parameters.forceProbeExecution()) + return {}; + + for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) { + if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) + return oldProbe; + } + + return {}; +} + +ProbeConstPtr ProbesResolver::findCurrentProbe( + const CodeLocation &location, + bool condition, + const QVariantMap &initialProperties) const +{ + const std::vector &cachedProbes = m_currentProbes.value(location); + for (const ProbeConstPtr &probe : cachedProbes) { + if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No)) + return probe; + } + return {}; +} + +bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const +{ + return probe->condition() == condition + && probe->initialProperties() == initialProperties + && (compareScript == CompareScript::No + || (probe->configureScript() == configureScript + && !probe->needsReconfigure(m_lastResolveTime))); +} + +void ProbesResolver::printProfilingInfo(int indent) +{ + if (!m_parameters.logElapsedTime()) + return; + 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); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/probesresolver.h b/src/lib/corelib/loader/probesresolver.h new file mode 100644 index 000000000..4c7cd93ac --- /dev/null +++ b/src/lib/corelib/loader/probesresolver.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2022 Raphaël Cotty +** 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$ +** +****************************************************************************/ + +#ifndef PROBESRESOLVER_H +#define PROBESRESOLVER_H + +#include + +#include + +#include + +#include + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class Item; +class Evaluator; +class Logger; + +class ProbesResolver +{ +public: + explicit ProbesResolver(const SetupProjectParameters ¶meters, Evaluator &evaluator, + Logger &logger); + void setOldProjectProbes(const std::vector &oldProbes); + void setOldProductProbes(const QHash> &oldProbes); + void printProfilingInfo(int indent); + + struct ProductContext { + const QString &name; + const QString &uniqueName; + }; + std::vector resolveProbes(const ProductContext &productContext, Item *item); + +private: + ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findOldProductProbe(const QString &productName, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findCurrentProbe(const CodeLocation &location, bool condition, + const QVariantMap &initialProperties) const; + enum class CompareScript { No, Yes }; + bool probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const; + ProbeConstPtr resolveProbe(const ProductContext &productContext, Item *parent, Item *probe); + + qint64 m_elapsedTimeProbes = 0; + quint64 m_probesEncountered = 0; + quint64 m_probesRun = 0; + quint64 m_probesCachedCurrent = 0; + quint64 m_probesCachedOld = 0; + + const SetupProjectParameters &m_parameters; + Evaluator &m_evaluator; + Logger &m_logger; + QHash> m_oldProjectProbes; + QHash> m_oldProductProbes; + FileTime m_lastResolveTime; + QHash> m_currentProbes; +}; + +} // namespace Internal +} // namespace qbs + +#endif // PROBESRESOLVER_H diff --git a/src/lib/corelib/loader/productitemmultiplexer.cpp b/src/lib/corelib/loader/productitemmultiplexer.cpp new file mode 100644 index 000000000..5f8e9f472 --- /dev/null +++ b/src/lib/corelib/loader/productitemmultiplexer.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +namespace qbs::Internal { +namespace { +using MultiplexConfigurationByIdTable = QThreadStorage>; +using MultiplexRow = std::vector; +using MultiplexTable = std::vector; +class MultiplexInfo +{ +public: + std::vector 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 ¶meters, 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 ¶meters; + Evaluator &evaluator; + Logger &logger; + const QbsItemRetriever qbsItemRetriever; +}; + +ProductItemMultiplexer::ProductItemMultiplexer( + const SetupProjectParameters ¶meters, Evaluator &evaluator, Logger &logger, + const QbsItemRetriever &qbsItemRetriever) + : d(new Private(parameters, evaluator, logger, qbsItemRetriever)) {} + +ProductItemMultiplexer::~ProductItemMultiplexer() { delete d; } + +QList ProductItemMultiplexer::multiplex( + const QString &productName, + Item *productItem, + Item *tempQbsModuleItem, + const std::function &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 additionalProductItems; + std::vector 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 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/loader/productitemmultiplexer.h b/src/lib/corelib/loader/productitemmultiplexer.h new file mode 100644 index 000000000..d99267336 --- /dev/null +++ b/src/lib/corelib/loader/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 +#include + +#include + +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; + ProductItemMultiplexer(const SetupProjectParameters ¶meters, 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 multiplex( + const QString &productName, + Item *productItem, + Item *tempQbsModuleItem, + const std::function &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/loader/projectresolver.cpp b/src/lib/corelib/loader/projectresolver.cpp new file mode 100644 index 000000000..564c2f3ea --- /dev/null +++ b/src/lib/corelib/loader/projectresolver.cpp @@ -0,0 +1,1800 @@ +/**************************************************************************** +** +** 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 "projectresolver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +extern bool debugProperties; + +static const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +struct ProjectResolver::ProjectContext +{ + ProjectContext *parentContext = nullptr; + ResolvedProjectPtr project; + std::vector fileTaggers; + std::vector rules; + JobLimits jobLimits; + ResolvedModulePtr dummyModule; +}; + +struct ProjectResolver::ProductContext +{ + ResolvedProductPtr product; + QString buildDirectory; + Item *item = nullptr; + using ArtifactPropertiesInfo = std::pair>; + QHash artifactPropertiesPerFilter; + ProjectResolver::FileLocations sourceArtifactLocations; + GroupConstPtr currentGroup; +}; + +struct ProjectResolver::ModuleContext +{ + ResolvedModulePtr module; + JobLimits jobLimits; +}; + +class CancelException { }; + + +ProjectResolver::ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult, + SetupProjectParameters setupParameters, Logger &logger) + : m_evaluator(evaluator) + , m_logger(logger) + , m_engine(m_evaluator->engine()) + , m_progressObserver(nullptr) + , m_setupParams(std::move(setupParameters)) + , m_loadResult(std::move(loadResult)) +{ + QBS_CHECK(FileInfo::isAbsolute(m_setupParams.buildRoot())); +} + +ProjectResolver::~ProjectResolver() = default; + +void ProjectResolver::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) +{ + const std::vector allProducts = project->allProducts(); + for (size_t i = 0; i < allProducts.size(); ++i) { + const ResolvedProductConstPtr product1 = allProducts.at(i); + const QString productName = product1->uniqueName(); + for (size_t j = i + 1; j < allProducts.size(); ++j) { + const ResolvedProductConstPtr product2 = allProducts.at(j); + if (product2->uniqueName() == productName) { + ErrorInfo error; + error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); + error.append(Tr::tr("First product defined here."), product1->location); + error.append(Tr::tr("Second product defined here."), product2->location); + throw error; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolve() +{ + TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"), + m_setupParams.logElapsedTime()); + qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath(); + + m_productContext = nullptr; + m_moduleContext = nullptr; + m_elapsedTimeModPropEval = m_elapsedTimeAllPropEval = m_elapsedTimeGroups = 0; + TopLevelProjectPtr tlp; + try { + tlp = resolveTopLevelProject(); + printProfilingInfo(); + } catch (const CancelException &) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.") + .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); + } + return tlp; +} + +void ProjectResolver::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) + throw CancelException(); +} + +QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propertyWasSet) const +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = std::static_pointer_cast( + value); + result = sourceCodeForEvaluation(sourceValue); + if (propertyWasSet) + *propertyWasSet = !sourceValue->isBuiltinDefaultValue(); + } else { + if (propertyWasSet) + *propertyWasSet = false; + } + return result; +} + +QString ProjectResolver::verbatimValue(Item *item, const QString &name, bool *propertyWasSet) const +{ + return verbatimValue(item->property(name), propertyWasSet); +} + +void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(item); + Q_UNUSED(projectContext); +} + +static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) +{ + Set subProjectNames; + Set projectsInNeedOfNameChange; + for (const ResolvedProjectPtr &p : qAsConst(parentProject->subProjects)) { + if (!subProjectNames.insert(p->name).second) + projectsInNeedOfNameChange << p; + makeSubProjectNamesUniqe(p); + } + while (!projectsInNeedOfNameChange.empty()) { + auto it = projectsInNeedOfNameChange.begin(); + while (it != projectsInNeedOfNameChange.end()) { + const ResolvedProjectPtr p = *it; + p->name += QLatin1Char('_'); + if (subProjectNames.insert(p->name).second) { + it = projectsInNeedOfNameChange.erase(it); + } else { + ++it; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolveTopLevelProject() +{ + if (m_progressObserver) + m_progressObserver->setMaximum(int(m_loadResult.productInfos.size())); + TopLevelProjectPtr project = TopLevelProject::create(); + project->buildDirectory = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), + TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree())); + project->buildSystemFiles = m_loadResult.qbsFiles; + project->profileConfigs = m_loadResult.profileConfigs; + project->probes = m_loadResult.projectProbes; + project->moduleProviderInfo = m_loadResult.storedModuleProviderInfo; + ProjectContext projectContext; + projectContext.project = project; + + resolveProject(m_loadResult.root, &projectContext); + ErrorInfo accumulatedErrors; + for (const ErrorInfo &e : m_queuedErrors) + appendError(accumulatedErrors, e); + if (accumulatedErrors.hasError()) + throw accumulatedErrors; + + project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); + project->overriddenValues = m_setupParams.overriddenValues(); + project->canonicalFilePathResults = m_engine->canonicalFilePathResults(); + project->fileExistsResults = m_engine->fileExistsResults(); + project->directoryEntriesResults = m_engine->directoryEntriesResults(); + project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); + project->environment = m_engine->environment(); + project->buildSystemFiles.unite(m_engine->imports()); + makeSubProjectNamesUniqe(project); + resolveProductDependencies(); + collectExportedProductDependencies(); + checkForDuplicateProductNames(project); + + for (const ResolvedProductPtr &product : project->allProducts()) { + if (!product->enabled) + continue; + + applyFileTaggers(product); + matchArtifactProperties(product, product->allEnabledFiles()); + + // Let a positive value of qbs.install imply the file tag "installable". + for (const SourceArtifactPtr &artifact : product->allFiles()) { + if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + artifact->fileTags += "installable"; + } + } + project->warningsEncountered = m_logger.warnings(); + return project; +} + +void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (projectContext->parentContext) + projectContext->project->enabled = projectContext->parentContext->project->enabled; + projectContext->project->location = item->location(); + try { + resolveProjectFully(item, projectContext); + } catch (const ErrorInfo &error) { + if (!projectContext->project->enabled) { + qCDebug(lcProjectResolver) << "error resolving project" + << projectContext->project->location << error.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + } +} + +void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + projectContext->project->enabled = projectContext->project->enabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + projectContext->project->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + if (projectContext->project->name.isEmpty()) + projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item? + QVariantMap projectProperties; + if (!projectContext->project->enabled) { + projectProperties.insert(StringConstants::profileProperty(), + m_evaluator->stringValue(item, + StringConstants::profileProperty())); + projectContext->project->setProjectProperties(projectProperties); + return; + } + + projectContext->dummyModule = ResolvedModule::create(); + + for (Item::PropertyDeclarationMap::const_iterator it + = item->propertyDeclarations().constBegin(); + it != item->propertyDeclarations().constEnd(); ++it) { + if (it.value().flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + continue; + const ValueConstPtr v = item->property(it.key()); + QBS_ASSERT(v && v->type() != Value::ItemValueType, continue); + const ScopedJsValue sv(m_engine->context(), m_evaluator->value(item, it.key())); + projectProperties.insert(it.key(), getJsVariant(m_engine->context(), sv)); + } + projectContext->project->setProjectProperties(projectProperties); + + static const ItemFuncMap mapping = { + { ItemType::Project, &ProjectResolver::resolveProject }, + { ItemType::SubProject, &ProjectResolver::resolveSubProject }, + { ItemType::Product, &ProjectResolver::resolveProduct }, + { ItemType::Probe, &ProjectResolver::ignoreItem }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } + }; + + for (Item * const child : item->children()) { + try { + callItemFunction(mapping, child, projectContext); + } catch (const ErrorInfo &e) { + m_queuedErrors.push_back(e); + } + } + + for (const ResolvedProductPtr &product : projectContext->project->products) + postProcess(product, projectContext); +} + +void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + ProjectContext subProjectContext = createProjectContext(projectContext); + + Item * const projectItem = item->child(ItemType::Project); + if (projectItem) { + resolveProject(projectItem, &subProjectContext); + return; + } + + // No project item was found, which means the project was disabled. + subProjectContext.project->enabled = false; + Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject); + if (propertiesItem) { + subProjectContext.project->name + = m_evaluator->stringValue(propertiesItem, StringConstants::nameProperty()); + } +} + +class ProjectResolver::ProductContextSwitcher +{ +public: + ProductContextSwitcher(ProjectResolver *resolver, ProductContext *newContext, + ProgressObserver *progressObserver) + : m_resolver(resolver), m_progressObserver(progressObserver) + { + QBS_CHECK(!m_resolver->m_productContext); + m_resolver->m_productContext = newContext; + } + + ~ProductContextSwitcher() + { + if (m_progressObserver) + m_progressObserver->incrementProgressValue(); + m_resolver->m_productContext = nullptr; + } + +private: + ProjectResolver * const m_resolver; + ProgressObserver * const m_progressObserver; +}; + +void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + m_evaluator->clearPropertyDependencies(); + ProductContext productContext; + productContext.item = item; + ResolvedProductPtr product = ResolvedProduct::create(); + product->enabled = projectContext->project->enabled; + product->moduleProperties = PropertyMapInternal::create(); + product->project = projectContext->project; + 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 &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); + 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:"); + ErrorInfo fullError(mainErrorString, item->location()); + appendError(fullError, e); + if (!product->enabled) { + qCDebug(lcProjectResolver) << fullError.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw fullError; + m_logger.printWarning(fullError); + m_logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") + .arg(product->name), item->location())); + product->enabled = false; + } +} + +void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectContext) +{ + const ResolvedProductPtr product = m_productContext->product; + m_productItemMap.insert(product, item); + projectContext->project->products.push_back(product); + product->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + + // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. + m_productContext->buildDirectory + = m_evaluator->stringValue(item, StringConstants::buildDirectoryProperty()); + product->multiplexConfigurationId + = m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty()); + qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName(); + m_productsByItem.insert(item, product); + product->enabled = product->enabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + 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( + item, StringConstants::sourceDirectoryProperty()); + product->destinationDirectory = m_evaluator->stringValue( + item, StringConstants::destinationDirProperty()); + + if (product->destinationDirectory.isEmpty()) { + product->destinationDirectory = m_productContext->buildDirectory; + } else { + product->destinationDirectory = FileInfo::resolvePath( + product->topLevelProject()->buildDirectory, + product->destinationDirectory); + } + product->probes = pi.probes; + createProductConfig(product.get()); + product->productProperties.insert(StringConstants::destinationDirProperty(), + product->destinationDirectory); + ModuleProperties::init(m_evaluator->engine(), m_evaluator->scriptValue(item), product.get()); + + QList subItems = item->children(); + const ValuePtr filesProperty = item->property(StringConstants::filesProperty()); + if (filesProperty) { + Item *fakeGroup = Item::create(item->pool(), ItemType::Group); + fakeGroup->setFile(item->file()); + fakeGroup->setLocation(item->location()); + fakeGroup->setScope(item); + fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name)); + fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty); + fakeGroup->setProperty(StringConstants::excludeFilesProperty(), + item->property(StringConstants::excludeFilesProperty())); + fakeGroup->setProperty(StringConstants::overrideTagsProperty(), + VariantValue::falseValue()); + fakeGroup->setupForBuiltinType(m_setupParams.deprecationWarningMode(), m_logger); + subItems.prepend(fakeGroup); + } + + static const ItemFuncMap mapping = { + { ItemType::Depends, &ProjectResolver::ignoreItem }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Group, &ProjectResolver::resolveGroup }, + { ItemType::Product, &ProjectResolver::resolveShadowProduct }, + { ItemType::Export, &ProjectResolver::resolveExport }, + { ItemType::Probe, &ProjectResolver::ignoreItem }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } + }; + + for (Item * const child : qAsConst(subItems)) + callItemFunction(mapping, child, projectContext); + + for (const ProjectContext *p = projectContext; p; p = p->parentContext) { + JobLimits tempLimits = p->jobLimits; + product->jobLimits = tempLimits.update(product->jobLimits); + } + + resolveModules(item, projectContext); + + for (const FileTag &t : qAsConst(product->fileTags)) + m_productsByType[t].push_back(product); +} + +void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext) +{ + JobLimits jobLimits; + 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) + m_productContext->product->jobLimits.setJobLimit(moduleJobLimit); + } +} + +void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits, + ProjectContext *projectContext) +{ + checkCancelation(); + if (!item->isPresentModule()) + return; + + ModuleContext * const oldModuleContext = m_moduleContext; + ModuleContext moduleContext; + moduleContext.module = ResolvedModule::create(); + m_moduleContext = &moduleContext; + + const ResolvedModulePtr &module = moduleContext.module; + module->name = moduleName.toString(); + module->isProduct = isProduct; + module->product = m_productContext->product.get(); + module->setupBuildEnvironmentScript.initialize( + scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty())); + module->setupRunEnvironmentScript.initialize( + scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty())); + + for (const Item::Module &m : item->modules()) { + if (m.item->isPresentModule()) + module->moduleDependencies += m.name.toString(); + } + + m_productContext->product->modules.push_back(module); + if (!parameters.empty()) + m_productContext->product->moduleParameters[module] = parameters; + + static const ItemFuncMap mapping { + { ItemType::Group, &ProjectResolver::ignoreItem }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Scanner, &ProjectResolver::resolveScanner }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }, + { ItemType::Depends, &ProjectResolver::ignoreItem }, + { ItemType::Parameter, &ProjectResolver::ignoreItem }, + { ItemType::Properties, &ProjectResolver::ignoreItem }, + { ItemType::Probe, &ProjectResolver::ignoreItem } + }; + for (Item *child : item->children()) + callItemFunction(mapping, child, projectContext); + for (int i = 0; i < moduleContext.jobLimits.count(); ++i) { + const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i); + const int oldLimit = jobLimits.getLimit(newJobLimit.pool()); + if (oldLimit == -1 || oldLimit > newJobLimit.limit()) + jobLimits.setJobLimit(newJobLimit); + } + + m_moduleContext = oldModuleContext; +} + +void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item) +{ + const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty()); + if (type) + product->fileTags = FileTags::fromStringList(type->value().toStringList()); +} + +SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct, + const QString &fileName, const GroupPtr &group, bool wildcard, + const CodeLocation &filesLocation, FileLocations *fileLocations, + ErrorInfo *errorInfo) +{ + const QString &baseDir = FileInfo::path(group->location.filePath()); + const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName)); + if (!wildcard && !FileInfo(absFilePath).exists()) { + if (errorInfo) + errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation); + rproduct->missingSourceFiles << absFilePath; + return {}; + } + if (group->enabled && fileLocations) { + CodeLocation &loc = (*fileLocations)[std::make_pair(group->targetOfModule, absFilePath)]; + if (loc.isValid()) { + if (errorInfo) { + errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath)); + errorInfo->append(Tr::tr("First occurrence is here."), loc); + errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation); + } + return {}; + } + loc = filesLocation; + } + SourceArtifactPtr artifact = SourceArtifactInternal::create(); + artifact->absoluteFilePath = absFilePath; + artifact->fileTags = group->fileTags; + artifact->overrideFileTags = group->overrideTags; + artifact->properties = group->properties; + artifact->targetOfModule = group->targetOfModule; + (wildcard ? group->wildcards->files : group->files).push_back(artifact); + return artifact; +} + +static QualifiedIdSet propertiesToEvaluate(std::deque initialProps, + const PropertyDependencies &deps) +{ + std::deque remainingProps = std::move(initialProps); + QualifiedIdSet allProperties; + while (!remainingProps.empty()) { + const QualifiedId prop = remainingProps.front(); + remainingProps.pop_front(); + const auto insertResult = allProperties.insert(prop); + if (!insertResult.second) + continue; + transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; }); + } + return allProperties; +} + +QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues) +{ + // Step 1: Retrieve the properties directly set in the group + const ModulePropertiesPerGroup &mp = mapValue( + m_loadResult.productInfos, m_productContext->item).modulePropertiesSetInGroups; + const auto it = mp.find(group); + if (it == mp.end()) + return {}; + const QualifiedIdSet &propsSetInGroup = it->second; + + // Step 2: Gather all properties that depend on these properties. + const QualifiedIdSet &propsToEval = propertiesToEvaluate( + rangeTo>(propsSetInGroup), + m_evaluator->propertyDependencies()); + + // Step 3: Evaluate all these properties and replace their values in the map + QVariantMap modulesMap = currentValues; + QHash propsPerModule; + for (auto fullPropName : propsToEval) { + const QString moduleName + = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString(); + propsPerModule[moduleName] << fullPropName.last(); + } + 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); + if (propsForModule.empty()) + continue; + QVariantMap reusableValues = modulesMap.value(fullModName).toMap(); + for (const QString &prop : qAsConst(propsForModule)) + reusableValues.remove(prop); + modulesMap.insert(fullModName, + evaluateProperties(module.item, module.item, reusableValues, true, true)); + } + return modulesMap; +} + +void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + const bool parentEnabled = m_productContext->currentGroup + ? m_productContext->currentGroup->enabled + : m_productContext->product->enabled; + const bool isEnabled = parentEnabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + try { + resolveGroupFully(item, projectContext, isEnabled); + } catch (const ErrorInfo &error) { + if (!isEnabled) { + qCDebug(lcProjectResolver) << "error resolving group at" << item->location() + << error.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + } +} + +void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectContext *projectContext, + bool isEnabled) +{ + AccumulatingTimer groupTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeGroups : nullptr); + + const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) { + PropertyMapPtr moduleProperties; + bool newPropertyMapRequired = false; + if (existingProps) + moduleProperties = existingProps->propertyMap(); + if (!moduleProperties) { + newPropertyMapRequired = true; + moduleProperties = m_productContext->currentGroup + ? m_productContext->currentGroup->properties + : m_productContext->product->moduleProperties; + } + const QVariantMap newModuleProperties + = resolveAdditionalModuleProperties(item, moduleProperties->value()); + if (!newModuleProperties.empty()) { + if (newPropertyMapRequired) + moduleProperties = PropertyMapInternal::create(); + moduleProperties->setValue(newModuleProperties); + } + return moduleProperties; + }; + + QStringList files = m_evaluator->stringListValue(item, StringConstants::filesProperty()); + bool fileTagsSet; + const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty(), + &fileTagsSet); + const QStringList fileTagsFilter + = m_evaluator->stringListValue(item, StringConstants::fileTagsFilterProperty()); + if (!fileTagsFilter.empty()) { + if (Q_UNLIKELY(!files.empty())) + throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), + item->location()); + + if (!isEnabled) + return; + + ProductContext::ArtifactPropertiesInfo &apinfo + = m_productContext->artifactPropertiesPerFilter[fileTagsFilter]; + if (apinfo.first) { + const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(), + [item](const CodeLocation &loc) { + return item->location().filePath() == loc.filePath(); + }); + if (it != apinfo.second.cend()) { + ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); + error.append(Tr::tr("First item"), *it); + error.append(Tr::tr("Second item"), item->location()); + throw error; + } + } else { + apinfo.first = ArtifactProperties::create(); + apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); + m_productContext->product->artifactProperties.push_back(apinfo.first); + } + apinfo.second.push_back(item->location()); + apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get())); + apinfo.first->addExtraFileTags(fileTags); + return; + } + QStringList patterns; + for (int i = files.size(); --i >= 0;) { + if (FileInfo::isPattern(files[i])) + patterns.push_back(files.takeAt(i)); + } + GroupPtr group = ResolvedGroup::create(); + bool prefixWasSet = false; + group->prefix = m_evaluator->stringValue(item, StringConstants::prefixProperty(), QString(), + &prefixWasSet); + if (!prefixWasSet && m_productContext->currentGroup) + group->prefix = m_productContext->currentGroup->prefix; + if (!group->prefix.isEmpty()) { + for (auto it = files.rbegin(), end = files.rend(); it != end; ++it) + it->prepend(group->prefix); + } + group->location = item->location(); + group->enabled = isEnabled; + group->properties = getGroupPropertyMap(nullptr); + group->fileTags = fileTags; + group->overrideTags = m_evaluator->boolValue(item, StringConstants::overrideTagsProperty()); + if (group->overrideTags && fileTagsSet) { + if (group->fileTags.empty() ) + group->fileTags.insert(unknownFileTag()); + } else if (m_productContext->currentGroup) { + group->fileTags.unite(m_productContext->currentGroup->fileTags); + } + + const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location(); + const VariantValueConstPtr moduleProp = item->variantProperty( + StringConstants::modulePropertyInternal()); + if (moduleProp) + group->targetOfModule = moduleProp->value().toString(); + ErrorInfo fileError; + if (!patterns.empty()) { + group->wildcards = std::make_unique(); + SourceWildCards *wildcards = group->wildcards.get(); + wildcards->group = group.get(); + wildcards->excludePatterns = m_evaluator->stringListValue( + item, StringConstants::excludeFilesProperty()); + wildcards->patterns = patterns; + const Set files = wildcards->expandPatterns(group, + FileInfo::path(item->file()->filePath()), + projectContext->project->topLevelProject()->buildDirectory); + for (const QString &fileName : files) + createSourceArtifact(m_productContext->product, fileName, group, true, filesLocation, + &m_productContext->sourceArtifactLocations, &fileError); + } + + for (const QString &fileName : qAsConst(files)) { + createSourceArtifact(m_productContext->product, fileName, group, false, filesLocation, + &m_productContext->sourceArtifactLocations, &fileError); + } + if (fileError.hasError()) { + if (group->enabled) { + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw ErrorInfo(fileError); + m_logger.printWarning(fileError); + } else { + qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString(); + } + } + group->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + if (group->name.isEmpty()) + group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.size()); + m_productContext->product->groups.push_back(group); + + class GroupContextSwitcher { + public: + GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup) + : m_context(context), m_oldGroup(context.currentGroup) { + m_context.currentGroup = newGroup; + } + ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; } + private: + ProductContext &m_context; + const GroupConstPtr m_oldGroup; + }; + GroupContextSwitcher groupSwitcher(*m_productContext, group); + for (Item * const childItem : item->children()) + resolveGroup(childItem, projectContext); +} + +void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem) +{ + ExportedModule &m = m_productContext->product->exportedModule; + const QVariantList prefixList = m.propertyValues.take( + StringConstants::prefixMappingProperty()).toList(); + const QString shadowProductName = m_evaluator->stringValue( + shadowProductItem, StringConstants::nameProperty()); + const QString shadowProductBuildDir = m_evaluator->stringValue( + shadowProductItem, StringConstants::buildDirectoryProperty()); + QVariantMap prefixMap; + for (const QVariant &v : prefixList) { + const QVariantMap o = v.toMap(); + prefixMap.insert(o.value(QStringLiteral("prefix")).toString(), + o.value(QStringLiteral("replacement")).toString()); + } + const auto valueRefersToImportingProduct + = [shadowProductName, shadowProductBuildDir](const QString &value) { + return value.toLower().contains(shadowProductName.toLower()) + || value.contains(shadowProductBuildDir); + }; + static const auto stringMapper = [](const QVariantMap &mappings, const QString &value) + -> QString { + for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) { + if (value.startsWith(it.key())) + return it.value().toString() + value.mid(it.key().size()); + } + return value; + }; + const auto stringListMapper = [&valueRefersToImportingProduct]( + const QVariantMap &mappings, const QStringList &value) -> QStringList { + QStringList result; + result.reserve(value.size()); + for (const QString &s : value) { + if (!valueRefersToImportingProduct(s)) + result.push_back(stringMapper(mappings, s)); + } + return result; + }; + const std::function mapper + = [&stringListMapper, &mapper]( + const QVariantMap &mappings, const QVariant &value) -> QVariant { + switch (static_cast(value.userType())) { + case QMetaType::QString: + return stringMapper(mappings, value.toString()); + case QMetaType::QStringList: + return stringListMapper(mappings, value.toStringList()); + case QMetaType::QVariantMap: { + QVariantMap m = value.toMap(); + for (auto it = m.begin(); it != m.end(); ++it) + it.value() = mapper(mappings, it.value()); + return m; + } + default: + return value; + } + }; + for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (ExportedModuleDependency &dep : m.moduleDependencies) { + for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + } +} + +void ProjectResolver::collectExportedProductDependencies() +{ + ResolvedProductPtr dummyProduct = ResolvedProduct::create(); + dummyProduct->enabled = false; + for (const auto &exportingProductInfo : qAsConst(m_productExportInfo)) { + const ResolvedProductPtr exportingProduct = exportingProductInfo.first; + if (!exportingProduct->enabled) + continue; + Item * const importingProductItem = exportingProductInfo.second; + + std::vector> directDeps; + for (const Item::Module &m : importingProductItem->modules()) { + 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); + } + } + } + for (const auto &dep : directDeps) { + if (!contains(exportingProduct->exportedModule.productDependencies, + dep.first->uniqueName())) { + exportingProduct->exportedModule.productDependencies.push_back( + dep.first->uniqueName()); + } + if (!dep.second.isEmpty()) { + exportingProduct->exportedModule.dependencyParameters.insert(dep.first, + dep.second); + } + } + auto &productDeps = exportingProduct->exportedModule.productDependencies; + std::sort(productDeps.begin(), productDeps.end()); + } +} + +void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectContext *) +{ + if (!m_productContext->product->enabled) + return; + for (const auto &m : item->modules()) { + if (m.name.toString() != m_productContext->product->name) + continue; + collectPropertiesForExportItem(m.item); + for (const auto &dep : m.item->modules()) + collectPropertiesForModuleInExportItem(dep); + break; + } + try { + adaptExportedPropertyValues(item); + } catch (const ErrorInfo &) {} + m_productExportInfo.emplace_back(m_productContext->product, item); +} + +void ProjectResolver::setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector &properties) +{ + const auto &props = item->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString qualifiedName = namePrefix.isEmpty() + ? it.key() : namePrefix + QLatin1Char('.') + it.key(); + if ((item->type() == ItemType::Export || item->type() == ItemType::Properties) + && qualifiedName == StringConstants::prefixMappingProperty()) { + continue; + } + const ValuePtr &v = it.value(); + if (v->type() == Value::ItemValueType) { + setupExportedProperties(std::static_pointer_cast(v)->item(), + qualifiedName, properties); + continue; + } + ExportedProperty exportedProperty; + exportedProperty.fullName = qualifiedName; + exportedProperty.type = item->propertyDeclaration(it.key()).type(); + if (v->type() == Value::VariantValueType) { + exportedProperty.sourceCode = toJSLiteral( + std::static_pointer_cast(v)->value()); + } else { + QBS_CHECK(v->type() == Value::JSSourceValueType); + const JSSourceValue * const sv = static_cast(v.get()); + exportedProperty.sourceCode = sv->sourceCode().toString(); + } + const ItemDeclaration itemDecl + = BuiltinDeclarations::instance().declarationsForType(item->type()); + PropertyDeclaration propertyDecl; + const auto itemProperties = itemDecl.properties(); + for (const PropertyDeclaration &decl : itemProperties) { + if (decl.name() == it.key()) { + propertyDecl = decl; + exportedProperty.isBuiltin = true; + break; + } + } + + // Do not add built-in properties that were left at their default value. + if (!exportedProperty.isBuiltin || m_evaluator->isNonDefaultValue(item, it.key())) + properties.push_back(exportedProperty); + } + + // Order the list of properties, so the output won't look so random. + static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool { + const int p1ComponentCount = p1.fullName.count(QLatin1Char('.')); + const int p2ComponentCount = p2.fullName.count(QLatin1Char('.')); + if (p1.isBuiltin && !p2.isBuiltin) + return true; + if (!p1.isBuiltin && p2.isBuiltin) + return false; + if (p1ComponentCount < p2ComponentCount) + return true; + if (p1ComponentCount > p2ComponentCount) + return false; + return p1.fullName < p2.fullName; + }; + std::sort(properties.begin(), properties.end(), less); +} + +static bool usesImport(const ExportedProperty &prop, const QRegularExpression ®ex) +{ + return prop.sourceCode.indexOf(regex) != -1; +} + +static bool usesImport(const ExportedItem &item, const QRegularExpression ®ex) +{ + return any_of(item.properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(item.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +static bool usesImport(const ExportedModule &module, const QString &name) +{ + // Imports are used in three ways: + // (1) var f = new TextFile(...); + // (2) var path = FileInfo.joinPaths(...) + // (3) var obj = DataCollection; + const QString pattern = QStringLiteral("\\b%1\\b"); + + const QRegularExpression regex(pattern.arg(name)); // std::regex is much slower + return any_of(module.m_properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(module.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +static QString getLineAtLocation(const CodeLocation &loc, const QString &content) +{ + int pos = 0; + int currentLine = 1; + while (currentLine < loc.line()) { + while (content.at(pos++) != QLatin1Char('\n')) + ; + ++currentLine; + } + const int eolPos = content.indexOf(QLatin1Char('\n'), pos); + return content.mid(pos, eolPos - pos); +} + +void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *) +{ + ExportedModule &exportedModule = m_productContext->product->exportedModule; + setupExportedProperties(exportItem, QString(), exportedModule.m_properties); + static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) { + return p1.fullName < p2.fullName; + }; + std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc); + + transform(exportItem->children(), exportedModule.children, + [&exportedModule, this](const auto &child) { + return resolveExportChild(child, exportedModule); }); + + for (const JsImport &jsImport : exportItem->file()->jsImports()) { + if (usesImport(exportedModule, jsImport.scopeName)) { + exportedModule.importStatements << getLineAtLocation(jsImport.location, + exportItem->file()->content()); + } + } + const auto builtInImports = JsExtensions::extensionNames(); + for (const QString &builtinImport: builtInImports) { + if (usesImport(exportedModule, builtinImport)) + exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport; + } + exportedModule.importStatements.sort(); +} + +// TODO: This probably wouldn't be necessary if we had item serialization. +std::unique_ptr ProjectResolver::resolveExportChild(const Item *item, + const ExportedModule &module) +{ + std::unique_ptr exportedItem(new ExportedItem); + + // This is the type of the built-in base item. It may turn out that we need to support + // derived items under Export. In that case, we probably need a new Item member holding + // the original type name. + exportedItem->name = item->typeName(); + + transform(item->children(), exportedItem->children, [&module, this](const auto &child) { + return resolveExportChild(child, module); }); + + setupExportedProperties(item, QString(), exportedItem->properties); + return exportedItem; +} + +QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) const +{ + QString &scriptFunction = m_scriptFunctions[std::make_pair(value->sourceCode(), + decl.functionArgumentNames())]; + if (!scriptFunction.isNull()) + return scriptFunction; + const QString args = decl.functionArgumentNames().join(QLatin1Char(',')); + if (value->hasFunctionForm()) { + // Insert the argument list. + scriptFunction = value->sourceCodeForEvaluation(); + scriptFunction.insert(10, args); + // Remove the function application "()" that has been + // added in ItemReaderASTVisitor::visitStatement. + scriptFunction.chop(2); + } else { + scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ") + + value->sourceCode().toString() + QLatin1String(";})"); + } + return scriptFunction; +} + +QString ProjectResolver::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const +{ + QString &code = m_sourceCode[value->sourceCode()]; + if (!code.isNull()) + return code; + code = value->sourceCodeForEvaluation(); + return code; +} + +ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const +{ + JSSourceValuePtr value = item->sourceProperty(name); + ScriptFunctionPtr &script = m_scriptFunctionMap[value ? value->location() : CodeLocation()]; + if (!script.get()) { + script = ScriptFunction::create(); + const PropertyDeclaration decl = item->propertyDeclaration(name); + script->sourceCode = sourceCodeAsFunction(value, decl); + script->location = value->location(); + script->fileContext = resolvedFileContext(value->file()); + } + return script; +} + +ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) + result = ResolvedFileContext::create(*ctx); + return result; +} + +void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + + RulePtr rule = Rule::create(); + + // read artifacts + bool hasArtifactChildren = false; + for (Item * const child : item->children()) { + if (Q_UNLIKELY(child->type() != ItemType::Artifact)) { + throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), + child->location()); + } + hasArtifactChildren = true; + resolveRuleArtifact(rule, child); + } + + rule->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + rule->prepareScript.initialize( + scriptFunctionValue(item, StringConstants::prepareProperty())); + rule->outputArtifactsScript.initialize( + scriptFunctionValue(item, StringConstants::outputArtifactsProperty())); + rule->outputFileTags = m_evaluator->fileTagsValue( + item, StringConstants::outputFileTagsProperty()); + if (rule->outputArtifactsScript.isValid()) { + if (hasArtifactChildren) + throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " + "that contain Artifact items."), + item->location()); + } + if (!hasArtifactChildren && rule->outputFileTags.empty()) { + throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty " + "outputFileTags property."), item->location()); + } + rule->multiplex = m_evaluator->boolValue(item, StringConstants::multiplexProperty()); + rule->alwaysRun = m_evaluator->boolValue(item, StringConstants::alwaysRunProperty()); + rule->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); + rule->inputsFromDependencies + = m_evaluator->fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); + bool requiresInputsSet = false; + rule->requiresInputs = m_evaluator->boolValue(item, StringConstants::requiresInputsProperty(), + &requiresInputsSet); + if (!requiresInputsSet) + rule->requiresInputs = rule->declaresInputs(); + rule->auxiliaryInputs + = m_evaluator->fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); + rule->excludedInputs + = m_evaluator->fileTagsValue(item, StringConstants::excludedInputsProperty()); + if (rule->excludedInputs.empty()) { + rule->excludedInputs = m_evaluator->fileTagsValue( + item, StringConstants::excludedAuxiliaryInputsProperty()); + } + rule->explicitlyDependsOn + = m_evaluator->fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); + rule->explicitlyDependsOnFromDependencies = m_evaluator->fileTagsValue( + item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); + rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + if (!rule->multiplex && !rule->declaresInputs()) { + throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."), + item->location()); + } + if (!rule->multiplex && !rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."), + item->location()); + } + if (!rule->declaresInputs() && rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule " + "does not declare any input tags."), item->location()); + } + if (m_productContext) { + rule->product = m_productContext->product.get(); + m_productContext->product->rules.push_back(rule); + } else { + projectContext->rules.push_back(rule); + } +} + +void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item) +{ + RuleArtifactPtr artifact = RuleArtifact::create(); + rule->artifacts.push_back(artifact); + artifact->location = item->location(); + + if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty())) + artifact->filePathLocation = sourceProperty->location(); + + artifact->filePath = verbatimValue(item, StringConstants::filePathProperty()); + artifact->fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); + artifact->alwaysUpdated = m_evaluator->boolValue(item, + StringConstants::alwaysUpdatedProperty()); + + QualifiedIdSet seenBindings; + for (Item *obj = item; obj; obj = obj->prototype()) { + for (QMap::const_iterator it = obj->properties().constBegin(); + it != obj->properties().constEnd(); ++it) + { + if (it.value()->type() != Value::ItemValueType) + continue; + resolveRuleArtifactBinding(artifact, + std::static_pointer_cast(it.value())->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, + Item *item, + const QStringList &namePrefix, + QualifiedIdSet *seenBindings) +{ + for (QMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) + { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(ruleArtifact, + std::static_pointer_cast(it.value())->item(), name, + seenBindings); + } else if (it.value()->type() == Value::JSSourceValueType) { + const auto insertResult = seenBindings->insert(name); + if (!insertResult.second) + continue; + JSSourceValuePtr sourceValue = std::static_pointer_cast(it.value()); + RuleArtifact::Binding rab; + rab.name = name; + rab.code = sourceCodeForEvaluation(sourceValue); + rab.location = sourceValue->location(); + ruleArtifact->bindings.push_back(rab); + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + std::vector &fileTaggers = m_productContext + ? m_productContext->product->fileTaggers + : projectContext->fileTaggers; + const QStringList patterns = m_evaluator->stringListValue(item, + StringConstants::patternsProperty()); + if (patterns.empty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + + const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); + if (fileTags.empty()) + throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); + + for (const QString &pattern : patterns) { + if (pattern.isEmpty()) + throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); + } + + const int priority = m_evaluator->intValue(item, StringConstants::priorityProperty()); + fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); +} + +void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + const QString jobPool = m_evaluator->stringValue(item, StringConstants::jobPoolProperty()); + if (jobPool.isEmpty()) + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.") + .arg(StringConstants::jobPoolProperty()), item->location()); + bool jobCountWasSet; + const int jobCount = m_evaluator->intValue(item, StringConstants::jobCountProperty(), -1, + &jobCountWasSet); + if (!jobCountWasSet) { + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + if (jobCount < 0) { + throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + JobLimits &jobLimits = m_moduleContext + ? m_moduleContext->jobLimits + : m_productContext ? m_productContext->product->jobLimits + : projectContext->jobLimits; + JobLimit jobLimit(jobPool, jobCount); + const int oldLimit = jobLimits.getLimit(jobPool); + if (oldLimit == -1 || oldLimit > jobCount) + jobLimits.setJobLimit(jobLimit); +} + +void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) { + qCDebug(lcProjectResolver) << "scanner condition is false"; + return; + } + + ResolvedScannerPtr scanner = ResolvedScanner::create(); + scanner->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + scanner->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); + scanner->recursive = m_evaluator->boolValue(item, StringConstants::recursiveProperty()); + scanner->searchPathsScript.initialize( + scriptFunctionValue(item, StringConstants::searchPathsProperty())); + scanner->scanScript.initialize( + scriptFunctionValue(item, StringConstants::scanProperty())); + m_productContext->product->scanners.push_back(scanner); +} + +void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product, + const std::vector &artifacts) +{ + for (const SourceArtifactPtr &artifact : artifacts) { + for (const auto &artifactProperties : product->artifactProperties) { + if (!artifact->isTargetOfModule() + && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) { + artifact->properties = artifactProperties->propertyMap(); + } + } + } +} + +void ProjectResolver::printProfilingInfo() +{ + if (!m_setupParams.logElapsedTime()) + return; + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("All property evaluation took %1.") + .arg(elapsedTimeString(m_elapsedTimeAllPropEval)); + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Module property evaluation took %1.") + .arg(elapsedTimeString(m_elapsedTimeModPropEval)); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Resolving groups (without module property " + "evaluation) took %1.") + .arg(elapsedTimeString(m_elapsedTimeGroups)); +} + +class TempScopeSetter +{ +public: + TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope()) + { + value->setScope(newScope, {}); + } + ~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: + ValuePtr m_value; + Item *m_oldScope; +}; + +void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance) +{ + if (!productModuleInstance->isPresentModule()) + return; + Item * const exportItem = productModuleInstance->prototype(); + 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; + const auto &props = exportItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + const auto match + = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); }; + if (it.key() != StringConstants::prefixMappingProperty() && + std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) { + continue; + } + if (it.value()->type() == Value::ItemValueType) { + collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance, + exportedModule.modulePropertyValues); + } else { + TempScopeSetter tss(it.value(), productModuleInstance); + evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues, + false); + } + } +} + +// Collects module properties assigned to in other (higher-level) modules. +void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + ExportedModule &exportedModule = m_productContext->product->exportedModule; + if (module.productInfo || module.name.first() == StringConstants::qbsModule()) + return; + const auto checkName = [module](const ExportedModuleDependency &d) { + return module.name.toString() == d.name; + }; + if (any_of(exportedModule.moduleDependencies, checkName)) + return; + + Item *modulePrototype = module.item->prototype(); + while (modulePrototype && modulePrototype->type() != ItemType::Module) + modulePrototype = modulePrototype->prototype(); + if (!modulePrototype) // Can happen for broken products in relaxed mode. + return; + const Item::PropertyMap &props = modulePrototype->properties(); + ExportedModuleDependency dep; + dep.name = module.name.toString(); + for (auto it = props.begin(); it != props.end(); ++it) { + if (it.value()->type() == Value::ItemValueType) + collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties); + } + exportedModule.moduleDependencies.push_back(dep); + + for (const auto &dep : module.item->modules()) + collectPropertiesForModuleInExportItem(dep); +} + +void ProjectResolver::resolveProductDependencies() +{ + 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; + 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(); + }); + } +} + +void ProjectResolver::postProcess(const ResolvedProductPtr &product, + ProjectContext *projectContext) const +{ + product->fileTaggers << projectContext->fileTaggers; + std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers), + [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) { + return a->priority() > b->priority(); + }); + for (const RulePtr &rule : projectContext->rules) { + RulePtr clonedRule = rule->clone(); + clonedRule->product = product.get(); + product->rules.push_back(clonedRule); + } +} + +void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const +{ + for (const SourceArtifactPtr &artifact : product->allEnabledFiles()) + applyFileTaggers(artifact, product); +} + +void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product) +{ + if (!artifact->overrideFileTags || artifact->fileTags.empty()) { + const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); + const FileTags fileTags = product->fileTagsForFileName(fileName); + artifact->fileTags.unite(fileTags); + if (artifact->fileTags.empty()) + artifact->fileTags.insert(unknownFileTag()); + qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags + << "to" << fileName; + } +} + +QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototype) +{ + AccumulatingTimer modPropEvalTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeModPropEval : nullptr); + QVariantMap moduleValues; + for (const Item::Module &module : item->modules()) { + if (!module.item->isPresentModule()) + continue; + const QString fullName = module.name.toString(); + moduleValues[fullName] = evaluateProperties(module.item, lookupPrototype, true); + } + + return moduleValues; +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors) +{ + const QVariantMap tmplt; + return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors); +} + +QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *propertiesContainer, + const QVariantMap &tmplt, bool lookupPrototype, bool checkErrors) +{ + AccumulatingTimer propEvalTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeAllPropEval : nullptr); + QVariantMap result = tmplt; + for (QMap::const_iterator it = propertiesContainer->properties().begin(); + it != propertiesContainer->properties().end(); ++it) { + checkCancelation(); + evaluateProperty(item, it.key(), it.value(), result, checkErrors); + } + return lookupPrototype && propertiesContainer->prototype() + ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors) + : result; +} + +void ProjectResolver::evaluateProperty(const Item *item, const QString &propName, + const ValuePtr &propValue, QVariantMap &result, bool checkErrors) +{ + JSContext * const ctx = m_engine->context(); + switch (propValue->type()) { + case Value::ItemValueType: + { + // Ignore items. Those point to module instances + // and are handled in evaluateModuleValues(). + break; + } + case Value::JSSourceValueType: + { + if (result.contains(propName)) + break; + const PropertyDeclaration pd = item->propertyDeclaration(propName); + if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) { + break; + } + const ScopedJsValue scriptValue(ctx, m_evaluator->property(item, propName)); + if (JsException ex = m_evaluator->engine()->checkAndClearException(propValue->location())) { + if (checkErrors) + throw ex.toErrorInfo(); + } + + // NOTE: Loses type information if scriptValue.isUndefined == true, + // as such QScriptValues become invalid QVariants. + QVariant v; + if (JS_IsFunction(ctx, scriptValue)) { + v = getJsString(ctx, scriptValue); + } else { + v = getJsVariant(ctx, scriptValue); + QVariantMap m = v.toMap(); + if (m.contains(StringConstants::importScopeNamePropertyInternal())) { + QVariantMap tmp = m; + const ScopedJsValue proto(ctx, JS_GetPrototype(ctx, scriptValue)); + m = getJsVariant(ctx, proto).toMap(); + for (auto it = tmp.begin(); it != tmp.end(); ++it) + m.insert(it.key(), it.value()); + v = m; + } + } + + if (pd.type() == PropertyDeclaration::Path && v.isValid()) { + v = v.toString(); + } else if (pd.type() == PropertyDeclaration::PathList + || pd.type() == PropertyDeclaration::StringList) { + v = v.toStringList(); + } else if (pd.type() == PropertyDeclaration::VariantList) { + v = v.toList(); + } + checkAllowedValues(v, propValue->location(), pd, propName); + result[propName] = v; + break; + } + case Value::VariantValueType: + { + if (result.contains(propName)) + break; + VariantValuePtr vvp = std::static_pointer_cast(propValue); + QVariant v = vvp->value(); + + const PropertyDeclaration pd = item->propertyDeclaration(propName); + if (v.isNull() && !pd.isScalar()) // QTBUG-51237 + v = QStringList(); + + checkAllowedValues(v, propValue->location(), pd, propName); + result[propName] = v; + break; + } + } +} + +void ProjectResolver::checkAllowedValues( + const QVariant &value, const CodeLocation &loc, const PropertyDeclaration &decl, + const QString &key) const +{ + const auto type = decl.type(); + if (type != PropertyDeclaration::String && type != PropertyDeclaration::StringList) + return; + + if (value.isNull()) + return; + + const auto &allowedValues = decl.allowedValues(); + if (allowedValues.isEmpty()) + return; + + const auto checkValue = [this, &loc, &allowedValues, &key](const QString &value) + { + if (!allowedValues.contains(value)) { + const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.") + .arg(value, key); + ErrorInfo error(message, loc); + handlePropertyError(error, m_setupParams, m_logger); + } + }; + + if (type == PropertyDeclaration::StringList) { + const auto strings = value.toStringList(); + for (const auto &string: strings) { + checkValue(string); + } + } else if (type == PropertyDeclaration::String) { + checkValue(value.toString()); + } +} + +void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleName, + const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps) +{ + QBS_CHECK(value->type() == Value::ItemValueType); + Item * const itemValueItem = std::static_pointer_cast(value)->item(); + 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, const QualifiedId &moduleName) + : valueItem(valueItem), + hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) + { + if (!hadName) { + // Evaluator expects a name here. + valueItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(moduleName.toString())); + } + } + ~EvalPreparer() + { + if (!hadName) + valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr()); + } + Item * const valueItem; + const bool hadName; + }; + EvalPreparer ep(itemValueItem, moduleName); + std::vector tss; + for (const ValuePtr &v : itemValueItem->properties()) + tss.emplace_back(v, moduleInstance); + moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false)); + return; + } + QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix); + const Item::PropertyMap &props = itemValueItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QualifiedId fullModuleName = moduleName; + fullModuleName << it.key(); + collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps); + } +} + +void ProjectResolver::createProductConfig(ResolvedProduct *product) +{ + 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); +} + +void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, + ProjectContext *projectContext) +{ + const ItemFuncPtr f = mappings.value(item->type()); + QBS_CHECK(f); + if (item->type() == ItemType::Project) { + ProjectContext subProjectContext = createProjectContext(projectContext); + (this->*f)(item, &subProjectContext); + } else { + (this->*f)(item, projectContext); + } +} + +ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const +{ + ProjectContext subProjectContext; + subProjectContext.parentContext = parentProjectContext; + subProjectContext.project = ResolvedProject::create(); + parentProjectContext->project->subProjects.push_back(subProjectContext.project); + subProjectContext.project->parentProject = parentProjectContext->project; + return subProjectContext; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/projectresolver.h b/src/lib/corelib/loader/projectresolver.h new file mode 100644 index 000000000..e29e1964c --- /dev/null +++ b/src/lib/corelib/loader/projectresolver.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PROJECTRESOLVER_H +#define PROJECTRESOLVER_H + +#include "projecttreebuilder.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace qbs { +class JobLimits; +namespace Internal { + +class Evaluator; +class Item; +class ProgressObserver; +class ScriptEngine; + +class ProjectResolver +{ +public: + ProjectResolver(Evaluator *evaluator, ProjectTreeBuilder::Result loadResult, + SetupProjectParameters setupParameters, Logger &logger); + ~ProjectResolver(); + + void setProgressObserver(ProgressObserver *observer); + TopLevelProjectPtr resolve(); + + static void applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product); + + using FileLocations = QHash, CodeLocation>; + static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct, + const QString &fileName, const GroupPtr &group, bool wildcard, + const CodeLocation &filesLocation = CodeLocation(), + FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr); + +private: + struct ProjectContext; + struct ProductContext; + struct ModuleContext; + class ProductContextSwitcher; + + void checkCancelation() const; + QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const; + QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const; + ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; + ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; + void ignoreItem(Item *item, ProjectContext *projectContext); + TopLevelProjectPtr resolveTopLevelProject(); + void resolveProject(Item *item, ProjectContext *projectContext); + void resolveProjectFully(Item *item, ProjectContext *projectContext); + void resolveSubProject(Item *item, ProjectContext *projectContext); + void resolveProduct(Item *item, ProjectContext *projectContext); + void resolveProductFully(Item *item, ProjectContext *projectContext); + void resolveModules(const Item *item, ProjectContext *projectContext); + void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits, + ProjectContext *projectContext); + void gatherProductTypes(ResolvedProduct *product, Item *item); + QVariantMap resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues); + void resolveGroup(Item *item, ProjectContext *projectContext); + void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled); + void resolveShadowProduct(Item *item, ProjectContext *); + void resolveExport(Item *exportItem, ProjectContext *); + std::unique_ptr resolveExportChild(const Item *item, + const ExportedModule &module); + void resolveRule(Item *item, ProjectContext *projectContext); + void resolveRuleArtifact(const RulePtr &rule, Item *item); + void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, + QualifiedIdSet *seenBindings); + void resolveFileTagger(Item *item, ProjectContext *projectContext); + void resolveJobLimit(Item *item, ProjectContext *projectContext); + void resolveScanner(Item *item, 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); + QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); + QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, + const QVariantMap &tmplt, bool lookupPrototype, + bool checkErrors); + void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, + QVariantMap &result, bool checkErrors); + void checkAllowedValues( + const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl, + const QString &key) const; + void createProductConfig(ResolvedProduct *product); + ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; + void adaptExportedPropertyValues(const Item *shadowProductItem); + void collectExportedProductDependencies(); + + struct ProductDependencyInfo + { + ProductDependencyInfo(ResolvedProductPtr product, + QVariantMap parameters = QVariantMap()) + : product(std::move(product)), parameters(std::move(parameters)) + { + } + + ResolvedProductPtr product; + QVariantMap parameters; + }; + + QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) const; + QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const; + static void matchArtifactProperties(const ResolvedProductPtr &product, + const std::vector &artifacts); + void printProfilingInfo(); + + void collectPropertiesForExportItem(Item *productModuleInstance); + void collectPropertiesForModuleInExportItem(const Item::Module &module); + + void collectPropertiesForExportItem(const QualifiedId &moduleName, + const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps); + void setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector &properties); + + Evaluator *m_evaluator = nullptr; + Logger &m_logger; + ScriptEngine *m_engine = nullptr; + ProgressObserver *m_progressObserver = nullptr; + ProductContext *m_productContext = nullptr; + ModuleContext *m_moduleContext = nullptr; + QHash m_productsByItem; + QHash > m_productsByType; + QHash m_productItemMap; + mutable QHash m_fileContextMap; + mutable QHash m_scriptFunctionMap; + mutable QHash, QString> m_scriptFunctions; + mutable QHash m_sourceCode; + const SetupProjectParameters m_setupParams; + ProjectTreeBuilder::Result m_loadResult; + Set m_groupLocationWarnings; + std::vector> m_productExportInfo; + std::vector m_queuedErrors; + qint64 m_elapsedTimeModPropEval = 0; + qint64 m_elapsedTimeAllPropEval = 0; + qint64 m_elapsedTimeGroups = 0; + + typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); + using ItemFuncMap = QMap; + void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); +}; + +} // namespace Internal +} // namespace qbs + +#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp new file mode 100644 index 000000000..9c0209882 --- /dev/null +++ b/src/lib/corelib/loader/projecttreebuilder.cpp @@ -0,0 +1,2213 @@ +/**************************************************************************** +** +** 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 "groupshandler.h" +#include "itemreader.h" +#include "localprofiles.h" +#include "moduleinstantiator.h" +#include "moduleloader.h" +#include "modulepropertymerger.h" +#include "probesresolver.h" +#include "productitemmultiplexer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace qbs::Internal { + +namespace { + +class ContextBase +{ +public: + Item *item = nullptr; + Item *scope = nullptr; + QString name; +}; + +class ProjectContext; + +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; + + struct ResolvedDependsItem { + Item *item = nullptr; + QualifiedId name; + QStringList subModules; + FileTags productTypes; + QStringList multiplexIds; + std::optional 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, + QualifiedId name, QString profile, QString multiplexId) + : item(dependency.item), name(std::move(name)), profile(std::move(profile)), + multiplexId(std::move(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 pendingDependsItems; + std::optional currentDependsItem; + std::queue pendingResolvedDependencies; + bool requiredByLoadingItem = true; + }; + std::list resolveDependenciesState; + + QString uniqueName() const; +}; + +class TopLevelProjectContext +{ +public: + TopLevelProjectContext() = default; + TopLevelProjectContext(const TopLevelProjectContext &) = delete; + TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete; + ~TopLevelProjectContext() { qDeleteAll(projects); } + + std::vector projects; + std::list> productsToHandle; + std::vector probes; + QString buildDirectory; +}; + +class ProjectContext : public ContextBase +{ +public: + TopLevelProjectContext *topLevelProject = nullptr; + ProjectTreeBuilder::Result *result = nullptr; + std::vector products; + std::vector searchPathsStack; +}; + + +using ShadowProductInfo = std::pair; +enum class Deferral { Allowed, NotAllowed }; +enum class HandleDependency { Use, Ignore, Defer }; + +class TimingData { +public: + qint64 prepareProducts = 0; + qint64 productDependencies = 0; + qint64 handleProducts = 0; + qint64 propertyChecking = 0; +}; + +} // namespace + +class ProjectTreeBuilder::Private +{ +public: + Private(const SetupProjectParameters ¶meters, 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 &referencedFilePaths); + void handleProject(Result *loadResult, TopLevelProjectContext *topLevelProjectContext, + Item *projectItem, const Set &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 &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 multiplexProductItem(ProductContext &dummyContext, Item *productItem); + void checkCancelation() const; + QList loadReferencedFile(const QString &relativePath, + const CodeLocation &referencingLocation, + const Set &referencedFilePaths, + ProductContext &dummyContext); + void copyProperties(const Item *sourceProject, Item *targetProject); + bool mergeExportItems(ProductContext &productContext); + bool checkExportItemCondition(Item *exportItem, const ProductContext &product); + void resolveProbes(ProductContext &product, Item *item); + + 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); + + std::optional + resolveDependsItem(const ProductContext &product, Item *dependsItem); + std::queue + multiplexDependency(const ProductContext &product, + const ProductContext::ResolvedDependsItem &dependency); + static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2); + QVariantMap extractParameters(Item *dependsItem) const; + + const SetupProjectParameters ¶meters; + ItemPool &itemPool; + Evaluator &evaluator; + Logger &logger; + ProgressObserver *progressObserver = nullptr; + TimingData timingData; + ItemReader reader{logger}; + ProbesResolver probesResolver{parameters, evaluator, logger}; + ModuleProviderLoader moduleProviderLoader{parameters, reader, evaluator, probesResolver, logger}; + ModuleLoader moduleLoader{parameters, moduleProviderLoader, 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 disabledItems; + std::unique_ptr settings; + Set projectNamesUsedInOverrides; + Set productNamesUsedInOverrides; + Set disabledProjects; + Set erroneousProducts; + std::multimap productsByName; + + // For fast look-up when resolving Depends.productTypes. + // The contract is that it contains fully handled, error-free, enabled products. + std::multimap productsByType; + + Version qbsVersion; + Item *tempScopeItem = nullptr; + +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 ¶meters, ItemPool &itemPool, + Evaluator &evaluator, Logger &logger) + : d(new Private(parameters, itemPool, evaluator, logger)) +{ + d->reader.setDeprecationWarningMode(parameters.deprecationWarningMode()); + d->reader.setEnableTiming(parameters.logElapsedTime()); + d->settings = std::make_unique(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 &oldProbes) +{ + d->probesResolver.setOldProjectProbes(oldProbes); +} + +void ProjectTreeBuilder::setOldProductProbes( + const QHash> &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..:value")); + e.append(QLatin1Char('\t') + Tr::tr("products..:value")); + e.append(QLatin1Char('\t') + Tr::tr("modules..:value")); + e.append(QLatin1Char('\t') + Tr::tr("products..." + ":value")); + e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.." + ":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 &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 &referencedFilePaths) +{ + auto p = std::make_unique(); + 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); + + resolveProbes(dummyProductContext, projectItem); + projectContext.topLevelProject->probes << dummyProductContext.info.probes; + + localProfiles.collectProfilesFromItems(projectItem, projectContext.scope); + + QList 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 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 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(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 { + 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; + } + } + 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 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.searchPaths); + + 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 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 ¤tSearchPaths = 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 &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(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)); + moduleLoader.printProfilingInfo(6); + 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 &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 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 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 ProjectTreeBuilder::Private::loadReferencedFile( + const QString &relativePath, const CodeLocation &referencingLocation, + const Set &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 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 builtinProjectProperties + = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties(); + Set 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(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(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(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( + baseValue->clone()); + jsValue->setBaseValue(jsBaseValue); + std::vector 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 exportItems; + QList 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 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::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); +} + +void ProjectTreeBuilder::Private::resolveProbes(ProductContext &product, Item *item) +{ + product.info.probes << probesResolver.resolveProbes({product.name, product.uniqueName()}, item); +} + +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 { + 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. + const ModuleLoader::ProductContext loaderContext{ + product.item, product.project->item, product.name, product.uniqueName(), + product.profileName, product.multiplexConfigurationId, product.moduleProperties, + product.profileModuleProperties}; + const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile( + loaderContext, dependency.location(), dependency.name, dependency.fallbackMode, + dependency.requiredGlobally); + moduleItem = loaderResult.moduleItem; + product.info.probes << loaderResult.providerProbes; + + 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.empty()) { + 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; +} + +std::optional +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 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 +ProjectTreeBuilder::Private::multiplexDependency( + const ProductContext &product, const ProductContext::ResolvedDependsItem &dependency) +{ + std::queue dependencies; + if (!dependency.productTypes.empty()) { + std::vector 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; +} + +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/loader/projecttreebuilder.h b/src/lib/corelib/loader/projecttreebuilder.h new file mode 100644 index 000000000..87111f730 --- /dev/null +++ b/src/lib/corelib/loader/projecttreebuilder.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 "moduleproviderloader.h" + +#include +#include +#include + +#include +#include + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class Evaluator; +class FileTime; +class Item; +class ItemPool; +class ProgressObserver; + +using ModulePropertiesPerGroup = std::unordered_map; + +// 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 ¶meters, ItemPool &itemPool, + Evaluator &evaluator, Logger &logger); + ~ProjectTreeBuilder(); + + struct Result + { + struct ProductInfo + { + std::vector probes; + ModulePropertiesPerGroup modulePropertiesSetInGroups; + ErrorInfo delayedError; + }; + + Item *root = nullptr; + std::unordered_map productInfos; + std::vector projectProbes; + StoredModuleProviderInfo storedModuleProviderInfo; + Set qbsFiles; + QVariantMap profileConfigs; + }; + Result load(); + + void setProgressObserver(ProgressObserver *progressObserver); + void setSearchPaths(const QStringList &searchPaths); + void setOldProjectProbes(const std::vector &oldProbes); + void setOldProductProbes(const QHash> &oldProbes); + void setLastResolveTime(const FileTime &time); + void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo); + +private: + class Private; + Private * const d; +}; + +} // namespace Internal +} // namespace qbs -- cgit v1.2.3