diff options
Diffstat (limited to 'src/lib/corelib/language')
52 files changed, 2577 insertions, 11583 deletions
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 <logging/logger.h> -#include <logging/translator.h> -#include <parser/qmljsast_p.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/qttools.h> -#include <tools/stringconstants.h> -#include <tools/version.h> - -#include <QtCore/qdiriterator.h> - -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 <Name>'"), - 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 <Name>'"), - 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 <parser/qmljsastfwd_p.h> -#include <tools/set.h> - -#include <QtCore/qhash.h> -#include <QtCore/qstringlist.h> - -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<QStringList, QString> 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<QStringList, QString> m_typeNameToFile; - Set<QString> m_importAsNames; - - using JsImportsHash = QHash<QString, JsImport>; - 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 b28fe7d76..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 <logging/translator.h> -#include <tools/error.h> -#include <tools/qbsassert.h> -#include <tools/stringconstants.h> - -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<ItemValue>(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<JSSourceValue>(outerVal), - std::static_pointer_cast<JSSourceValue>(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->setSourceUsesBaseFlag(); - } - 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<JSSourceValue>(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/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index 7004244fa..44dc8a326 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -166,25 +166,28 @@ void BuiltinDeclarations::insert(const ItemDeclaration &decl) static PropertyDeclaration conditionProperty() { - return PropertyDeclaration(StringConstants::conditionProperty(), PropertyDeclaration::Boolean, - StringConstants::trueValue()); + return { + StringConstants::conditionProperty(), + PropertyDeclaration::Boolean, + StringConstants::trueValue()}; } static PropertyDeclaration alwaysRunProperty() { - return PropertyDeclaration(StringConstants::alwaysRunProperty(), PropertyDeclaration::Boolean, - StringConstants::falseValue()); + return { + StringConstants::alwaysRunProperty(), + PropertyDeclaration::Boolean, + StringConstants::falseValue()}; } static PropertyDeclaration nameProperty() { - return PropertyDeclaration(StringConstants::nameProperty(), PropertyDeclaration::String); + return {StringConstants::nameProperty(), PropertyDeclaration::String}; } static PropertyDeclaration buildDirProperty() { - return PropertyDeclaration(StringConstants::buildDirectoryProperty(), - PropertyDeclaration::Path); + return {StringConstants::buildDirectoryProperty(), PropertyDeclaration::Path}; } static PropertyDeclaration prepareScriptProperty() @@ -244,11 +247,11 @@ void BuiltinDeclarations::addDependsItem() PropertyDeclaration::StringList); item << PropertyDeclaration(StringConstants::limitToSubProjectProperty(), PropertyDeclaration::Boolean, StringConstants::falseValue()); - item << PropertyDeclaration(StringConstants::multiplexConfigurationIdsProperty(), - PropertyDeclaration::StringList, QString(), - PropertyDeclaration::ReadOnlyFlag); - item << PropertyDeclaration(StringConstants::enableFallbackProperty(), - PropertyDeclaration::Boolean, StringConstants::trueValue()); + item << PropertyDeclaration( + StringConstants::multiplexConfigurationIdsProperty(), + PropertyDeclaration::StringList, + QString(), + PropertyDeclaration::ReadOnlyFlag); insert(item); } @@ -258,7 +261,6 @@ void BuiltinDeclarations::addExportItem() item << PropertyDeclaration(StringConstants::prefixMappingProperty(), PropertyDeclaration::Variant); auto allowedChildTypes = item.allowedChildTypes(); - allowedChildTypes.insert(ItemType::Parameters); allowedChildTypes.insert(ItemType::Properties); item.setAllowedChildTypes(allowedChildTypes); insert(item); @@ -326,26 +328,28 @@ void BuiltinDeclarations::addModuleProviderItem() ItemDeclaration item(ItemType::ModuleProvider); item << nameProperty() << PropertyDeclaration(QStringLiteral("outputBaseDir"), PropertyDeclaration::String) + << PropertyDeclaration(StringConstants::isEagerProperty(), + PropertyDeclaration::Boolean, + StringConstants::trueValue()) + << PropertyDeclaration(StringConstants::moduleNameProperty(), PropertyDeclaration::String) << PropertyDeclaration(QStringLiteral("relativeSearchPaths"), PropertyDeclaration::StringList); - item.setAllowedChildTypes({ItemType::Probe}); + item.setAllowedChildTypes({ItemType::PropertyOptions, ItemType::Probe}); insert(item); } ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type) { ItemDeclaration item(type); - item.setAllowedChildTypes(ItemDeclaration::TypeNames() - << ItemType::Group - << ItemType::Depends - << ItemType::FileTagger - << ItemType::JobLimit - << ItemType::Rule - << ItemType::Parameter - << ItemType::Probe - << ItemType::PropertyOptions - << ItemType::Scanner); - item << nameProperty(); + item.setAllowedChildTypes({ItemType::Depends, ItemType::FileTagger, ItemType::Group, + ItemType::JobLimit, ItemType::Parameter, ItemType::Parameters, + ItemType::Probe, ItemType::PropertyOptions, + ItemType::Rule, ItemType::Scanner}); + PropertyDeclaration nameDecl = nameProperty(); + PropertyDeclaration::Flags nameFlags = nameDecl.flags(); + nameFlags |= PropertyDeclaration::ReadOnlyFlag; + nameDecl.setFlags(nameFlags); + item << nameDecl; item << conditionProperty(); PropertyDeclaration setupBuildEnvDecl(StringConstants::setupBuildEnvironmentProperty(), PropertyDeclaration::Variant, QString(), diff --git a/src/lib/corelib/language/deprecationinfo.h b/src/lib/corelib/language/deprecationinfo.h index 89cd07f4a..2f9bfc103 100644 --- a/src/lib/corelib/language/deprecationinfo.h +++ b/src/lib/corelib/language/deprecationinfo.h @@ -39,6 +39,11 @@ #ifndef QBS_DEPRECATIONINFO_H #define QBS_DEPRECATIONINFO_H +#include <api/languageinfo.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/deprecationwarningmode.h> +#include <tools/error.h> #include <tools/version.h> #include <QtCore/qstring.h> @@ -58,7 +63,46 @@ public: bool isValid() const { return m_removalVersion.isValid(); } Version removalVersion() const { return m_removalVersion; } - QString additionalUserInfo() const { return m_additionalUserInfo; } + + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, bool isItem, Logger &logger) const + { + if (!isValid()) + return {}; + const Version qbsVersion = LanguageInfo::qbsVersion(); + if (removalVersion() <= qbsVersion) { + const QString msgTemplate = isItem + ? Tr::tr("The item '%1' can no longer be used. It was removed in Qbs %2.") + : Tr::tr("The property '%1' can no longer be used. It was removed in Qbs %2."); + ErrorInfo error(msgTemplate.arg(name, removalVersion().toString()), loc); + if (!m_additionalUserInfo.isEmpty()) + error.append(m_additionalUserInfo); + return error; + } + const QString msgTemplate = isItem + ? Tr::tr("The item '%1' is deprecated and will be removed in Qbs %2.") + : Tr::tr("The property '%1' is deprecated and will be removed in Qbs %2."); + ErrorInfo error(msgTemplate.arg(name, removalVersion().toString()), loc); + if (!m_additionalUserInfo.isEmpty()) + error.append(m_additionalUserInfo); + switch (mode) { + case DeprecationWarningMode::Error: + return error; + case DeprecationWarningMode::On: + logger.printWarning(error); + break; + case DeprecationWarningMode::BeforeRemoval: { + const Version next(qbsVersion.majorVersion(), qbsVersion.minorVersion() + 1); + if (removalVersion() == next || removalVersion().minorVersion() == 0) + logger.printWarning(error); + break; + } + case DeprecationWarningMode::Off: + break; + } + + return {}; + } private: Version m_removalVersion; diff --git a/src/lib/corelib/language/evaluationdata.h b/src/lib/corelib/language/evaluationdata.h deleted file mode 100644 index 791b2f234..000000000 --- a/src/lib/corelib/language/evaluationdata.h +++ /dev/null @@ -1,66 +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_EVALUATIONDATA_H -#define QBS_EVALUATIONDATA_H - -#include <QtCore/qhash.h> -#include <QtCore/qvariant.h> - -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> - -namespace qbs { -namespace Internal { - -class Evaluator; -class Item; - -class EvaluationData -{ -public: - Evaluator *evaluator = nullptr; - const Item *item = nullptr; - mutable QHash<QScriptString, QScriptValue> valueCache; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_EVALUATIONDATA_H diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp index 84272af2f..084f2f4a9 100644 --- a/src/lib/corelib/language/evaluator.cpp +++ b/src/lib/corelib/language/evaluator.cpp @@ -39,18 +39,19 @@ #include "evaluator.h" -#include "evaluationdata.h" -#include "evaluatorscriptclass.h" #include "filecontext.h" #include "filetags.h" #include "item.h" #include "scriptengine.h" #include "value.h" +#include <quickjs.h> + #include <buildgraph/buildgraph.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/error.h> +#include <tools/fileinfo.h> #include <tools/scripttools.h> #include <tools/qbsassert.h> #include <tools/qttools.h> @@ -61,27 +62,63 @@ namespace qbs { namespace Internal { +class EvaluationData +{ +public: + Evaluator *evaluator = nullptr; + const Item *item = nullptr; + mutable QHash<QString, JSValue> valueCache; +}; + +static void convertToPropertyType_impl( + ScriptEngine *engine, const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, const Value *value, const CodeLocation &location, JSValue &v); +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj); +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); +static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); + +static bool debugProperties = false; + Evaluator::Evaluator(ScriptEngine *scriptEngine) : m_scriptEngine(scriptEngine) - , m_scriptClass(new EvaluatorScriptClass(scriptEngine)) + , m_scriptClass(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED, + getEvalPropertyNames, getEvalPropertySafe)) { + scriptEngine->registerEvaluator(this); } Evaluator::~Evaluator() { - for (const auto &data : qAsConst(m_scriptValueMap)) - delete attachedPointer<EvaluationData>(data); - delete m_scriptClass; + Set<JSValue> valuesToFree; + for (const auto &data : std::as_const(m_scriptValueMap)) { + const auto evalData = attachedPointer<EvaluationData>(data, m_scriptClass); + valuesToFree << data; + for (const JSValue cachedValue : evalData->valueCache) + JS_FreeValue(m_scriptEngine->context(), cachedValue); + evalData->item->removeObserver(this); + delete evalData; + } + for (const auto &scopes : std::as_const(m_fileContextScopesMap)) { + valuesToFree << scopes.fileScope; + valuesToFree << scopes.importScope; + } + for (const JSValue v : std::as_const(valuesToFree)) { + JS_FreeValue(m_scriptEngine->context(), v); + } + m_scriptEngine->unregisterEvaluator(this); } -QScriptValue Evaluator::property(const Item *item, const QString &name) +JSValue Evaluator::property(const Item *item, const QString &name) { - return scriptValue(item).property(name); + return getJsProperty(m_scriptEngine->context(), scriptValue(item), name); } -QScriptValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) +JSValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v; + JSValue v; evaluateProperty(&v, item, name, propertyWasSet); return v; } @@ -89,16 +126,19 @@ QScriptValue Evaluator::value(const Item *item, const QString &name, bool *prope bool Evaluator::boolValue(const Item *item, const QString &name, bool *propertyWasSet) { - return value(item, name, propertyWasSet).toBool(); + const ScopedJsValue sv(m_scriptEngine->context(), value(item, name, propertyWasSet)); + return JS_ToBool(m_scriptEngine->context(), sv); } int Evaluator::intValue(const Item *item, const QString &name, int defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toInt32(); + qint32 n; + JS_ToInt32(m_scriptEngine->context(), &n, v); + return n; } FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *propertySet) @@ -109,24 +149,27 @@ FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *p QString Evaluator::stringValue(const Item *item, const QString &name, const QString &defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toString(); + QString str = getJsString(m_scriptEngine->context(), v); + JS_FreeValue(m_scriptEngine->context(), v); + return str; } -static QStringList toStringList(const QScriptValue &scriptValue) +static QStringList toStringList(ScriptEngine *engine, const JSValue &scriptValue) { - if (scriptValue.isString()) - return {scriptValue.toString()}; - if (scriptValue.isArray()) { + if (JS_IsString(scriptValue)) + return {getJsString(engine->context(), scriptValue)}; + if (JS_IsArray(engine->context(), scriptValue)) { QStringList lst; int i = 0; for (;;) { - QScriptValue elem = scriptValue.property(i++); - if (!elem.isValid()) + JSValue elem = JS_GetPropertyUint32(engine->context(), scriptValue, i++); + if (JS_IsUndefined(elem)) break; - lst.push_back(elem.toString()); + lst.push_back(getJsString(engine->context(), elem)); + JS_FreeValue(engine->context(), elem); } return lst; } @@ -142,13 +185,22 @@ QStringList Evaluator::stringListValue(const Item *item, const QString &name, bo std::optional<QStringList> Evaluator::optionalStringListValue( const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v = property(item, name); - handleEvaluationError(item, name, v); + const ScopedJsValue v(m_scriptEngine->context(), property(item, name)); + handleEvaluationError(item, name); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - if (v.isUndefined()) + if (JS_IsUndefined(v)) return std::nullopt; - return toStringList(v); + return toStringList(m_scriptEngine, v); +} + +QVariant Evaluator::variantValue(const Item *item, const QString &name, bool *propertySet) +{ + const ScopedJsValue jsValue(m_scriptEngine->context(), property(item, name)); + handleEvaluationError(item, name); + if (propertySet) + *propertySet = isNonDefaultValue(item, name); + return getJsVariant(m_scriptEngine->context(), jsValue); } bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const @@ -159,15 +211,15 @@ bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const } void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc, - QScriptValue &v) + JSValue &v) { - m_scriptClass->convertToPropertyType(decl, loc, v); + convertToPropertyType_impl(engine(), QString(), nullptr, decl, nullptr, loc, v); } -QScriptValue Evaluator::scriptValue(const Item *item) +JSValue Evaluator::scriptValue(const Item *item) { - QScriptValue &scriptValue = m_scriptValueMap[item]; - if (scriptValue.isObject()) { + JSValue &scriptValue = m_scriptValueMap[item]; + if (JS_IsObject(scriptValue)) { // already initialized return scriptValue; } @@ -175,111 +227,784 @@ QScriptValue Evaluator::scriptValue(const Item *item) const auto edata = new EvaluationData; edata->evaluator = this; edata->item = item; - edata->item->setObserver(this); + edata->item->addObserver(this); - scriptValue = m_scriptEngine->newObject(m_scriptClass); + scriptValue = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass); attachPointerTo(scriptValue, edata); return scriptValue; } -void Evaluator::onItemPropertyChanged(Item *item) +void Evaluator::handleEvaluationError(const Item *item, const QString &name) { - auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item)); - if (data) - data->valueCache.clear(); -} - -void Evaluator::handleEvaluationError(const Item *item, const QString &name, - const QScriptValue &scriptValue) -{ - throwOnEvaluationError(m_scriptEngine, scriptValue, [&item, &name] () { + throwOnEvaluationError(m_scriptEngine, [&item, &name] () { const ValueConstPtr &value = item->property(name); return value ? value->location() : CodeLocation(); }); } -void Evaluator::setPathPropertiesBaseDir(const QString &dirPath) -{ - m_scriptClass->setPathPropertiesBaseDir(dirPath); -} - -void Evaluator::clearPathPropertiesBaseDir() -{ - m_scriptClass->clearPathPropertiesBaseDir(); -} - -bool Evaluator::evaluateProperty(QScriptValue *result, const Item *item, const QString &name, +bool Evaluator::evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet) { *result = property(item, name); - handleEvaluationError(item, name, *result); + ScopedJsValue valMgr(m_scriptEngine->context(), *result); + handleEvaluationError(item, name); + valMgr.release(); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - return result->isValid() && !result->isUndefined(); + return !JS_IsUndefined(*result); } Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConstPtr &file) { FileContextScopes &result = m_fileContextScopesMap[file]; - if (!result.fileScope.isObject()) { + if (!JS_IsObject(result.fileScope)) { if (file->idScope()) result.fileScope = scriptValue(file->idScope()); else result.fileScope = m_scriptEngine->newObject(); - result.fileScope.setProperty(StringConstants::filePathGlobalVar(), file->filePath()); - result.fileScope.setProperty(StringConstants::pathGlobalVar(), file->dirPath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::filePathGlobalVar(), file->filePath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::pathGlobalVar(), file->dirPath()); } - if (!result.importScope.isObject()) { + if (!JS_IsObject(result.importScope)) { try { - result.importScope = m_scriptEngine->newObject(); - setupScriptEngineForFile(m_scriptEngine, file, result.importScope, - ObserveMode::Enabled); + ScopedJsValue importScope(m_scriptEngine->context(), m_scriptEngine->newObject()); + setupScriptEngineForFile(m_scriptEngine, file, importScope, ObserveMode::Enabled); + result.importScope = importScope.release(); } catch (const ErrorInfo &e) { - result.importScope = m_scriptEngine->currentContext()->throwError(e.toString()); + result.importScope = throwError(m_scriptEngine->context(), e.toString()); } } return result; } -void Evaluator::setCachingEnabled(bool enabled) +// This is the only function in this class that can be called from a thread that is not +// the evaluating one. For this reason, we do not clear the cache here, as that would +// incur enourmous synchronization overhead. Instead, we mark the item's cache as invalidated +// and do the actual clearing only at the very few places where the cache is actually accessed. +void Evaluator::invalidateCache(const Item *item) { - m_scriptClass->setValueCacheEnabled(enabled); + std::lock_guard lock(m_cacheInvalidationMutex); + m_invalidatedCaches << item; } -PropertyDependencies Evaluator::propertyDependencies() const +void Evaluator::clearCache(const Item *item) { - return m_scriptClass->propertyDependencies(); + std::lock_guard lock(m_cacheInvalidationMutex); + if (const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item), + m_scriptEngine->dataWithPtrClass())) { + clearCache(*data); + m_invalidatedCaches.remove(data->item); + } +} + +void Evaluator::clearCacheIfInvalidated(EvaluationData &edata) +{ + std::lock_guard lock(m_cacheInvalidationMutex); + if (const auto it = m_invalidatedCaches.find(edata.item); it != m_invalidatedCaches.end()) { + clearCache(edata); + m_invalidatedCaches.erase(it); + } } -void Evaluator::clearPropertyDependencies() +void Evaluator::clearCache(EvaluationData &edata) { - m_scriptClass->clearPropertyDependencies(); + for (const auto value : std::as_const(edata.valueCache)) + JS_FreeValue(m_scriptEngine->context(), value); + edata.valueCache.clear(); } -void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, +void throwOnEvaluationError(ScriptEngine *engine, const std::function<CodeLocation()> &provideFallbackCodeLocation) { - if (Q_LIKELY(!engine->hasErrorOrException(scriptValue))) + if (JsException ex = engine->checkAndClearException(provideFallbackCodeLocation())) + throw ex.toErrorInfo(); +} + +static void makeTypeError(ScriptEngine *engine, const ErrorInfo &error, JSValue &v) +{ + v = throwError(engine->context(), error.toString()); +} + +static void makeTypeError(ScriptEngine *engine, const PropertyDeclaration &decl, + const CodeLocation &location, JSValue &v) +{ + const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") + .arg(decl.name(), decl.typeString()), location); + makeTypeError(engine, error, v); +} + +static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) +{ + const VariantValuePtr v = item->variantProperty + (StringConstants::qbsSourceDirPropertyInternal()); + return v ? v->value().toString() : defaultValue; +} + +static void convertToPropertyType_impl(ScriptEngine *engine, + const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, + const Value *value, const CodeLocation &location, JSValue &v) +{ + JSContext * const ctx = engine->context(); + if (JS_IsUndefined(v) || JS_IsError(ctx, v) || JS_IsException(v)) return; - QString message; - QString filePath; - int line = -1; - const QScriptValue value = scriptValue.isError() ? scriptValue - : engine->uncaughtException(); - if (value.isError()) { - QScriptValue v = value.property(QStringLiteral("message")); - if (v.isString()) - message = v.toString(); - v = value.property(StringConstants::fileNameProperty()); - if (v.isString()) - filePath = v.toString(); - v = value.property(QStringLiteral("lineNumber")); - if (v.isNumber()) - line = v.toInt32(); - throw ErrorInfo(message, CodeLocation(filePath, line, -1, false)); - } - message = value.toString(); - throw ErrorInfo(message, provideFallbackCodeLocation()); + QString srcDir; + QString actualBaseDir; + const Item * const srcDirItem = value && value->scope() ? value->scope() : item; + if (item && !pathPropertiesBaseDir.isEmpty()) { + const VariantValueConstPtr itemSourceDir + = item->variantProperty(QStringLiteral("sourceDirectory")); + actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; + } + switch (decl.type()) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Boolean: + if (!JS_IsBool(v)) + v = JS_NewBool(ctx, JS_ToBool(ctx, v)); + break; + case PropertyDeclaration::Integer: + if (!JS_IsNumber(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::Path: + { + if (!JS_IsString(v)) { + makeTypeError(engine, decl, location, v); + break; + } + const QString srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir) + : pathPropertiesBaseDir; + if (!srcDir.isEmpty()) { + v = engine->toScriptValue(QDir::cleanPath(FileInfo::resolvePath(srcDir, + getJsString(ctx, v)))); + JS_FreeValue(ctx, v); + } + break; + } + case PropertyDeclaration::String: + if (!JS_IsString(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::PathList: + srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir) + : pathPropertiesBaseDir; + // Fall-through. + case PropertyDeclaration::StringList: + { + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + const quint32 c = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 i = 0; i < c; ++i) { + const ScopedJsValue elem(ctx, JS_GetPropertyUint32(ctx, v, i)); + if (JS_IsUndefined(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (JS_IsNull(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (!JS_IsString(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " + "string type.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (srcDir.isEmpty()) + continue; + const JSValue newElem = engine->toScriptValue( + QDir::cleanPath(FileInfo::resolvePath(srcDir, getJsString(ctx, elem)))); + JS_SetPropertyUint32(ctx, v, i, newElem); + } + break; + } + case PropertyDeclaration::VariantList: + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + break; + } +} + +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValue obj) +{ + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + if (!data) + return -1; + const Item::PropertyMap &map = data->item->properties(); + *plen = map.size(); + if (!map.isEmpty()) { + *ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab)); + JSPropertyEnum *entry = *ptab; + for (auto it = map.cbegin(); it != map.cend(); ++it, ++entry) { + entry->atom = JS_NewAtom(ctx, it.key().toUtf8().constData()); + entry->is_enumerable = 1; + } + } else { + *ptab = nullptr; + } + return 0; +} + +class PropertyStackManager +{ +public: + PropertyStackManager(const Item *itemOfProperty, const QString &name, const Value *value, + std::stack<QualifiedId> &requestedProperties, + PropertyDependencies &propertyDependencies) + : m_requestedProperties(requestedProperties) + { + if (value->type() == Value::JSSourceValueType + && (itemOfProperty->type() == ItemType::ModuleInstance + || itemOfProperty->type() == ItemType::Module + || itemOfProperty->type() == ItemType::Export)) { + const VariantValueConstPtr varValue + = itemOfProperty->variantProperty(StringConstants::nameProperty()); + if (!varValue) + return; + m_stackUpdate = true; + const QualifiedId fullPropName + = QualifiedId::fromString(varValue->value().toString()) << name; + if (!requestedProperties.empty()) + propertyDependencies[fullPropName].insert(requestedProperties.top()); + m_requestedProperties.push(fullPropName); + } + } + + ~PropertyStackManager() + { + if (m_stackUpdate) + m_requestedProperties.pop(); + } + +private: + std::stack<QualifiedId> &m_requestedProperties; + bool m_stackUpdate = false; +}; + +class SVConverter : ValueHandler +{ + ScriptEngine * const engine; + const JSValue * const object; + Value * const valuePtr; + const Item * const itemOfProperty; + const QString * const propertyName; + const EvaluationData * const data; + JSValue * const result; + JSValueList scopeChain; + char pushedScopesCount; + +public: + + SVConverter(ScriptEngine *engine, const JSValue *obj, const ValuePtr &v, + const Item *_itemOfProperty, const QString *propertyName, + const EvaluationData *data, JSValue *result) + : engine(engine) + , object(obj) + , valuePtr(v.get()) + , itemOfProperty(_itemOfProperty) + , propertyName(propertyName) + , data(data) + , result(result) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + friend class AutoScopePopper; + + class AutoScopePopper + { + public: + AutoScopePopper(SVConverter *converter) + : m_converter(converter) + { + } + + ~AutoScopePopper() + { + m_converter->popScopes(); + } + + private: + SVConverter *m_converter; + }; + + void setupConvenienceProperty(const QString &conveniencePropertyName, JSValue *extraScope, + const JSValue &scriptValue) + { + if (!JS_IsObject(*extraScope)) + *extraScope = engine->newObject(); + const PropertyDeclaration::Type type + = itemOfProperty->propertyDeclaration(*propertyName).type(); + const bool isArray = type == PropertyDeclaration::StringList + || type == PropertyDeclaration::PathList + || type == PropertyDeclaration::Variant // TODO: Why? + || type == PropertyDeclaration::VariantList; + JSValue valueToSet = JS_DupValue(engine->context(), scriptValue); + if (isArray && JS_IsUndefined(valueToSet)) + valueToSet = engine->newArray(0, JsValueOwner::Caller); + setJsProperty(engine->context(), *extraScope, conveniencePropertyName, valueToSet); + } + + std::pair<JSValue, bool> createExtraScope(const JSSourceValue *value, Item *outerItem, + JSValue *outerScriptValue) + { + std::pair<JSValue, bool> result; + auto &extraScope = result.first; + result.second = true; + if (value->sourceUsesBase()) { + JSValue baseValue = JS_UNDEFINED; + if (value->baseValue()) { + SVConverter converter(engine, object, value->baseValue(), itemOfProperty, + propertyName, data, &baseValue); + converter.start(); + } + setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); + } + if (value->sourceUsesOuter()) { + JSValue v = JS_UNDEFINED; + bool doSetup = false; + if (outerItem) { + v = data->evaluator->property(outerItem, *propertyName); + if (JsException ex = engine->checkAndClearException({})) { + extraScope = engine->throwError(ex.toErrorInfo().toString()); + result.second = false; + return result; + } + doSetup = true; + JS_FreeValue(engine->context(), v); + } else if (outerScriptValue) { + doSetup = true; + v = *outerScriptValue; + } + if (doSetup) + setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); + } + if (value->sourceUsesOriginal()) { + JSValue originalJs = JS_UNDEFINED; + ScopedJsValue originalMgr(engine->context(), JS_UNDEFINED); + if (data->item->propertyDeclaration(*propertyName).isScalar()) { + const Item *item = itemOfProperty; + + if (item->type() != ItemType::ModuleInstance + && item->type() != ItemType::ModuleInstancePlaceholder) { + const QString errorMessage = Tr::tr("The special value 'original' can only " + "be used with module properties."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + if (!value->scope()) { + const QString errorMessage = Tr::tr("The special value 'original' cannot " + "be used on the right-hand side of a property declaration."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + ValuePtr original; + for (const ValuePtr &v : value->candidates()) { + if (!v->scope()) { + original = v; + break; + } + } + + // This can happen when resolving shadow products. The error will be ignored + // in that case. + if (!original) { + const QString errorMessage = Tr::tr("Error setting up 'original'."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + SVConverter converter(engine, object, original, item, propertyName, data, + &originalJs); + converter.start(); + } else { + originalJs = engine->newArray(0, JsValueOwner::Caller); + originalMgr.setValue(originalJs); + } + setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalJs); + } + return result; + } + + void pushScope(const JSValue &scope) + { + if (JS_IsObject(scope)) { + scopeChain << scope; + ++pushedScopesCount; + } + } + + void pushScopeRecursively(const Item *scope) + { + if (scope) { + pushScopeRecursively(scope->scope()); + pushScope(data->evaluator->scriptValue(scope)); + } + } + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scopeChain.pop_back(); + } + + void handle(JSSourceValue *value) override + { + JSValue outerScriptValue = JS_UNDEFINED; + for (const JSSourceValue::Alternative &alternative : value->alternatives()) { + if (alternative.value->sourceUsesOuter() + && !data->item->outerItem() + && JS_IsUndefined(outerScriptValue)) { + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); + if (sver.hasError) { + *result = sver.scriptValue; + return; + } + outerScriptValue = sver.scriptValue; + } + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), + data->item->outerItem(), + &alternative, + value, &outerScriptValue); + if (!sver.tryNextAlternative || sver.hasError) { + *result = sver.scriptValue; + return; + } + } + *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; + } + + struct JSSourceValueEvaluationResult + { + JSValue scriptValue = JS_UNDEFINED; + bool tryNextAlternative = true; + bool hasError = false; + }; + + JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, + const JSSourceValue::Alternative *alternative = nullptr, + JSSourceValue *elseCaseValue = nullptr, JSValue *outerScriptValue = nullptr) + { + JSSourceValueEvaluationResult result; + QBS_ASSERT(!alternative || value == alternative->value.get(), return result); + AutoScopePopper autoScopePopper(this); + auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); + if (!maybeExtraScope.second) { + result.scriptValue = maybeExtraScope.first; + result.hasError = true; + return result; + } + const ScopedJsValue extraScopeMgr(engine->context(), maybeExtraScope.first); + const Evaluator::FileContextScopes fileCtxScopes + = data->evaluator->fileContextScopes(value->file()); + if (JsException ex = engine->checkAndClearException({})) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + result.hasError = true; + return result; + } + pushScope(fileCtxScopes.fileScope); + pushItemScopes(data->item); + if ((itemOfProperty->type() != ItemType::ModuleInstance + && itemOfProperty->type() != ItemType::ModuleInstancePlaceholder) || !value->scope()) { + pushScope(*object); + } + pushScopeRecursively(value->scope()); + pushScope(maybeExtraScope.first); + pushScope(fileCtxScopes.importScope); + if (alternative) { + ScopedJsValue sv(engine->context(), engine->evaluate(JsValueOwner::Caller, + alternative->condition.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException(alternative->condition.location)) { + + // This handles cases like the following: + // Depends { name: "cpp" } + // Properties { + // condition: qbs.targetOS.contains("darwin") + // bundle.isBundle: false + // } + // On non-Darwin platforms, bundle never gets instantiated, and thus bundle.isBundle + // has no scope, so the qbs item in the condition is not found. + // TODO: Ideally, we would never evaluate values in placeholder items, but + // there are currently several contexts where we do that, e.g. Export + // and Group items. Perhaps change that, or try to collect all such + // exceptions and don't try to evaluate other cases. + if (itemOfProperty->type() == ItemType::ModuleInstancePlaceholder) { + result.scriptValue = JS_UNDEFINED; + result.tryNextAlternative = false; + return result; + } + + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + //result.scriptValue = ex.takeValue(); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) { + // The condition is true. Continue evaluating the value. + result.tryNextAlternative = false; + } else { + // The condition is false. Try the next alternative or the else value. + result.tryNextAlternative = true; + return result; + } + sv.reset(engine->evaluate(JsValueOwner::Caller, + alternative->overrideListProperties.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException( + alternative->overrideListProperties.location)) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) + elseCaseValue->setIsExclusiveListValue(); + } + result.scriptValue = engine->evaluate(JsValueOwner::ScriptEngine, + value->sourceCodeForEvaluation(), value->file()->filePath(), value->line(), + scopeChain); + return result; + } + + void handle(ItemValue *value) override + { + *result = data->evaluator->scriptValue(value->item()); + if (JS_IsUninitialized(*result)) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) override + { + *result = engine->toScriptValue(variantValue->value(), variantValue->id()); + engine->takeOwnership(*result); + } +}; + +static void convertToPropertyType(ScriptEngine *engine, const Item *item, + const PropertyDeclaration& decl, const Value *value, JSValue &v) +{ + if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) { + v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237 + return; + } + convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl, + value, value->location(), v); +} + +static QString resultToString(JSContext *ctx, const JSValue &scriptValue) +{ + if (JS_IsUndefined(scriptValue)) + return QLatin1String("undefined"); + if (JS_IsArray(ctx, scriptValue)) + return getJsStringList(ctx, scriptValue).join(QLatin1Char(',')); + if (JS_IsObject(scriptValue)) { + return QStringLiteral("[Object: ") + + QString::number(jsObjectId(scriptValue)) + QLatin1Char(']'); + } + return getJsVariant(ctx, scriptValue).toString(); +} + +static void collectValuesFromNextChain( + ScriptEngine *engine, JSValue obj, const ValuePtr &value, const Item *itemOfProperty, const QString &name, + const EvaluationData *data, JSValue *result) +{ + JSValueList lst; + for (ValuePtr next = value; next; next = next->next()) { + JSValue v = JS_UNDEFINED; + SVConverter svc(engine, &obj, next, itemOfProperty, &name, data, &v); + svc.start(); + if (JsException ex = engine->checkAndClearException({})) { + const ScopedJsValueList l(engine->context(), lst); + *result = engine->throwError(ex.toErrorInfo().toString()); + return; + } + if (JS_IsUndefined(v)) + continue; + const PropertyDeclaration decl = data->item->propertyDeclaration(name); + convertToPropertyType(engine, data->item, decl, next.get(), v); + lst.push_back(JS_DupValue(engine->context(), v)); + if (next->type() == Value::JSSourceValueType + && std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) { + // TODO: Why on earth do we keep the last _2_ elements? + auto keepIt = lst.rbegin(); + for (int i = 0; i < 2 && keepIt != lst.rend(); ++i) + ++keepIt; + for (auto it = lst.begin(); it < keepIt.base(); ++it) + JS_FreeValue(engine->context(), *it); + lst.erase(lst.begin(), keepIt.base()); + break; + } + } + + *result = engine->newArray(int(lst.size()), JsValueOwner::ScriptEngine); + quint32 k = 0; + JSContext * const ctx = engine->context(); + for (const JSValue &v : std::as_const(lst)) { + QBS_ASSERT(!JS_IsError(ctx, v), continue); + if (JS_IsArray(ctx, v)) { + const quint32 vlen = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 j = 0; j < vlen; ++j) + JS_SetPropertyUint32(ctx, *result, k++, JS_GetPropertyUint32(ctx, v, j)); + JS_FreeValue(ctx, v); + } else { + JS_SetPropertyUint32(ctx, *result, k++, v); + } + } + setJsProperty(ctx, *result, StringConstants::lengthProperty(), JS_NewInt32(ctx, k)); +} + +struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; }; +static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item, + const QString &name, EvaluationData *data) +{ + Evaluator * const evaluator = data->evaluator; + const bool isModuleInstance = item->type() == ItemType::ModuleInstance + || item->type() == ItemType::ModuleInstancePlaceholder; + for (; item; item = item->prototype()) { + if (isModuleInstance + && (item->type() == ItemType::Module || item->type() == ItemType::Export)) { + break; // There's no property in a prototype that's not also in the instance. + } + const ValuePtr value = item->ownProperty(name); + if (!value) + continue; + const Item * const itemOfProperty = item; // The item that owns the property. + PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), + evaluator->requestedProperties(), + evaluator->propertyDependencies()); + JSValue result; + if (evaluator->cachingEnabled()) { + data->evaluator->clearCacheIfInvalidated(*data); + const auto result = data->valueCache.constFind(name); + if (result != data->valueCache.constEnd()) { + if (debugProperties) + qDebug() << "[SC] cache hit " << name << ": " + << resultToString(engine->context(), *result); + return {*result, true}; + } + } + + if (value->next()) { + collectValuesFromNextChain(engine, obj, value, itemOfProperty, name, data, &result); + } else { + SVConverter converter(engine, &obj, value, itemOfProperty, &name, data, &result); + converter.start(); + const PropertyDeclaration decl = data->item->propertyDeclaration(name); + convertToPropertyType(engine, data->item, decl, value.get(), result); + } + + if (debugProperties) + qDebug() << "[SC] cache miss " << name << ": " + << resultToString(engine->context(), result); + if (evaluator->cachingEnabled()) { + data->evaluator->clearCacheIfInvalidated(*data); + const auto it = data->valueCache.find(name); + if (it != data->valueCache.end()) { + JS_FreeValue(engine->context(), it.value()); + it.value() = JS_DupValue(engine->context(), result); + } else { + data->valueCache.insert(name, JS_DupValue(engine->context(), result)); + } + } + return {result, true}; + } + return {JS_UNDEFINED, false}; +} + +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop) +{ + if (desc) { + desc->getter = desc->setter = desc->value = JS_UNDEFINED; + desc->flags = JS_PROP_ENUMERABLE; + } + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + const QString name = getJsString(ctx, prop); + if (debugProperties) + qDebug() << "[SC] queryProperty " << jsObjectId(obj) << " " << name; + + if (name == QStringLiteral("parent")) { + if (desc) { + Item * const parent = data->item->parent(); + desc->value = parent + ? JS_DupValue(ctx, data->evaluator->scriptValue(data->item->parent())) + : JS_UNDEFINED; + } + return 1; + } + + if (!data) { + if (debugProperties) + qDebug() << "[SC] queryProperty: no data attached"; + engine->setLastLookupStatus(false); + return -1; + } + + EvalResult result = getEvalProperty(engine, obj, data->item, name, data); + if (!result.found && data->item->parent()) { + if (debugProperties) + qDebug() << "[SC] queryProperty: query parent"; + const Item * const parentItem = data->item->parent(); + result = getEvalProperty(engine, evaluator->scriptValue(parentItem), parentItem, + name, data); + } + if (result.found) { + if (desc) + desc->value = JS_DupValue(ctx, result.v); + engine->setLastLookupStatus(true); + return 1; + } + + if (debugProperties) + qDebug() << "[SC] queryProperty: no such property"; + engine->setLastLookupStatus(false); + return 0; +} + +static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop) +{ + try { + return getEvalProperty(ctx, desc, obj, prop); + } catch (const ErrorInfo &e) { + ScopedJsValue error(ctx, throwError(ctx, e.toString())); + return -1; + } } } // namespace Internal diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h index 1e21391dc..d86e08eb1 100644 --- a/src/lib/corelib/language/evaluator.h +++ b/src/lib/corelib/language/evaluator.h @@ -44,16 +44,18 @@ #include "itemobserver.h" #include "qualifiedid.h" -#include <QtCore/qhash.h> +#include <quickjs.h> -#include <QtScript/qscriptvalue.h> +#include <QtCore/qhash.h> #include <functional> +#include <mutex> #include <optional> +#include <stack> namespace qbs { namespace Internal { -class EvaluatorScriptClass; +class EvaluationData; class FileTags; class Logger; class PropertyDeclaration; @@ -68,9 +70,10 @@ public: ~Evaluator() override; ScriptEngine *engine() const { return m_scriptEngine; } - QScriptValue property(const Item *item, const QString &name); + JSClassID classId() const { return m_scriptClass; } + JSValue property(const Item *item, const QString &name); - QScriptValue value(const Item *item, const QString &name, bool *propertySet = nullptr); + JSValue value(const Item *item, const QString &name, bool *propertySet = nullptr); bool boolValue(const Item *item, const QString &name, bool *propertyWasSet = nullptr); int intValue(const Item *item, const QString &name, int defaultValue = 0, bool *propertyWasSet = nullptr); @@ -82,54 +85,76 @@ public: std::optional<QStringList> optionalStringListValue(const Item *item, const QString &name, bool *propertyWasSet = nullptr); + QVariant variantValue(const Item *item, const QString &name, bool *propertySet = nullptr); + void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, - QScriptValue &v); + JSValue &v); - QScriptValue scriptValue(const Item *item); + JSValue scriptValue(const Item *item); struct FileContextScopes { - QScriptValue fileScope; - QScriptValue importScope; + JSValue fileScope = JS_UNDEFINED; + JSValue importScope = JS_UNDEFINED; }; FileContextScopes fileContextScopes(const FileContextConstPtr &file); - void setCachingEnabled(bool enabled); + void setCachingEnabled(bool enabled) { m_valueCacheEnabled = enabled; } + bool cachingEnabled() const { return m_valueCacheEnabled; } + void clearCache(const Item *item); + void invalidateCache(const Item *item); + void clearCacheIfInvalidated(EvaluationData &edata); + + PropertyDependencies &propertyDependencies() { return m_propertyDependencies; } + void clearPropertyDependencies() { m_propertyDependencies.clear(); } - PropertyDependencies propertyDependencies() const; - void clearPropertyDependencies(); + std::stack<QualifiedId> &requestedProperties() { return m_requestedProperties; } - void handleEvaluationError(const Item *item, const QString &name, - const QScriptValue &scriptValue); + void handleEvaluationError(const Item *item, const QString &name); - void setPathPropertiesBaseDir(const QString &dirPath); - void clearPathPropertiesBaseDir(); + QString pathPropertiesBaseDir() const { return m_pathPropertiesBaseDir; } + void setPathPropertiesBaseDir(const QString &dirPath) { m_pathPropertiesBaseDir = dirPath; } + void clearPathPropertiesBaseDir() { m_pathPropertiesBaseDir.clear(); } bool isNonDefaultValue(const Item *item, const QString &name) const; private: - void onItemPropertyChanged(Item *item) override; - bool evaluateProperty(QScriptValue *result, const Item *item, const QString &name, + void onItemPropertyChanged(Item *item) override { invalidateCache(item); } + bool evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet); + void clearCache(EvaluationData &edata); - ScriptEngine *m_scriptEngine; - EvaluatorScriptClass *m_scriptClass; - mutable QHash<const Item *, QScriptValue> m_scriptValueMap; + ScriptEngine * const m_scriptEngine; + const JSClassID m_scriptClass; + mutable QHash<const Item *, JSValue> m_scriptValueMap; mutable QHash<FileContextConstPtr, FileContextScopes> m_fileContextScopesMap; + QString m_pathPropertiesBaseDir; + PropertyDependencies m_propertyDependencies; + std::stack<QualifiedId> m_requestedProperties; + std::mutex m_cacheInvalidationMutex; + Set<const Item *> m_invalidatedCaches; + bool m_valueCacheEnabled = false; }; -void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, +void throwOnEvaluationError(ScriptEngine *engine, const std::function<CodeLocation()> &provideFallbackCodeLocation); class EvalCacheEnabler { public: - EvalCacheEnabler(Evaluator *evaluator) : m_evaluator(evaluator) + EvalCacheEnabler(Evaluator *evaluator, const QString &baseDir) : m_evaluator(evaluator) { m_evaluator->setCachingEnabled(true); + m_evaluator->setPathPropertiesBaseDir(baseDir); } - ~EvalCacheEnabler() { m_evaluator->setCachingEnabled(false); } + ~EvalCacheEnabler() { reset(); } + + void reset() + { + m_evaluator->setCachingEnabled(false); + m_evaluator->clearPathPropertiesBaseDir(); + } private: Evaluator * const m_evaluator; diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp deleted file mode 100644 index 375954133..000000000 --- a/src/lib/corelib/language/evaluatorscriptclass.cpp +++ /dev/null @@ -1,776 +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 "evaluatorscriptclass.h" - -#include "evaluationdata.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "scriptengine.h" -#include "propertydeclaration.h" -#include "value.h" -#include <logging/translator.h> -#include <tools/fileinfo.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/scripttools.h> -#include <tools/stringconstants.h> - -#include <QtCore/qbytearray.h> -#include <QtCore/qdebug.h> -#include <QtCore/qsettings.h> - -#include <QtScript/qscriptclasspropertyiterator.h> -#include <QtScript/qscriptstring.h> -#include <QtScript/qscriptvalue.h> - -#include <utility> - -namespace qbs { -namespace Internal { - -class SVConverter : ValueHandler -{ - EvaluatorScriptClass * const scriptClass; - ScriptEngine * const engine; - QScriptContext * const scriptContext; - const QScriptValue * const object; - Value * const valuePtr; - const Item * const itemOfProperty; - const QScriptString * const propertyName; - const EvaluationData * const data; - QScriptValue * const result; - char pushedScopesCount; - -public: - - SVConverter(EvaluatorScriptClass *esc, const QScriptValue *obj, const ValuePtr &v, - const Item *_itemOfProperty, const QScriptString *propertyName, const EvaluationData *data, - QScriptValue *result) - : scriptClass(esc) - , engine(static_cast<ScriptEngine *>(esc->engine())) - , scriptContext(esc->engine()->currentContext()) - , object(obj) - , valuePtr(v.get()) - , itemOfProperty(_itemOfProperty) - , propertyName(propertyName) - , data(data) - , result(result) - , pushedScopesCount(0) - { - } - - void start() - { - valuePtr->apply(this); - } - -private: - friend class AutoScopePopper; - - class AutoScopePopper - { - public: - AutoScopePopper(SVConverter *converter) - : m_converter(converter) - { - } - - ~AutoScopePopper() - { - m_converter->popScopes(); - } - - private: - SVConverter *m_converter; - }; - - void setupConvenienceProperty(const QString &conveniencePropertyName, QScriptValue *extraScope, - const QScriptValue &scriptValue) - { - if (!extraScope->isObject()) - *extraScope = engine->newObject(); - const PropertyDeclaration::Type type - = itemOfProperty->propertyDeclaration(propertyName->toString()).type(); - const bool isArray = type == PropertyDeclaration::StringList - || type == PropertyDeclaration::PathList - || type == PropertyDeclaration::Variant // TODO: Why? - || type == PropertyDeclaration::VariantList; - QScriptValue valueToSet = scriptValue; - if (isArray) { - if (!valueToSet.isValid() || valueToSet.isUndefined()) - valueToSet = engine->newArray(); - } else if (!valueToSet.isValid()) { - valueToSet = engine->undefinedValue(); - } - extraScope->setProperty(conveniencePropertyName, valueToSet); - } - - std::pair<QScriptValue, bool> createExtraScope(const JSSourceValue *value, Item *outerItem, - QScriptValue *outerScriptValue) - { - std::pair<QScriptValue, bool> result; - auto &extraScope = result.first; - result.second = true; - if (value->sourceUsesBase()) { - QScriptValue baseValue; - if (value->baseValue()) { - SVConverter converter(scriptClass, object, value->baseValue(), itemOfProperty, - propertyName, data, &baseValue); - converter.start(); - } - setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); - } - if (value->sourceUsesOuter()) { - QScriptValue v; - if (outerItem) { - v = data->evaluator->property(outerItem, *propertyName); - if (engine->hasErrorOrException(v)) { - extraScope = engine->lastErrorValue(v); - result.second = false; - return result; - } - } else if (outerScriptValue) { - v = *outerScriptValue; - } - if (v.isValid()) - setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); - } - if (value->sourceUsesOriginal()) { - QScriptValue originalValue; - if (data->item->propertyDeclaration(propertyName->toString()).isScalar()) { - const Item *item = itemOfProperty; - if (item->type() == ItemType::Module || item->type() == ItemType::Export) { - const QString errorMessage = Tr::tr("The special value 'original' cannot " - "be used on the right-hand side of a property declaration."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - // TODO: Provide a dedicated item type for not-yet-instantiated things that - // look like module instances in the AST visitor. - if (item->type() == ItemType::ModuleInstance - && !item->hasProperty(StringConstants::presentProperty())) { - const QString errorMessage = Tr::tr("Trying to assign property '%1' " - "on something that is not a module.").arg(propertyName->toString()); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - while (item->type() == ItemType::ModuleInstance) - item = item->prototype(); - if (item->type() != ItemType::Module && item->type() != ItemType::Export) { - const QString errorMessage = Tr::tr("The special value 'original' can only " - "be used with module properties."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - const ValuePtr v = item->property(*propertyName); - - // This can happen when resolving shadow products. The error will be ignored - // in that case. - if (!v) { - const QString errorMessage = Tr::tr("Error setting up 'original'."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - SVConverter converter(scriptClass, object, v, item, - propertyName, data, &originalValue); - converter.start(); - } else { - originalValue = engine->newArray(0); - } - setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue); - } - return result; - } - - void pushScope(const QScriptValue &scope) - { - if (scope.isObject()) { - scriptContext->pushScope(scope); - ++pushedScopesCount; - } - } - - void pushItemScopes(const Item *item) - { - const Item *scope = item->scope(); - if (scope) { - pushItemScopes(scope); - pushScope(data->evaluator->scriptValue(scope)); - } - } - - void popScopes() - { - for (; pushedScopesCount; --pushedScopesCount) - scriptContext->popScope(); - } - - void handle(JSSourceValue *value) override - { - QScriptValue outerScriptValue; - for (const JSSourceValue::Alternative &alternative : value->alternatives()) { - if (alternative.value->sourceUsesOuter() - && !data->item->outerItem() - && !outerScriptValue.isValid()) { - JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); - if (sver.hasError) { - *result = sver.scriptValue; - return; - } - outerScriptValue = sver.scriptValue; - } - JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), - data->item->outerItem(), - &alternative, - value, &outerScriptValue); - if (!sver.tryNextAlternative || sver.hasError) { - *result = sver.scriptValue; - return; - } - } - *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; - } - - struct JSSourceValueEvaluationResult - { - QScriptValue scriptValue; - bool tryNextAlternative = true; - bool hasError = false; - }; - - void injectErrorLocation(QScriptValue &sv, const CodeLocation &loc) - { - if (sv.isError() && !engine->lastErrorLocation(sv).isValid()) - sv = engine->currentContext()->throwError(engine->lastError(sv, loc).toString()); - } - - JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, - const JSSourceValue::Alternative *alternative = nullptr, - JSSourceValue *elseCaseValue = nullptr, QScriptValue *outerScriptValue = nullptr) - { - JSSourceValueEvaluationResult result; - QBS_ASSERT(!alternative || value == alternative->value.get(), return result); - AutoScopePopper autoScopePopper(this); - auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); - if (!maybeExtraScope.second) { - result.scriptValue = maybeExtraScope.first; - result.hasError = true; - return result; - } - const Evaluator::FileContextScopes fileCtxScopes - = data->evaluator->fileContextScopes(value->file()); - if (fileCtxScopes.importScope.isError()) { - result.scriptValue = fileCtxScopes.importScope; - result.hasError = true; - return result; - } - pushScope(fileCtxScopes.fileScope); - pushItemScopes(data->item); - if (itemOfProperty->type() != ItemType::ModuleInstance) { - // Own properties of module instances must not have the instance itself in the scope. - pushScope(*object); - } - if (value->definingItem()) - pushItemScopes(value->definingItem()); - pushScope(maybeExtraScope.first); - pushScope(fileCtxScopes.importScope); - if (alternative) { - QScriptValue sv = engine->evaluate(alternative->condition.value); - if (engine->hasErrorOrException(sv)) { - result.scriptValue = sv; - result.hasError = true; - injectErrorLocation(result.scriptValue, alternative->condition.location); - return result; - } - if (sv.toBool()) { - // The condition is true. Continue evaluating the value. - result.tryNextAlternative = false; - } else { - // The condition is false. Try the next alternative or the else value. - result.tryNextAlternative = true; - return result; - } - sv = engine->evaluate(alternative->overrideListProperties.value); - if (engine->hasErrorOrException(sv)) { - result.scriptValue = sv; - result.hasError = true; - injectErrorLocation(result.scriptValue, - alternative->overrideListProperties.location); - return result; - } - if (sv.toBool()) - elseCaseValue->setIsExclusiveListValue(); - } - result.scriptValue = engine->evaluate(value->sourceCodeForEvaluation(), - value->file()->filePath(), value->line()); - return result; - } - - void handle(ItemValue *value) override - { - *result = data->evaluator->scriptValue(value->item()); - if (!result->isValid()) - qDebug() << "SVConverter returned invalid script value."; - } - - void handle(VariantValue *variantValue) override - { - *result = engine->toScriptValue(variantValue->value()); - } -}; - -bool debugProperties = false; - -enum QueryPropertyType -{ - QPTDefault, - QPTParentProperty -}; - -EvaluatorScriptClass::EvaluatorScriptClass(ScriptEngine *scriptEngine) - : QScriptClass(scriptEngine) - , m_valueCacheEnabled(false) -{ -} - -QScriptClass::QueryFlags EvaluatorScriptClass::queryProperty(const QScriptValue &object, - const QScriptString &name, - QScriptClass::QueryFlags flags, - uint *id) -{ - Q_UNUSED(flags); - - // We assume that it's safe to save the result of the query in a member of the scriptclass. - // It must be cleared in the property method before doing any further lookup. - QBS_ASSERT(m_queryResult.isNull(), return {}); - - if (debugProperties) - qDebug() << "[SC] queryProperty " << object.objectId() << " " << name; - - auto const data = attachedPointer<EvaluationData>(object); - const QString nameString = name.toString(); - if (nameString == QStringLiteral("parent")) { - *id = QPTParentProperty; - m_queryResult.data = data; - return QScriptClass::HandlesReadAccess; - } - - *id = QPTDefault; - if (!data) { - if (debugProperties) - qDebug() << "[SC] queryProperty: no data attached"; - return {}; - } - - return queryItemProperty(data, nameString); -} - -QScriptClass::QueryFlags EvaluatorScriptClass::queryItemProperty(const EvaluationData *data, - const QString &name, - bool ignoreParent) -{ - for (const Item *item = data->item; item; item = item->prototype()) { - m_queryResult.value = item->ownProperty(name); - if (m_queryResult.value) { - m_queryResult.data = data; - m_queryResult.itemOfProperty = item; - return HandlesReadAccess; - } - } - - if (!ignoreParent && data->item && data->item->parent()) { - if (debugProperties) - qDebug() << "[SC] queryProperty: query parent"; - EvaluationData parentdata = *data; - parentdata.item = data->item->parent(); - const QueryFlags qf = queryItemProperty(&parentdata, name, true); - if (qf.testFlag(HandlesReadAccess)) { - m_queryResult.foundInParent = true; - m_queryResult.data = data; - return qf; - } - } - - if (debugProperties) - qDebug() << "[SC] queryProperty: no such property"; - return {}; -} - -QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue) -{ - return (scriptValue.isObject() - ? QStringLiteral("[Object: ") - + QString::number(scriptValue.objectId()) + QLatin1Char(']') - : scriptValue.toVariant().toString()); -} - -void EvaluatorScriptClass::collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, - const QString &propertyName, const ValuePtr &value) -{ - QScriptValueList lst; - Set<Value *> oldNextChain = m_currentNextChain; - for (ValuePtr next = value; next; next = next->next()) - m_currentNextChain.insert(next.get()); - - for (ValuePtr next = value; next; next = next->next()) { - QScriptValue v = data->evaluator->property(next->definingItem(), propertyName); - const auto se = static_cast<const ScriptEngine *>(engine()); - if (se->hasErrorOrException(v)) { - *result = se->lastErrorValue(v); - return; - } - if (v.isUndefined()) - continue; - lst << v; - if (next->type() == Value::JSSourceValueType - && std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) { - lst = lst.mid(lst.length() - 2); - break; - } - } - m_currentNextChain = oldNextChain; - - *result = engine()->newArray(); - quint32 k = 0; - for (const QScriptValue &v : qAsConst(lst)) { - QBS_ASSERT(!v.isError(), continue); - if (v.isArray()) { - const quint32 vlen = v.property(StringConstants::lengthProperty()).toInt32(); - for (quint32 j = 0; j < vlen; ++j) - result->setProperty(k++, v.property(j)); - } else { - result->setProperty(k++, v); - } - } -} - -static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) -{ - const VariantValuePtr v = item->variantProperty - (StringConstants::qbsSourceDirPropertyInternal()); - return v ? v->value().toString() : defaultValue; -} - -static void makeTypeError(const ErrorInfo &error, QScriptValue &v) -{ - v = v.engine()->currentContext()->throwError(QScriptContext::TypeError, - error.toString()); -} - -static void makeTypeError(const PropertyDeclaration &decl, const CodeLocation &location, - QScriptValue &v) -{ - const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") - .arg(decl.name(), decl.typeString()), location); - makeTypeError(error, v); -} - -static void convertToPropertyType_impl(const QString &pathPropertiesBaseDir, const Item *item, - const PropertyDeclaration& decl, - const CodeLocation &location, QScriptValue &v) -{ - if (v.isUndefined() || v.isError()) - return; - QString srcDir; - QString actualBaseDir; - if (item && !pathPropertiesBaseDir.isEmpty()) { - const VariantValueConstPtr itemSourceDir - = item->variantProperty(QStringLiteral("sourceDirectory")); - actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; - } - switch (decl.type()) { - case PropertyDeclaration::UnknownType: - case PropertyDeclaration::Variant: - break; - case PropertyDeclaration::Boolean: - if (!v.isBool()) - v = v.toBool(); - break; - case PropertyDeclaration::Integer: - if (!v.isNumber()) - makeTypeError(decl, location, v); - break; - case PropertyDeclaration::Path: - { - if (!v.isString()) { - makeTypeError(decl, location, v); - break; - } - const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) - : pathPropertiesBaseDir; - if (!srcDir.isEmpty()) - v = v.engine()->toScriptValue(QDir::cleanPath( - FileInfo::resolvePath(srcDir, v.toString()))); - break; - } - case PropertyDeclaration::String: - if (!v.isString()) - makeTypeError(decl, location, v); - break; - case PropertyDeclaration::PathList: - srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) - : pathPropertiesBaseDir; - // Fall-through. - case PropertyDeclaration::StringList: - { - if (!v.isArray()) { - QScriptValue x = v.engine()->newArray(1); - x.setProperty(0, v); - v = x; - } - const quint32 c = v.property(StringConstants::lengthProperty()).toUInt32(); - for (quint32 i = 0; i < c; ++i) { - QScriptValue elem = v.property(i); - if (elem.isUndefined()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " - "String expected.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (elem.isNull()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " - "String expected.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (!elem.isString()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " - "string type.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (srcDir.isEmpty()) - continue; - elem = v.engine()->toScriptValue( - QDir::cleanPath(FileInfo::resolvePath(srcDir, elem.toString()))); - v.setProperty(i, elem); - } - break; - } - case PropertyDeclaration::VariantList: - if (!v.isArray()) { - QScriptValue x = v.engine()->newArray(1); - x.setProperty(0, v); - v = x; - } - break; - } -} - -void EvaluatorScriptClass::convertToPropertyType(const PropertyDeclaration &decl, - const CodeLocation &loc, QScriptValue &v) -{ - convertToPropertyType_impl(QString(), nullptr, decl, loc, v); -} - -void EvaluatorScriptClass::convertToPropertyType(const Item *item, const PropertyDeclaration& decl, - const Value *value, QScriptValue &v) -{ - if (value->type() == Value::VariantValueType && v.isUndefined() && !decl.isScalar()) { - v = v.engine()->newArray(); // QTBUG-51237 - return; - } - convertToPropertyType_impl(m_pathPropertiesBaseDir, item, decl, value->location(), v); -} - -class PropertyStackManager -{ -public: - PropertyStackManager(const Item *itemOfProperty, const QScriptString &name, const Value *value, - std::stack<QualifiedId> &requestedProperties, - PropertyDependencies &propertyDependencies) - : m_requestedProperties(requestedProperties) - { - if (value->type() == Value::JSSourceValueType - && (itemOfProperty->type() == ItemType::ModuleInstance - || itemOfProperty->type() == ItemType::Module - || itemOfProperty->type() == ItemType::Export)) { - const VariantValueConstPtr varValue - = itemOfProperty->variantProperty(StringConstants::nameProperty()); - if (!varValue) - return; - m_stackUpdate = true; - const QualifiedId fullPropName - = QualifiedId::fromString(varValue->value().toString()) << name.toString(); - if (!requestedProperties.empty()) - propertyDependencies[fullPropName].insert(requestedProperties.top()); - m_requestedProperties.push(fullPropName); - } - } - - ~PropertyStackManager() - { - if (m_stackUpdate) - m_requestedProperties.pop(); - } - -private: - std::stack<QualifiedId> &m_requestedProperties; - bool m_stackUpdate = false; -}; - -QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QScriptString &name, - uint id) -{ - const bool foundInParent = m_queryResult.foundInParent; - const EvaluationData *data = m_queryResult.data; - const Item * const itemOfProperty = m_queryResult.itemOfProperty; - m_queryResult.foundInParent = false; - m_queryResult.data = nullptr; - m_queryResult.itemOfProperty = nullptr; - QBS_ASSERT(data, {}); - - const auto qpt = static_cast<QueryPropertyType>(id); - if (qpt == QPTParentProperty) { - return data->item->parent() - ? data->evaluator->scriptValue(data->item->parent()) - : engine()->undefinedValue(); - } - - ValuePtr value; - m_queryResult.value.swap(value); - QBS_ASSERT(value, return {}); - QBS_ASSERT(m_queryResult.isNull(), return {}); - - if (debugProperties) - qDebug() << "[SC] property " << name; - - PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), - m_requestedProperties, m_propertyDependencies); - - QScriptValue result; - if (m_valueCacheEnabled) { - result = data->valueCache.value(name); - if (result.isValid()) { - if (debugProperties) - qDebug() << "[SC] cache hit " << name << ": " << resultToString(result); - return result; - } - } - - if (value->next() && !m_currentNextChain.contains(value.get())) { - collectValuesFromNextChain(data, &result, name.toString(), value); - } else { - QScriptValue parentObject; - if (foundInParent) - parentObject = data->evaluator->scriptValue(data->item->parent()); - SVConverter converter(this, foundInParent ? &parentObject : &object, value, itemOfProperty, - &name, data, &result); - converter.start(); - - const PropertyDeclaration decl = data->item->propertyDeclaration(name.toString()); - convertToPropertyType(data->item, decl, value.get(), result); - } - - if (debugProperties) - qDebug() << "[SC] cache miss " << name << ": " << resultToString(result); - if (m_valueCacheEnabled) - data->valueCache.insert(name, result); - return result; -} - -class EvaluatorScriptClassPropertyIterator : public QScriptClassPropertyIterator -{ -public: - EvaluatorScriptClassPropertyIterator(const QScriptValue &object, EvaluationData *data) - : QScriptClassPropertyIterator(object), m_it(data->item->properties()) - { - } - - bool hasNext() const override - { - return m_it.hasNext(); - } - - void next() override - { - m_it.next(); - } - - bool hasPrevious() const override - { - return m_it.hasPrevious(); - } - - void previous() override - { - m_it.previous(); - } - - void toFront() override - { - m_it.toFront(); - } - - void toBack() override - { - m_it.toBack(); - } - - QScriptString name() const override - { - return object().engine()->toStringHandle(m_it.key()); - } - -private: - QMapIterator<QString, ValuePtr> m_it; -}; - -QScriptClassPropertyIterator *EvaluatorScriptClass::newIterator(const QScriptValue &object) -{ - auto const data = attachedPointer<EvaluationData>(object); - return data ? new EvaluatorScriptClassPropertyIterator(object, data) : nullptr; -} - -void EvaluatorScriptClass::setValueCacheEnabled(bool enabled) -{ - m_valueCacheEnabled = enabled; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h deleted file mode 100755 index c234c17fa..000000000 --- a/src/lib/corelib/language/evaluatorscriptclass.h +++ /dev/null @@ -1,133 +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_EVALUATORSCRIPTCLASS_H -#define QBS_EVALUATORSCRIPTCLASS_H - -#include "forward_decls.h" -#include "qualifiedid.h" - -#include <tools/set.h> - -#include <QtScript/qscriptclass.h> - -#include <stack> - -QT_BEGIN_NAMESPACE -class QScriptContext; -QT_END_NAMESPACE - -namespace qbs { -namespace Internal { -class EvaluationData; -class Item; -class PropertyDeclaration; -class ScriptEngine; - -class EvaluatorScriptClass : public QScriptClass -{ -public: - EvaluatorScriptClass(ScriptEngine *scriptEngine); - - QueryFlags queryProperty(const QScriptValue &object, - const QScriptString &name, - QueryFlags flags, uint *id) override; - QScriptValue property(const QScriptValue &object, - const QScriptString &name, uint id) override; - QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override; - - void setValueCacheEnabled(bool enabled); - - void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, - QScriptValue &v); - - PropertyDependencies propertyDependencies() const { return m_propertyDependencies; } - void clearPropertyDependencies() { m_propertyDependencies.clear(); } - - void setPathPropertiesBaseDir(const QString &dirPath) { m_pathPropertiesBaseDir = dirPath; } - void clearPathPropertiesBaseDir() { m_pathPropertiesBaseDir.clear(); } - -private: - QueryFlags queryItemProperty(const EvaluationData *data, - const QString &name, - bool ignoreParent = false); - static QString resultToString(const QScriptValue &scriptValue); - void collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, const QString &propertyName, const ValuePtr &value); - - void convertToPropertyType(const Item *item, - const PropertyDeclaration& decl, const Value *value, - QScriptValue &v); - - struct QueryResult - { - QueryResult() - : data(nullptr), itemOfProperty(nullptr) - {} - - bool isNull() const - { - static const QueryResult pristine; - return *this == pristine; - } - - bool operator==(const QueryResult &rhs) const - { - return foundInParent == rhs.foundInParent - && data == rhs.data - && itemOfProperty == rhs.itemOfProperty - && value == rhs.value; - } - - bool foundInParent = false; - const EvaluationData *data; - const Item *itemOfProperty; // The item that owns the property. - ValuePtr value; - }; - QueryResult m_queryResult; - bool m_valueCacheEnabled; - Set<Value *> m_currentNextChain; - PropertyDependencies m_propertyDependencies; - std::stack<QualifiedId> m_requestedProperties; - QString m_pathPropertiesBaseDir; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_EVALUATORSCRIPTCLASS_H diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp index 2f23de210..e5de8f195 100644 --- a/src/lib/corelib/language/item.cpp +++ b/src/lib/corelib/language/item.cpp @@ -40,13 +40,14 @@ #include "item.h" #include "builtindeclarations.h" -#include "deprecationinfo.h" #include "filecontext.h" #include "itemobserver.h" #include "itempool.h" #include "value.h" #include <api/languageinfo.h> +#include <loader/loaderutils.h> +#include <logging/categories.h> #include <logging/logger.h> #include <logging/translator.h> #include <tools/error.h> @@ -59,27 +60,19 @@ namespace qbs { namespace Internal { -Item::Item(ItemPool *pool, ItemType type) - : m_pool(pool) - , m_observer(nullptr) - , m_prototype(nullptr) - , m_scope(nullptr) - , m_outerItem(nullptr) - , m_parent(nullptr) - , m_type(type) -{ -} - Item *Item::create(ItemPool *pool, ItemType type) { return pool->allocateItem(type); } -Item *Item::clone() const +Item *Item::clone(ItemPool &pool) const { - Item *dup = create(pool(), type()); + assertModuleLocked(); + + Item *dup = create(&pool, type()); dup->m_id = m_id; dup->m_location = m_location; + dup->m_endPosition = m_endPosition; dup->m_prototype = m_prototype; dup->m_scope = m_scope; dup->m_outerItem = m_outerItem; @@ -89,25 +82,34 @@ Item *Item::clone() const dup->m_modules = m_modules; dup->m_children.reserve(m_children.size()); - for (const Item * const child : qAsConst(m_children)) { - Item *clonedChild = child->clone(); + for (const Item * const child : std::as_const(m_children)) { + Item *clonedChild = child->clone(pool); clonedChild->m_parent = dup; dup->m_children.push_back(clonedChild); } for (PropertyMap::const_iterator it = m_properties.constBegin(); it != m_properties.constEnd(); ++it) { - dup->m_properties.insert(it.key(), it.value()->clone()); + dup->m_properties.insert(it.key(), it.value()->clone(pool)); } return dup; } +Item *Item::rootPrototype() +{ + Item *proto = this; + while (proto->prototype()) + proto = proto->prototype(); + return proto; +} + QString Item::typeName() const { switch (type()) { case ItemType::IdScope: return QStringLiteral("[IdScope]"); case ItemType::ModuleInstance: return QStringLiteral("[ModuleInstance]"); + case ItemType::ModuleInstancePlaceholder: return QStringLiteral("[ModuleInstancePlaceholder]"); case ItemType::ModuleParameters: return QStringLiteral("[ModuleParametersInstance]"); case ItemType::ModulePrefix: return QStringLiteral("[ModulePrefix]"); case ItemType::Outer: return QStringLiteral("[Outer]"); @@ -118,6 +120,7 @@ QString Item::typeName() const bool Item::hasProperty(const QString &name) const { + assertModuleLocked(); const Item *item = this; do { if (item->m_properties.contains(name)) @@ -129,15 +132,18 @@ bool Item::hasProperty(const QString &name) const bool Item::hasOwnProperty(const QString &name) const { + assertModuleLocked(); return m_properties.contains(name); } ValuePtr Item::property(const QString &name) const { + assertModuleLocked(); ValuePtr value; const Item *item = this; do { - if ((value = item->m_properties.value(name))) + value = item->m_properties.value(name); + if (value) break; item = item->m_prototype; } while (item); @@ -146,30 +152,31 @@ ValuePtr Item::property(const QString &name) const ValuePtr Item::ownProperty(const QString &name) const { + assertModuleLocked(); return m_properties.value(name); } -ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate) +ItemValuePtr Item::itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate) { - return itemProperty(name, itemTemplate, ItemValueConstPtr()); + return itemProperty(name, itemTemplate, ItemValueConstPtr(), pool); } -ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value) +ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool) { - return itemProperty(name, value->item(), value); + return itemProperty(name, value->item(), value, pool); } ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate, - const ItemValueConstPtr &itemValue) + const ItemValueConstPtr &itemValue, ItemPool &pool) { const ValuePtr v = property(name); if (v && v->type() == Value::ItemValueType) return std::static_pointer_cast<ItemValue>(v); if (!itemTemplate) - return ItemValuePtr(); + return {}; const bool createdByPropertiesBlock = itemValue && itemValue->createdByPropertiesBlock(); - const ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()), - createdByPropertiesBlock); + ItemValuePtr result = ItemValue::create(Item::create(&pool, itemTemplate->type()), + createdByPropertiesBlock); setProperty(name, result); return result; } @@ -178,7 +185,7 @@ JSSourceValuePtr Item::sourceProperty(const QString &name) const { ValuePtr v = property(name); if (!v || v->type() != Value::JSSourceValueType) - return JSSourceValuePtr(); + return {}; return std::static_pointer_cast<JSSourceValue>(v); } @@ -186,7 +193,7 @@ VariantValuePtr Item::variantProperty(const QString &name) const { ValuePtr v = property(name); if (!v || v->type() != Value::VariantValueType) - return VariantValuePtr(); + return {}; return std::static_pointer_cast<VariantValue>(v); } @@ -201,6 +208,28 @@ bool Item::isOfTypeOrhasParentOfType(ItemType type) const return false; } +void Item::addObserver(ItemObserver *observer) const +{ + // Cached Module properties never change. + if (m_type == ItemType::Module) + return; + + std::lock_guard lock(m_observersMutex); + if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) + QBS_CHECK(!contains(m_observers, observer)); + m_observers << observer; +} + +void Item::removeObserver(ItemObserver *observer) const +{ + if (m_type == ItemType::Module) + return; + std::lock_guard lock(m_observersMutex); + const auto it = std::find(m_observers.begin(), m_observers.end(), observer); + QBS_CHECK(it != m_observers.end()); + m_observers.erase(it); +} + PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExpired) const { auto it = m_propertyDeclarations.find(name); @@ -216,22 +245,32 @@ PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExp void Item::addModule(const Item::Module &module) { - const auto it = std::lower_bound(m_modules.begin(), m_modules.end(), module); - QBS_CHECK(it == m_modules.end() || (module.name != it->name && module.item != it->item)); - m_modules.insert(it, module); -} + if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) { + QBS_CHECK(none_of(m_modules, [&](const Module &m) { + if (m.name != module.name) + return false; + if (!!module.product != !!m.product) + return true; + if (!module.product) + return true; + if (module.product->multiplexConfigurationId == m.product->multiplexConfigurationId + && module.product->profileName == m.product->profileName) { + return true; + } + return false; + })); + } -void Item::setObserver(ItemObserver *observer) const -{ - QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten - m_observer = observer; + m_modules.push_back(module); } void Item::setProperty(const QString &name, const ValuePtr &value) { + assertModuleLocked(); m_properties.insert(name, value); - if (m_observer) - m_observer->onItemPropertyChanged(this); + std::lock_guard lock(m_observersMutex); + for (ItemObserver * const observer : m_observers) + observer->onItemPropertyChanged(this); } void Item::dump() const @@ -246,8 +285,14 @@ bool Item::isPresentModule() const return v && v->type() == Value::JSSourceValueType; } -void Item::setupForBuiltinType(Logger &logger) +bool Item::isFallbackModule() const { + return hasProperty(QLatin1String("__fallback")); +} + +void Item::setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger) +{ + assertModuleLocked(); const BuiltinDeclarations &builtins = BuiltinDeclarations::instance(); const auto properties = builtins.declarationsForType(type()).properties(); for (const PropertyDeclaration &pd : properties) { @@ -263,23 +308,9 @@ void Item::setupForBuiltinType(Logger &logger) ? StringConstants::undefinedValue() : pd.initialValueSource()); m_properties.insert(pd.name(), sourceValue); - } else if (pd.isDeprecated()) { - const DeprecationInfo &di = pd.deprecationInfo(); - if (di.removalVersion() <= LanguageInfo::qbsVersion()) { - QString message = Tr::tr("The property '%1' is no longer valid for %2 items. " - "It was removed in qbs %3.") - .arg(pd.name(), typeName(), di.removalVersion().toString()); - ErrorInfo error(message, value->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - throw error; - } - QString warning = Tr::tr("The property '%1' is deprecated and will be removed in " - "qbs %2.").arg(pd.name(), di.removalVersion().toString()); - ErrorInfo error(warning, value->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - logger.printWarning(error); + } else if (ErrorInfo error = pd.checkForDeprecation(deprecationMode, value->location(), + logger); error.hasError()) { + throw error; } } } @@ -289,6 +320,15 @@ void Item::copyProperty(const QString &propertyName, Item *target) const target->setProperty(propertyName, property(propertyName)); } +void Item::overrideProperties(const QVariantMap &config, const QString &key, + const SetupProjectParameters ¶meters, Logger &logger) +{ + const QVariant configMap = config.value(key); + if (configMap.isNull()) + return; + overrideProperties(configMap.toMap(), QualifiedId(key), parameters, logger); +} + static const char *valueType(const Value *v) { switch (v->type()) { @@ -326,7 +366,7 @@ void Item::dump(int indentation) const } if (!m_children.empty()) qDebug("%schildren:", indent.constData()); - for (const Item * const child : qAsConst(m_children)) + for (const Item * const child : std::as_const(m_children)) child->dump(indentation + 4); if (prototype()) { qDebug("%sprototype:", indent.constData()); @@ -334,8 +374,39 @@ void Item::dump(int indentation) const } } +void Item::lockModule() const +{ + QBS_CHECK(m_type == ItemType::Module); + m_moduleMutex.lock(); +#ifndef NDEBUG + QBS_CHECK(!m_moduleLocked); + m_moduleLocked = true; +#endif +} + +void Item::unlockModule() const +{ + QBS_CHECK(m_type == ItemType::Module); +#ifndef NDEBUG + QBS_CHECK(m_moduleLocked); + m_moduleLocked = false; +#endif + m_moduleMutex.unlock(); +} + +// This safeguard verifies that all contexts which access Module properties have really +// acquired the lock via ModuleItemLocker, as they must. +void Item::assertModuleLocked() const +{ +#ifndef NDEBUG + if (m_type == ItemType::Module) + QBS_CHECK(m_moduleLocked); +#endif +} + void Item::removeProperty(const QString &name) { + assertModuleLocked(); m_properties.remove(name); } @@ -404,10 +475,43 @@ void Item::overrideProperties( handlePropertyError(error, parameters, logger); continue; } - setProperty(it.key(), - VariantValue::create(PropertyDeclaration::convertToPropertyType( - it.value(), decl.type(), namePrefix, it.key()))); + const auto overridenValue = VariantValue::create(PropertyDeclaration::convertToPropertyType( + it.value(), decl.type(), namePrefix, it.key())); + overridenValue->markAsSetByCommandLine(); + setProperty(it.key(), overridenValue); + } +} + +void Item::setModules(const Modules &modules) +{ + m_modules = modules; +} + +Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, Item *module) +{ + qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")." + << "Creating dummy module for presence check."; + if (!module) { + module = Item::create(&pool, ItemType::ModuleInstance); + module->setFile(FileContext::create()); + module->setProperty(StringConstants::nameProperty(), VariantValue::create(name)); } + module->setType(ItemType::ModuleInstance); + module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue()); + return module; +} + +void setScopeForDescendants(Item *item, Item *scope) +{ + for (Item * const child : item->children()) { + child->setScope(scope); + setScopeForDescendants(child, scope); + } +} + +CodeRange Item::codeRange() const +{ + return {{m_location.line(), m_location.column()}, m_endPosition}; } } // namespace Internal diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h index 22cf6b810..d0dde98c4 100644 --- a/src/lib/corelib/language/item.h +++ b/src/lib/corelib/language/item.h @@ -46,12 +46,16 @@ #include "qualifiedid.h" #include <parser/qmljsmemorypool_p.h> #include <tools/codelocation.h> +#include <tools/deprecationwarningmode.h> #include <tools/error.h> #include <tools/version.h> #include <QtCore/qlist.h> #include <QtCore/qmap.h> +#include <atomic> +#include <mutex> +#include <utility> #include <vector> namespace qbs { @@ -62,55 +66,87 @@ namespace Internal { class ItemObserver; class ItemPool; class Logger; +class ModuleItemLocker; +class ProductContext; class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed { friend class ASTPropertiesItemHandler; friend class ItemPool; friend class ItemReaderASTVisitor; + friend class ModuleItemLocker; Q_DISABLE_COPY(Item) - Item(ItemPool *pool, ItemType type); + Item(ItemType type) : m_type(type) {} public: struct Module { - Module() - : item(nullptr), isProduct(false), required(true) - {} - QualifiedId name; - Item *item; - bool isProduct; - bool requiredValue = true; // base value of the required prop - bool required; - bool fallbackEnabled = true; + Item *item = nullptr; + ProductContext *product = nullptr; // Set if and only if the dep is a product. + + // All the sites that declared an explicit dependency on this module. Can contain any + // number of module instances and at most one product. + using ParametersWithPriority = std::pair<QVariantMap, int>; + struct LoadContext { + LoadContext(Item *dependsItem, + const ParametersWithPriority ¶meters) + : dependsItem(dependsItem), parameters(parameters) {} + LoadContext(Item *dependsItem, ParametersWithPriority &¶meters) + : dependsItem(dependsItem), parameters(std::move(parameters)) {} + + LoadContext(const LoadContext &) = default; + LoadContext(LoadContext &&) = default; + LoadContext &operator=(const LoadContext &) = default; + LoadContext &operator=(LoadContext &&) = default; + + Item *loadingItem() const { return dependsItem ? dependsItem->parent() : nullptr; } + Item *dependsItem; + ParametersWithPriority parameters; + }; + std::vector<LoadContext> loadContexts; + QVariantMap parameters; VersionRange versionRange; + + // The shorter this value, the "closer to the product" we consider the module, + // and the higher its precedence is when merging property values. + int maxDependsChainLength = 0; + + bool required = true; }; using Modules = std::vector<Module>; using PropertyDeclarationMap = QMap<QString, PropertyDeclaration>; using PropertyMap = QMap<QString, ValuePtr>; static Item *create(ItemPool *pool, ItemType type); - Item *clone() const; - ItemPool *pool() const { return m_pool; } + Item *clone(ItemPool &pool) const; const QString &id() const { return m_id; } const CodeLocation &location() const { return m_location; } + CodeRange codeRange() const; Item *prototype() const { return m_prototype; } + Item *rootPrototype(); Item *scope() const { return m_scope; } Item *outerItem() const { return m_outerItem; } Item *parent() const { return m_parent; } const FileContextPtr &file() const { return m_file; } const QList<Item *> &children() const { return m_children; } + QList<Item *> &children() { return m_children; } Item *child(ItemType type, bool checkForMultiple = true) const; - const PropertyMap &properties() const { return m_properties; } + const PropertyMap &properties() const { assertModuleLocked(); return m_properties; } + PropertyMap &properties() { assertModuleLocked(); return m_properties; } const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; } PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const; + + // The list of modules is ordered such that dependencies appear before the modules + // depending on them. const Modules &modules() const { return m_modules; } + Modules &modules() { return m_modules; } + void addModule(const Module &module); void removeModules() { m_modules.clear(); } - void setModules(const Modules &modules) { m_modules = modules; } + void setModules(const Modules &modules); ItemType type() const { return m_type; } void setType(ItemType type) { m_type = type; } @@ -120,18 +156,20 @@ public: bool hasOwnProperty(const QString &name) const; ValuePtr property(const QString &name) const; ValuePtr ownProperty(const QString &name) const; - ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate = nullptr); - ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value); + ItemValuePtr itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate = nullptr); + ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool); JSSourceValuePtr sourceProperty(const QString &name) const; VariantValuePtr variantProperty(const QString &name) const; bool isOfTypeOrhasParentOfType(ItemType type) const; - void setObserver(ItemObserver *observer) const; + void addObserver(ItemObserver *observer) const; + void removeObserver(ItemObserver *observer) const; void setProperty(const QString &name, const ValuePtr &value); - void setProperties(const PropertyMap &props) { m_properties = props; } + void setProperties(const PropertyMap &props) { assertModuleLocked(); m_properties = props; } void removeProperty(const QString &name); void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); void setPropertyDeclarations(const PropertyDeclarationMap &decls); void setLocation(const CodeLocation &location) { m_location = location; } + void setEndPosition(const CodePosition &position) { m_endPosition = position; } void setPrototype(Item *prototype) { m_prototype = prototype; } void setFile(const FileContextPtr &file) { m_file = file; } void setId(const QString &id) { m_id = id; } @@ -144,28 +182,39 @@ public: static void removeChild(Item *parent, Item *child); void dump() const; bool isPresentModule() const; - void setupForBuiltinType(Logger &logger); + bool isFallbackModule() const; + void setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger); void copyProperty(const QString &propertyName, Item *target) const; void overrideProperties( const QVariantMap &config, + const QString &key, + const SetupProjectParameters ¶meters, + Logger &logger); + void overrideProperties( + const QVariantMap &config, const QualifiedId &namePrefix, const SetupProjectParameters ¶meters, Logger &logger); private: ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate, - const ItemValueConstPtr &itemValue); + const ItemValueConstPtr &itemValue, ItemPool &pool); void dump(int indentation) const; - ItemPool *m_pool; - mutable ItemObserver *m_observer; + void lockModule() const; + void unlockModule() const; + void assertModuleLocked() const; + + mutable std::vector<ItemObserver *> m_observers; + mutable std::mutex m_observersMutex; QString m_id; CodeLocation m_location; - Item *m_prototype; - Item *m_scope; - Item *m_outerItem; - Item *m_parent; + CodePosition m_endPosition; + Item *m_prototype = nullptr; + Item *m_scope = nullptr; + Item *m_outerItem = nullptr; + Item *m_parent = nullptr; QList<Item *> m_children; FileContextPtr m_file; PropertyMap m_properties; @@ -173,10 +222,34 @@ private: PropertyDeclarationMap m_expiredPropertyDeclarations; Modules m_modules; ItemType m_type; + mutable std::mutex m_moduleMutex; +#ifndef NDEBUG + mutable std::atomic_bool m_moduleLocked = false; +#endif }; inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; } +Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, + Item *module); +void setScopeForDescendants(Item *item, Item *scope); + +// This mechanism is needed because Module items are shared between products (not doing so +// would be prohibitively expensive). +// The competing accesses are between +// - Attaching a temporary qbs module for evaluating the Module condition. +// - Cloning the module when creating an instance. +// - Directly accessing Module properties, which happens rarely (as opposed to properties of +// an instance). +class ModuleItemLocker +{ +public: + ModuleItemLocker(const Item &item) : m_item(item) { item.lockModule(); } + ~ModuleItemLocker() { m_item.unlockModule(); } +private: + const Item &m_item; +}; + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.cpp b/src/lib/corelib/language/itemdeclaration.cpp index d7230e9d6..eb9fd84a6 100644 --- a/src/lib/corelib/language/itemdeclaration.cpp +++ b/src/lib/corelib/language/itemdeclaration.cpp @@ -60,5 +60,11 @@ bool ItemDeclaration::isChildTypeAllowed(ItemType type) const return m_allowedChildTypes.contains(type); } +ErrorInfo ItemDeclaration::checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, Logger &logger) const +{ + return deprecationInfo().checkForDeprecation(mode, name, loc, true, logger); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.h b/src/lib/corelib/language/itemdeclaration.h index 6da699d28..1fbd7e456 100644 --- a/src/lib/corelib/language/itemdeclaration.h +++ b/src/lib/corelib/language/itemdeclaration.h @@ -71,6 +71,9 @@ public: const TypeNames &allowedChildTypes() const { return m_allowedChildTypes; } bool isChildTypeAllowed(ItemType type) const; + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, Logger &logger) const; + private: ItemType m_type; Properties m_properties; diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp index ccd22fe2e..6552f92ef 100644 --- a/src/lib/corelib/language/itempool.cpp +++ b/src/lib/corelib/language/itempool.cpp @@ -53,7 +53,7 @@ ItemPool::~ItemPool() Item *ItemPool::allocateItem(const ItemType &type) { - const auto item = new (&m_pool) Item(this, type); + const auto item = new (&m_pool) Item(type); m_items.push_back(item); return item; } diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp deleted file mode 100644 index 1abc5caf9..000000000 --- a/src/lib/corelib/language/itemreader.cpp +++ /dev/null @@ -1,147 +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 "itemreadervisitorstate.h" - -#include <tools/profiling.h> -#include <tools/stlutils.h> - -#include <QtCore/qfileinfo.h> - -#include <algorithm> - -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<ItemReaderVisitorState>(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<QStringList> &ItemReader::extraSearchPathsStack() const -{ - return m_extraSearchPaths; -} - -void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &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<QString> ItemReader::filesRead() const -{ - return m_visitorState->filesRead(); -} - -void ItemReader::setEnableTiming(bool on) -{ - m_elapsedTime = on ? 0 : -1; -} - -} // 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 3dc5329d2..000000000 --- a/src/lib/corelib/language/itemreader.h +++ /dev/null @@ -1,102 +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 "forward_decls.h" -#include <logging/logger.h> -#include <tools/set.h> - -#include <QtCore/qstringlist.h> - -#include <memory> - -namespace qbs { -namespace Internal { - -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<QStringList> &extraSearchPathsStack() const; - void setExtraSearchPathsStack(const std::vector<QStringList> &s); - void clearExtraSearchPathsStack(); - const QStringList &allSearchPaths() const; - - Item *readFile(const QString &filePath); - Item *readFile(const QString &filePath, const CodeLocation &referencingLocation); - - Set<QString> filesRead() const; - - void setEnableTiming(bool on); - qint64 elapsedTime() const { return m_elapsedTime; } - -private: - ItemPool *m_pool = nullptr; - QStringList m_searchPaths; - std::vector<QStringList> m_extraSearchPaths; - mutable QStringList m_allSearchPaths; - const std::unique_ptr<ItemReaderVisitorState> m_visitorState; - qint64 m_elapsedTime = -1; -}; - -} // 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 f22a1c4e8..000000000 --- a/src/lib/corelib/language/itemreaderastvisitor.cpp +++ /dev/null @@ -1,405 +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 <api/languageinfo.h> -#include <jsextensions/jsextensions.h> -#include <parser/qmljsast_p.h> -#include <tools/codelocation.h> -#include <tools/error.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/stringconstants.h> -#include <logging/translator.h> - -#include <algorithm> - -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<ItemValue>(v)->item() == item; - }); - if (it != srcprops.end()) - itemValue = std::static_pointer_cast<ItemValue>(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_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::ExpressionStatement *>(ast->statement); - if (Q_UNLIKELY(!expStmt)) - throw ErrorInfo(Tr::tr("id: must be followed by identifier")); - const auto * const idExp = AST::cast<AST::IdentifierExpression *>(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<AST::Block *>(statement)) - value->m_flags |= JSSourceValue::HasFunctionForm; - - 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->m_flags |= JSSourceValue::SourceUsesBase; - if (usesOuter) - value->m_flags |= JSSourceValue::SourceUsesOuter; - if (usesOriginal) - value->m_flags |= JSSourceValue::SourceUsesOriginal; - 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<ItemValue>(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<JSSourceValue>(v); - QBS_CHECK(!sv->baseValue()); - const JSSourceValuePtr baseValue = std::static_pointer_cast<JSSourceValue>(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<ItemValue>(v)->item(), - std::static_pointer_cast<const ItemValue>(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 DeprecationInfo &di = itemDecl.deprecationInfo(); - if (!di.isValid()) - return; - if (di.removalVersion() <= LanguageInfo::qbsVersion()) { - QString message = Tr::tr("The item '%1' cannot be used anymore. " - "It was removed in qbs %2.") - .arg(itemName, di.removalVersion().toString()); - ErrorInfo error(message, itemLocation); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - throw error; - } - QString warning = Tr::tr("The item '%1' is deprecated and will be removed in " - "qbs %2.").arg(itemName, di.removalVersion().toString()); - ErrorInfo error(warning, itemLocation); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - m_logger.printWarning(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 963b78471..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 <logging/logger.h> -#include <parser/qmljsastvisitor_p.h> - -#include <QtCore/qhash.h> -#include <QtCore/qstringlist.h> - -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<QStringList, QString> m_typeNameToFile; - Item *m_item = nullptr; - ItemType m_instanceItemType = ItemType::ModuleInstance; -}; - -} // 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 <logging/translator.h> -#include <parser/qmljsengine_p.h> -#include <parser/qmljslexer_p.h> -#include <parser/qmljsparser_p.h> -#include <tools/error.h> - -#include <QtCore/qshareddata.h> -#include <QtCore/qfile.h> -#include <QtCore/qfileinfo.h> -#include <QtCore/qshareddata.h> -#include <QtCore/qtextstream.h> - -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<ASTCacheValueData> d; -}; - -class ItemReaderVisitorState::ASTCache : public std::unordered_map<QString, ASTCacheValue> {}; - - -ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger) - : m_logger(logger) - , m_astCache(std::make_unique<ASTCache>()) -{ - -} - -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<QbsQmlJS::DiagnosticMessage> &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 3901be16e..000000000 --- a/src/lib/corelib/language/itemreadervisitorstate.h +++ /dev/null @@ -1,83 +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 <logging/logger.h> -#include <tools/set.h> - -#include <QtCore/qstringlist.h> - -#include <memory> - -namespace qbs { -namespace Internal { -class Item; -class ItemPool; - -class ItemReaderVisitorState -{ -public: - ItemReaderVisitorState(Logger &logger); - ~ItemReaderVisitorState(); - - Set<QString> 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); - -private: - Logger &m_logger; - Set<QString> m_filesRead; - QHash<QString, QStringList> m_directoryEntries; - Item *m_mostDerivingItem = nullptr; - - class ASTCache; - const std::unique_ptr<ASTCache> m_astCache; -}; - -} // namespace Internal -} // namespace qbs - -#endif // Include guard. diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h index 465396f45..7e9900a5b 100644 --- a/src/lib/corelib/language/itemtype.h +++ b/src/lib/corelib/language/itemtype.h @@ -74,6 +74,7 @@ enum class ItemType { // Internal items created mainly by the module loader. IdScope, ModuleInstance, + ModuleInstancePlaceholder, ModuleParameters, ModulePrefix, Outer, diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index 3db41b8c8..b3f4b2a64 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -51,6 +51,8 @@ #include <buildgraph/rulegraph.h> // TODO: Move to language? #include <buildgraph/transformer.h> #include <jsextensions/jsextensions.h> +#include <language/value.h> +#include <loader/loaderutils.h> #include <logging/categories.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> @@ -60,6 +62,7 @@ #include <tools/qbsassert.h> #include <tools/qttools.h> #include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> #include <tools/stlutils.h> #include <tools/stringconstants.h> @@ -68,8 +71,6 @@ #include <QtCore/qdiriterator.h> #include <QtCore/qmap.h> -#include <QtScript/qscriptvalue.h> - #include <algorithm> #include <memory> #include <mutex> @@ -117,6 +118,12 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const return Internal::any_of(m_importedFilesUsed, criterion); } +void Probe::restoreValues() +{ + for (auto it = m_properties.begin(), end = m_properties.end(); it != end; ++it) { + m_values[it.key()] = VariantValue::createStored(it.value()); + } +} /*! * \class SourceArtifact @@ -137,41 +144,16 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const /*! * \variable ResolvedGroup::files * \brief The files listed in the group item's "files" binding. - * Note that these do not include expanded wildcards. */ /*! * \variable ResolvedGroup::wildcards - * \brief Represents the wildcard elements in this group's "files" binding. + * \brief Represents the wildcard patterns in this group's "files" binding. * If no wildcards are specified there, this variable is null. * \sa SourceWildCards */ /*! - * \brief Returns all files specified in the group item as source artifacts. - * This includes the expanded list of wildcards. - */ -std::vector<SourceArtifactPtr> ResolvedGroup::allFiles() const -{ - std::vector<SourceArtifactPtr> lst = files; - if (wildcards) - lst << wildcards->files; - return lst; -} - -void ResolvedGroup::load(PersistentPool &pool) -{ - serializationOp<PersistentPool::Load>(pool); - if (wildcards) - wildcards->group = this; -} - -void ResolvedGroup::store(PersistentPool &pool) -{ - serializationOp<PersistentPool::Store>(pool); -} - -/*! * \class RuleArtifact * \brief The \c RuleArtifact class represents an Artifact item encountered in the context * of a Rule item. @@ -313,7 +295,7 @@ void ResolvedProduct::accept(BuildGraphVisitor *visitor) const { if (!buildData) return; - for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) + for (BuildGraphNode * const node : std::as_const(buildData->rootNodes())) node->accept(visitor); } @@ -325,7 +307,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const { std::vector<SourceArtifactPtr> lst; for (const auto &group : groups) - lst << group->allFiles(); + lst << group->files; return lst; } @@ -338,7 +320,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const std::vector<SourceArtifactPtr> lst; for (const auto &group : groups) { if (group->enabled) - lst << group->allFiles(); + lst << group->files; } return lst; } @@ -347,7 +329,7 @@ FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const { FileTags result; std::unique_ptr<int> priority; - for (const FileTaggerConstPtr &tagger : qAsConst(fileTaggers)) { + for (const FileTaggerConstPtr &tagger : std::as_const(fileTaggers)) { for (const QRegularExpression &pattern : tagger->patterns()) { if (pattern.match(fileName).hasMatch()) { if (priority) { @@ -374,6 +356,8 @@ void ResolvedProduct::load(PersistentPool &pool) rule->product = this; for (const ResolvedModulePtr &module : modules) module->product = this; + for (const auto &group: groups) + group->restoreWildcards(buildDirectory()); } void ResolvedProduct::store(PersistentPool &pool) @@ -425,18 +409,9 @@ QString ResolvedProduct::uniqueName() const return uniqueName(name, multiplexConfigurationId); } -QString ResolvedProduct::fullDisplayName(const QString &name, - const QString &multiplexConfigurationId) -{ - QString result = name; - if (!multiplexConfigurationId.isEmpty()) - result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId)); - return result; -} - QString ResolvedProduct::fullDisplayName() const { - return fullDisplayName(name, multiplexConfigurationId); + return fullProductDisplayName(name, multiplexConfigurationId); } QString ResolvedProduct::profile() const @@ -512,6 +487,19 @@ QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const return m_executablePathCache.value(origFilePath); } +void ResolvedGroup::restoreWildcards(const QString &buildDir) +{ + if (wildcards) { + wildcards->buildDir = buildDir; + wildcards->prefix = prefix; + wildcards->baseDir = FileInfo::path(location.filePath()); + for (const auto &sourceArtifact : files) { + if (sourceArtifact->fromWildcard) + wildcards->expandedFiles += sourceArtifact->absoluteFilePath; + } + } +} + ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(nullptr) { @@ -523,7 +511,7 @@ void ResolvedProject::accept(BuildGraphVisitor *visitor) const { for (const ResolvedProductPtr &product : products) product->accept(visitor); - for (const ResolvedProjectPtr &subProject : qAsConst(subProjects)) + for (const ResolvedProjectPtr &subProject : std::as_const(subProjects)) subProject->accept(visitor); } @@ -550,7 +538,7 @@ std::vector<ResolvedProjectPtr> ResolvedProject::allSubProjects() const std::vector<ResolvedProductPtr> ResolvedProject::allProducts() const { std::vector<ResolvedProductPtr> productList = products; - for (const auto &subProject : qAsConst(subProjects)) + for (const auto &subProject : std::as_const(subProjects)) productList << subProject->allProducts(); return productList; } @@ -562,11 +550,11 @@ void ResolvedProject::load(PersistentPool &pool) [](const ResolvedProductPtr &p) { if (!p->buildData) return; - for (BuildGraphNode * const node : qAsConst(p->buildData->allNodes())) { + for (BuildGraphNode * const node : std::as_const(p->buildData->allNodes())) { node->product = p; // restore parent links - for (BuildGraphNode * const child : qAsConst(node->children)) + for (BuildGraphNode * const child : std::as_const(node->children)) child->parents.insert(node); } }); @@ -592,7 +580,7 @@ TopLevelProject::~TopLevelProject() QString TopLevelProject::deriveId(const QVariantMap &config) { const QVariantMap qbsProperties = config.value(StringConstants::qbsModule()).toMap(); - const QString configurationName = qbsProperties.value( + QString configurationName = qbsProperties.value( StringConstants::configurationNameProperty()).toString(); return configurationName; } @@ -619,6 +607,16 @@ void TopLevelProject::makeModuleProvidersNonTransient() m.transientOutput = false; } +QVariantMap TopLevelProject::fullProfileConfigsTree() const +{ + QVariantMap tree; + for (auto it = profileConfigs.cbegin(); it != profileConfigs.cend(); ++it) { + tree.insert(it.key(), SetupProjectParameters::finalBuildConfigurationTree( + it.value().toMap(), overriddenValues)); + } + return tree; +} + QString TopLevelProject::buildGraphFilePath() const { return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); @@ -665,7 +663,7 @@ void TopLevelProject::store(PersistentPool &pool) void TopLevelProject::cleanupModuleProviderOutput() { QString error; - for (const ModuleProviderInfo &m : qAsConst(moduleProviderInfo.providers)) { + for (const ModuleProviderInfo &m : std::as_const(moduleProviderInfo.providers)) { if (m.transientOutput) { if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error)) qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; @@ -709,25 +707,22 @@ void TopLevelProject::cleanupModuleProviderOutput() * \brief The \c SourceArtifacts resulting from the expanded list of matching files. */ -Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, - const QString &baseDir, const QString &buildDir) +void SourceWildCards::expandPatterns() { - Set<QString> files = expandPatterns(group, patterns, baseDir, buildDir); - files -= expandPatterns(group, excludePatterns, baseDir, buildDir); - return files; + dirTimeStamps.clear(); + expandedFiles = expandPatterns(patterns) - expandPatterns(excludePatterns); } -Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, - const QStringList &patterns, const QString &baseDir, const QString &buildDir) +Set<QString> SourceWildCards::expandPatterns(const QStringList &patterns) { Set<QString> files; - QString expandedPrefix = group->prefix; + QString expandedPrefix = prefix; if (expandedPrefix.startsWith(StringConstants::tildeSlash())) expandedPrefix.replace(0, 1, QDir::homePath()); for (QString pattern : patterns) { pattern.prepend(expandedPrefix); pattern.replace(QLatin1Char('\\'), QLatin1Char('/')); - QStringList parts = pattern.split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS); + QStringList parts = pattern.split(QLatin1Char('/'), Qt::SkipEmptyParts); if (FileInfo::isAbsolute(pattern)) { QString rootDir; if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) { @@ -737,18 +732,17 @@ Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, } else { rootDir = QLatin1Char('/'); } - expandPatterns(files, group, parts, rootDir, buildDir); + expandPatterns(files, parts, rootDir); } else { - expandPatterns(files, group, parts, baseDir, buildDir); + expandPatterns(files, parts, baseDir); } } return files; } -void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &group, - const QStringList &parts, - const QString &baseDir, const QString &buildDir) +void SourceWildCards::expandPatterns(Set<QString> &result, const QStringList &parts, + const QString &baseDir) { // People might build directly in the project source directory. This is okay, since // we keep the build data in a "container" directory. However, we must make sure we don't @@ -756,8 +750,6 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & if (baseDir.startsWith(buildDir)) return; - dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified()); - QStringList changed_parts = parts; bool recursive = false; QString part = changed_parts.takeFirst(); @@ -784,8 +776,12 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & : QDir::Files | QDir::System | QDir::Dirs; // This one is needed to get symbolic links to directories - if (isDir && !FileInfo::isPattern(filePattern)) + if (FileInfo::isPattern(filePattern)) { + if (!recursive) + dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified()); + } else if (isDir) { itFilters |= QDir::Hidden; + } if (filePattern != StringConstants::dotDot() && filePattern != StringConstants::dot()) itFilters |= QDir::NoDotAndDotDot; @@ -797,16 +793,28 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & continue; // See above. if (!isDir && it.fileInfo().isDir() && !it.fileInfo().isSymLink()) continue; - if (isDir) { - expandPatterns(result, group, changed_parts, filePath, buildDir); - } else { - if (parentDir != baseDir) - dirTimeStamps.emplace_back(parentDir, FileInfo(baseDir).lastModified()); + if (isDir) + expandPatterns(result, changed_parts, filePath); + else result += QDir::cleanPath(filePath); - } } } +bool SourceWildCards::hasChangedSinceExpansion() const +{ + const bool reExpansionRequired = + Internal::any_of(dirTimeStamps, + [](const std::pair<QString, FileTime> &pair) { + return FileInfo(pair.first).lastModified() > pair.second; + }); + if (reExpansionRequired) + return true; + + auto wc = *this; + wc.expandPatterns(); + return this->expandedFiles != wc.expandedFiles; +} + template<typename L> QMap<QString, typename L::value_type> listToMap(const L &list) { @@ -915,11 +923,6 @@ bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1, return listsAreEqual(l1, l2); } -QString multiplexIdToString(const QString &id) -{ - return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8())); -} - bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b) { return equals(a.m_sharedData.get(), b.m_sharedData.get()); @@ -935,7 +938,7 @@ bool operator==(const ExportedProperty &p1, const ExportedProperty &p2) bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2) { - return d1.name == d2.name && d1.moduleProperties == d2.moduleProperties; + return d1.name == d2.name && qVariantMapsEqual(d1.moduleProperties, d2.moduleProperties); } bool equals(const std::vector<ExportedItemPtr> &l1, const std::vector<ExportedItemPtr> &l2) @@ -962,20 +965,35 @@ bool operator==(const ExportedModule &m1, const ExportedModule &m2) for (auto it1 = m1.cbegin(), it2 = m2.cbegin(); it1 != m1.cend(); ++it1, ++it2) { if (it1.key()->name != it2.key()->name) return false; - if (it1.value() != it2.value()) + if (!qVariantMapsEqual(it1.value(), it2.value())) return false; } return true; }; - return m1.propertyValues == m2.propertyValues - && m1.modulePropertyValues == m2.modulePropertyValues - && equals(m1.children, m2.children) - && m1.m_properties == m2.m_properties - && m1.importStatements == m2.importStatements - && m1.productDependencies.size() == m2.productDependencies.size() - && m1.productDependencies == m2.productDependencies - && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters); + return qVariantMapsEqual(m1.propertyValues, m2.propertyValues) + && qVariantMapsEqual(m1.modulePropertyValues, m2.modulePropertyValues) + && equals(m1.children, m2.children) && m1.m_properties == m2.m_properties + && m1.importStatements == m2.importStatements + && m1.productDependencies.size() == m2.productDependencies.size() + && m1.productDependencies == m2.productDependencies + && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters); +} + +JSValue PrivateScriptFunction::getFunction(ScriptEngine *engine, const QString &errorMessage) const +{ + if (JS_IsUndefined(scriptFunction)) { + ScopedJsValue val(engine->context(), + engine->evaluate(JsValueOwner::Caller, sourceCode(), + location().filePath(), location().line())); + if (Q_UNLIKELY(!JS_IsFunction(engine->context(), val))) + throw ErrorInfo(errorMessage, location()); + scriptFunction = val.release(); + engine->addExternallyCachedValue(&scriptFunction); + } else { + QBS_CHECK(JS_IsFunction(engine->context(), scriptFunction)); + } + return scriptFunction; } } // namespace Internal diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index 146f00f89..774d703d0 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -49,6 +49,7 @@ #include <buildgraph/forward_decls.h> #include <tools/codelocation.h> +#include <tools/fileinfo.h> #include <tools/filetime.h> #include <tools/joblimits.h> #include <tools/persistence.h> @@ -64,21 +65,18 @@ #include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptvalue.h> +#include <quickjs.h> #include <memory> #include <mutex> #include <vector> -QT_BEGIN_NAMESPACE -class QScriptEngine; -QT_END_NAMESPACE - namespace qbs { namespace Internal { class BuildGraphLocker; class BuildGraphLoader; class BuildGraphVisitor; +class ScriptEngine; class FileTagger { @@ -119,17 +117,20 @@ public: const QString &configureScript, const QVariantMap &properties, const QVariantMap &initialProperties, + const QMap<QString, VariantValuePtr> &values, const std::vector<QString> &importedFilesUsed) { return ProbeConstPtr(new Probe(globalId, location, condition, configureScript, properties, - initialProperties, importedFilesUsed)); + initialProperties, values, importedFilesUsed)); } const QString &globalId() const { return m_globalId; } bool condition() const { return m_condition; } + const CodeLocation &location() const { return m_location; } const QString &configureScript() const { return m_configureScript; } const QVariantMap &properties() const { return m_properties; } const QVariantMap &initialProperties() const { return m_initialProperties; } + const QMap<QString, VariantValuePtr> &values() const { return m_values; } const std::vector<QString> &importedFilesUsed() const { return m_importedFilesUsed; } bool needsReconfigure(const FileTime &referenceTime) const; @@ -137,6 +138,8 @@ public: { pool.serializationOp<opType>(m_globalId, m_location, m_condition, m_configureScript, m_properties, m_initialProperties, m_importedFilesUsed); + if constexpr (opType == PersistentPool::OpType::Load) + restoreValues(); } private: @@ -147,21 +150,27 @@ private: QString configureScript, QVariantMap properties, QVariantMap initialProperties, + QMap<QString, VariantValuePtr> values, std::vector<QString> importedFilesUsed) : m_globalId(std::move(globalId)) , m_location(location) , m_configureScript(std::move(configureScript)) , m_properties(std::move(properties)) , m_initialProperties(std::move(initialProperties)) + , m_values(std::move(values)) , m_importedFilesUsed(std::move(importedFilesUsed)) , m_condition(condition) - {} + { + } + + void restoreValues(); QString m_globalId; CodeLocation m_location; QString m_configureScript; QVariantMap m_properties; QVariantMap m_initialProperties; + QMap<QString, VariantValuePtr> m_values; std::vector<QString> m_importedFilesUsed; bool m_condition = false; }; @@ -233,11 +242,12 @@ public: bool overrideFileTags; QString targetOfModule; PropertyMapPtr properties; + bool fromWildcard; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { pool.serializationOp<opType>(absoluteFilePath, fileTags, overrideFileTags, properties, - targetOfModule); + targetOfModule, fromWildcard); } private: @@ -251,26 +261,28 @@ inline bool operator!=(const SourceArtifactInternal &sa1, const SourceArtifactIn class SourceWildCards { public: - Set<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir, - const QString &buildDir); + void expandPatterns(); + bool hasChangedSinceExpansion() const; + + // to be restored by the owning class + QString prefix; + QString baseDir; + QString buildDir; + Set<QString> expandedFiles; - const ResolvedGroup *group = nullptr; // The owning group. + // stored QStringList patterns; QStringList excludePatterns; std::vector<std::pair<QString, FileTime>> dirTimeStamps; - std::vector<SourceArtifactPtr> files; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { - pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps, files); + pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps); } private: - Set<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns, - const QString &baseDir, const QString &buildDir); - void expandPatterns(Set<QString> &result, const GroupConstPtr &group, - const QStringList &parts, const QString &baseDir, - const QString &buildDir); + Set<QString> expandPatterns(const QStringList &patterns); + void expandPatterns(Set<QString> &result, const QStringList &parts, const QString &baseDir); }; class QBS_AUTOTEST_EXPORT ResolvedGroup @@ -290,13 +302,9 @@ public: QString targetOfModule; bool overrideTags = false; - std::vector<SourceArtifactPtr> allFiles() const; - - void load(PersistentPool &pool); - void store(PersistentPool &pool); + void restoreWildcards(const QString &buildDir); -private: - template<PersistentPool::OpType opType> void serializationOp(PersistentPool &pool) + template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { pool.serializationOp<opType>(name, enabled, location, prefix, files, wildcards, properties, fileTags, targetOfModule, overrideTags); @@ -335,8 +343,8 @@ class PrivateScriptFunction friend bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); public: void initialize(const ScriptFunctionPtr &sharedData) { m_sharedData = sharedData; } - mutable QScriptValue scriptFunction; // not stored + JSValue getFunction(ScriptEngine *engine, const QString &errorMessage) const; QString &sourceCode() const { return m_sharedData->sourceCode; } CodeLocation &location() const { return m_sharedData->location; } ResolvedFileContextConstPtr &fileContext() const { return m_sharedData->fileContext; } @@ -349,6 +357,7 @@ public: private: ScriptFunctionPtr m_sharedData; + mutable JSValue scriptFunction = JS_UNDEFINED; // not stored }; bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); @@ -363,7 +372,7 @@ public: static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); } QString name; - QStringList moduleDependencies; + QStringList moduleDependencies; // TODO: Still needed? PrivateScriptFunction setupBuildEnvironmentScript; PrivateScriptFunction setupRunEnvironmentScript; ResolvedProduct *product = nullptr; @@ -599,7 +608,6 @@ public: static QString uniqueName(const QString &name, const QString &multiplexConfigurationId); QString uniqueName() const; - static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId); QString fullDisplayName() const; QString profile() const; @@ -695,6 +703,7 @@ public: QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()". QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults; // Results of calls to "File.directoryEntries()". QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()". + CodeLinks codeLinks; std::unique_ptr<ProjectBuildData> buildData; BuildGraphLocker *bgLocker; // This holds the system-wide build graph file lock. bool locked; // This is the API-level lock for the project instance. @@ -709,6 +718,7 @@ public: QString id() const { return m_id; } QString profile() const; void makeModuleProvidersNonTransient(); + QVariantMap fullProfileConfigsTree() const; // Tree-ified + overridden values QVariantMap profileConfigs; QVariantMap overriddenValues; @@ -725,7 +735,7 @@ private: directoryEntriesResults, fileLastModifiedResults, environment, probes, profileConfigs, overriddenValues, buildSystemFiles, lastStartResolveTime, lastEndResolveTime, warningsEncountered, - buildData, moduleProviderInfo); + buildData, moduleProviderInfo, codeLinks); } void load(PersistentPool &pool) override; void store(PersistentPool &pool) override; @@ -739,8 +749,6 @@ private: bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1, const std::vector<ArtifactPropertiesPtr> &l2); -QString multiplexIdToString(const QString &id); - } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri deleted file mode 100644 index 0b4cfbd08..000000000 --- a/src/lib/corelib/language/language.pri +++ /dev/null @@ -1,86 +0,0 @@ -include(../../../install_prefix.pri) - -HEADERS += \ - $$PWD/artifactproperties.h \ - $$PWD/astimportshandler.h \ - $$PWD/astpropertiesitemhandler.h \ - $$PWD/asttools.h \ - $$PWD/builtindeclarations.h \ - $$PWD/deprecationinfo.h \ - $$PWD/evaluationdata.h \ - $$PWD/evaluator.h \ - $$PWD/evaluatorscriptclass.h \ - $$PWD/filecontext.h \ - $$PWD/filecontextbase.h \ - $$PWD/filetags.h \ - $$PWD/forward_decls.h \ - $$PWD/identifiersearch.h \ - $$PWD/item.h \ - $$PWD/itemdeclaration.h \ - $$PWD/itemobserver.h \ - $$PWD/itempool.h \ - $$PWD/itemreader.h \ - $$PWD/itemreaderastvisitor.h \ - $$PWD/itemreadervisitorstate.h \ - $$PWD/itemtype.h \ - $$PWD/jsimports.h \ - $$PWD/language.h \ - $$PWD/loader.h \ - $$PWD/moduleloader.h \ - $$PWD/modulemerger.h \ - $$PWD/moduleproviderinfo.h \ - $$PWD/moduleproviderloader.h \ - $$PWD/preparescriptobserver.h \ - $$PWD/probesresolver.h \ - $$PWD/projectresolver.h \ - $$PWD/property.h \ - $$PWD/propertydeclaration.h \ - $$PWD/propertymapinternal.h \ - $$PWD/qualifiedid.h \ - $$PWD/resolvedfilecontext.h \ - $$PWD/scriptengine.h \ - $$PWD/scriptimporter.h \ - $$PWD/scriptpropertyobserver.h \ - $$PWD/value.h - -SOURCES += \ - $$PWD/artifactproperties.cpp \ - $$PWD/astimportshandler.cpp \ - $$PWD/astpropertiesitemhandler.cpp \ - $$PWD/asttools.cpp \ - $$PWD/builtindeclarations.cpp \ - $$PWD/evaluator.cpp \ - $$PWD/evaluatorscriptclass.cpp \ - $$PWD/filecontext.cpp \ - $$PWD/filecontextbase.cpp \ - $$PWD/filetags.cpp \ - $$PWD/identifiersearch.cpp \ - $$PWD/item.cpp \ - $$PWD/itemdeclaration.cpp \ - $$PWD/itempool.cpp \ - $$PWD/itemreader.cpp \ - $$PWD/itemreaderastvisitor.cpp \ - $$PWD/itemreadervisitorstate.cpp \ - $$PWD/language.cpp \ - $$PWD/loader.cpp \ - $$PWD/moduleloader.cpp \ - $$PWD/modulemerger.cpp \ - $$PWD/moduleproviderloader.cpp \ - $$PWD/preparescriptobserver.cpp \ - $$PWD/scriptpropertyobserver.cpp \ - $$PWD/probesresolver.cpp \ - $$PWD/projectresolver.cpp \ - $$PWD/property.cpp \ - $$PWD/propertydeclaration.cpp \ - $$PWD/propertymapinternal.cpp \ - $$PWD/qualifiedid.cpp \ - $$PWD/resolvedfilecontext.cpp \ - $$PWD/scriptengine.cpp \ - $$PWD/scriptimporter.cpp \ - $$PWD/value.cpp - -!qbs_no_dev_install { - language_headers.files = $$PWD/forward_decls.h - language_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/language - INSTALLS += language_headers -} diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp deleted file mode 100644 index 1de84da63..000000000 --- a/src/lib/corelib/language/loader.cpp +++ /dev/null @@ -1,222 +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 "language.h" -#include "moduleloader.h" -#include "projectresolver.h" -#include "scriptengine.h" - -#include <logging/translator.h> -#include <tools/fileinfo.h> -#include <tools/profile.h> -#include <tools/progressobserver.h> -#include <tools/qbsassert.h> -#include <tools/settings.h> -#include <tools/setupprojectparameters.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdir.h> -#include <QtCore/qobject.h> -#include <QtCore/qtimer.h> - -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<ProbeConstPtr> &oldProbes) -{ - m_oldProjectProbes = oldProbes; -} - -void Loader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &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->clearExceptions(); - m_engine->clearImportsCache(); - m_engine->clearRequestedProperties(); - m_engine->enableProfiling(parameters.logElapsedTime()); - m_logger.clearWarnings(); - EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation); - - QTimer cancelationTimer; - - // 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); - cancelationTimer.setSingleShot(false); - QObject::connect(&cancelationTimer, &QTimer::timeout, [this]() { - QBS_ASSERT(m_progressObserver, return); - if (m_progressObserver->canceled()) - m_engine->cancel(); - }); - cancelationTimer.start(1000); - } - - const FileTime resolveTime = FileTime::currentTime(); - Evaluator evaluator(m_engine); - ModuleLoader moduleLoader(&evaluator, m_logger); - moduleLoader.setProgressObserver(m_progressObserver); - moduleLoader.setSearchPaths(m_searchPaths); - moduleLoader.setOldProjectProbes(m_oldProjectProbes); - moduleLoader.setOldProductProbes(m_oldProductProbes); - moduleLoader.setLastResolveTime(m_lastResolveTime); - moduleLoader.setStoredProfiles(m_storedProfiles); - moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo); - const ModuleLoaderResult loadResult = moduleLoader.load(parameters); - ProjectResolver resolver(&evaluator, loadResult, std::move(parameters), m_logger); - resolver.setProgressObserver(m_progressObserver); - const 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 <logging/logger.h> -#include <tools/filetime.h> - -#include <QtCore/qstringlist.h> - -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<ProbeConstPtr> &oldProbes); - void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); - void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } - void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const StoredModuleProviderInfo &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<ProbeConstPtr> m_oldProjectProbes; - QHash<QString, std::vector<ProbeConstPtr>> 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/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp deleted file mode 100644 index 574c03bfb..000000000 --- a/src/lib/corelib/language/moduleloader.cpp +++ /dev/null @@ -1,3840 +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 "moduleloader.h" - -#include "builtindeclarations.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "itemreader.h" -#include "language.h" -#include "modulemerger.h" -#include "moduleproviderloader.h" -#include "probesresolver.h" -#include "qualifiedid.h" -#include "scriptengine.h" -#include "value.h" - -#include <api/languageinfo.h> -#include <language/language.h> -#include <logging/categories.h> -#include <logging/logger.h> -#include <logging/translator.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/preferences.h> -#include <tools/profile.h> -#include <tools/profiling.h> -#include <tools/progressobserver.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/scripttools.h> -#include <tools/settings.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdebug.h> -#include <QtCore/qdir.h> -#include <QtCore/qglobalstatic.h> -#include <QtCore/qdiriterator.h> -#include <QtCore/qjsondocument.h> -#include <QtCore/qjsonobject.h> -#include <QtCore/qtextstream.h> -#include <QtCore/qthreadstorage.h> -#include <QtScript/qscriptvalueiterator.h> - -#include <algorithm> -#include <memory> -#include <utility> - -namespace qbs { -namespace Internal { - -using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap> >; -Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById); - -static bool multiplexConfigurationIntersects(const QVariantMap &lhs, const QVariantMap &rhs) -{ - QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty()); - - for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) { - const auto rhsProperty = rhs.find(lhsProperty.key()); - const bool isCommonProperty = rhsProperty != rhs.constEnd(); - if (isCommonProperty && lhsProperty.value() != rhsProperty.value()) - return false; - } - - return true; -} - -class ModuleLoader::ItemModuleList : public QList<Item::Module> {}; - -class ModuleLoader::ProductSortByDependencies -{ -public: - ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp) - { - } - - void apply() - { - QHash<QString, std::vector<ProductContext *>> productsMap; - QList<ProductContext *> allProducts; - for (ProjectContext * const projectContext : qAsConst(m_tlp.projects)) { - for (auto &product : projectContext->products) { - allProducts.push_back(&product); - productsMap[product.name].push_back(&product); - } - } - Set<ProductContext *> allDependencies; - for (auto productContext : qAsConst(allProducts)) { - auto &productDependencies = m_dependencyMap[productContext]; - for (const auto &dep : qAsConst(productContext->info.usedProducts)) { - QBS_CHECK(!dep.name.isEmpty()); - const auto &deps = productsMap.value(dep.name); - if (dep.profile == StringConstants::star()) { - QBS_CHECK(!deps.empty()); - for (ProductContext *depProduct : deps) { - if (depProduct == productContext) - continue; - productDependencies.push_back(depProduct); - allDependencies << depProduct; - } - } else { - auto it = std::find_if(deps.begin(), deps.end(), [&dep] (ProductContext *p) { - return p->multiplexConfigurationId == dep.multiplexConfigurationId; - }); - if (it == deps.end()) { - QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty()); - const QString productName = ResolvedProduct::fullDisplayName( - productContext->name, productContext->multiplexConfigurationId); - const QString depName = ResolvedProduct::fullDisplayName( - dep.name, dep.multiplexConfigurationId); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not " - "fulfilled.").arg(productName, depName), - productContext->item->location()); - } - productDependencies.push_back(*it); - allDependencies << *it; - } - } - } - const Set<ProductContext *> rootProducts - = rangeTo<Set<ProductContext *>>(allProducts) - allDependencies; - for (ProductContext * const rootProduct : rootProducts) - traverse(rootProduct); - if (m_sortedProducts.size() < allProducts.size()) { - for (auto const product : qAsConst(allProducts)) { - QList<ModuleLoader::ProductContext *> path; - findCycle(product, path); - } - } - QBS_CHECK(m_sortedProducts.size() == allProducts.size()); - } - - // No product at position i has dependencies to a product at position j > i. - const QList<ProductContext *> &sortedProducts() const - { - return m_sortedProducts; - } - -private: - void traverse(ModuleLoader::ProductContext *product) - { - if (!m_seenProducts.insert(product).second) - return; - for (const auto &dependency : m_dependencyMap.value(product)) - traverse(dependency); - m_sortedProducts << product; - } - - void findCycle(ModuleLoader::ProductContext *product, - QList<ModuleLoader::ProductContext *> &path) - { - if (path.contains(product)) { - ErrorInfo error(Tr::tr("Cyclic dependencies detected.")); - for (const auto * const p : path) - error.append(p->name, p->item->location()); - error.append(product->name, product->item->location()); - throw error; - } - path << product; - for (auto const dep : m_dependencyMap.value(product)) - findCycle(dep, path); - path.removeLast(); - } - - TopLevelProjectContext &m_tlp; - QHash<ProductContext *, std::vector<ProductContext *>> m_dependencyMap; - Set<ProductContext *> m_seenProducts; - QList<ProductContext *> m_sortedProducts; -}; - -class SearchPathsManager { -public: - explicit SearchPathsManager(ItemReader *itemReader) - : m_itemReader(itemReader) - , m_oldSize(itemReader->extraSearchPathsStack().size()) - { - } - SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths) - : SearchPathsManager(itemReader) - { - m_itemReader->pushExtraSearchPaths(extraSearchPaths); - } - ~SearchPathsManager() - { - reset(); - } - void reset() - { - while (m_itemReader->extraSearchPathsStack().size() > m_oldSize) - m_itemReader->popExtraSearchPaths(); - } - -private: - ItemReader * const m_itemReader; - size_t m_oldSize{0}; -}; - -ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger) - : m_pool(nullptr) - , m_logger(logger) - , m_progressObserver(nullptr) - , m_reader(std::make_unique<ItemReader>(logger)) - , m_evaluator(evaluator) - , m_probesResolver(std::make_unique<ProbesResolver>(m_evaluator, m_logger)) - , m_moduleProviderLoader( - std::make_unique<ModuleProviderLoader>(m_reader.get(), m_evaluator, m_probesResolver.get(), - m_logger)) -{ -} - -ModuleLoader::~ModuleLoader() = default; - -void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) -{ - m_progressObserver = progressObserver; -} - -void ModuleLoader::setSearchPaths(const QStringList &searchPaths) -{ - m_reader->setSearchPaths(searchPaths); - qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths; -} - -void ModuleLoader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) -{ - m_probesResolver->setOldProjectProbes(oldProbes); -} - -void ModuleLoader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) -{ - m_probesResolver->setOldProductProbes(oldProbes); -} - -void ModuleLoader::setStoredProfiles(const QVariantMap &profiles) -{ - m_storedProfiles = profiles; -} - -void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo) -{ - m_moduleProviderLoader->setStoredModuleProviderInfo(moduleProviderInfo); -} - -ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) -{ - TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"), - parameters.logElapsedTime()); - qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath(); - m_parameters = parameters; - m_modulePrototypes.clear(); - m_modulePrototypeEnabledInfo.clear(); - m_parameterDeclarations.clear(); - m_disabledItems.clear(); - m_reader->clearExtraSearchPathsStack(); - m_reader->setEnableTiming(parameters.logElapsedTime()); - m_moduleProviderLoader->setProjectParameters(m_parameters); - m_probesResolver->setProjectParameters(m_parameters); - m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts - = m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies - = m_elapsedTimePropertyChecking = 0; - m_elapsedTimeModuleProviders = 0; - m_settings = std::make_unique<Settings>(parameters.settingsDirectory()); - - const auto keys = m_parameters.overriddenValues().keys(); - for (const QString &key : keys) { - static const QStringList prefixes({ StringConstants::projectPrefix(), - QStringLiteral("projects"), - QStringLiteral("products"), QStringLiteral("modules"), - StringConstants::moduleProviders(), - StringConstants::qbsModule()}); - bool ok = false; - for (const auto &prefix : prefixes) { - if (key.startsWith(prefix + QLatin1Char('.'))) { - ok = true; - break; - } - } - if (ok) { - collectNameFromOverride(key); - continue; - } - ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(key)); - e.append(Tr::tr("Please use one of the following:")); - e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>." - "<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>." - "<property-name>:value")); - handlePropertyError(e, m_parameters, m_logger); - } - - ModuleLoaderResult result; - result.profileConfigs = m_storedProfiles; - m_pool = result.itemPool.get(); - m_reader->setPool(m_pool); - - const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree() - .value(StringConstants::projectPrefix()).toMap() - .value(StringConstants::qbsSearchPathsProperty()).toStringList(); - Item *root; - { - SearchPathsManager searchPathsManager(m_reader.get(), topLevelSearchPaths); - root = loadItemFromFile(parameters.projectFilePath(), CodeLocation()); - if (!root) - return ModuleLoaderResult(); - } - - switch (root->type()) { - case ItemType::Product: - root = wrapInProjectIfNecessary(root); - break; - case ItemType::Project: - break; - default: - throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it" - " is of type '%1'.").arg(root->typeName()), root->location()); - } - - const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), - TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())); - root->setProperty(StringConstants::sourceDirectoryProperty(), - VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath())); - root->setProperty(StringConstants::buildDirectoryProperty(), - VariantValue::create(buildDirectory)); - root->setProperty(StringConstants::profileProperty(), - VariantValue::create(m_parameters.topLevelProfile())); - handleTopLevelProject(&result, root, buildDirectory, - Set<QString>() << QDir::cleanPath(parameters.projectFilePath())); - result.root = root; - result.qbsFiles = m_reader->filesRead() - m_moduleProviderLoader->tempQbsFiles(); - for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) - result.profileConfigs.remove(it.key()); - printProfilingInfo(); - return result; -} - -class PropertyDeclarationCheck : public ValueHandler -{ - const Set<Item *> &m_disabledItems; - Set<Item *> m_handledItems; - std::vector<Item *> m_parentItems; - Item *m_currentModuleInstance = nullptr; - QualifiedId m_currentModuleName; - QString m_currentName; - SetupProjectParameters m_params; - Logger &m_logger; -public: - PropertyDeclarationCheck(const Set<Item *> &disabledItems, - SetupProjectParameters params, Logger &logger) - : m_disabledItems(disabledItems) - , m_params(std::move(params)) - , m_logger(logger) - { - } - - void operator()(Item *item) - { - handleItem(item); - } - -private: - void handle(JSSourceValue *value) override - { - if (!value->createdByPropertiesBlock()) { - const ErrorInfo error(Tr::tr("Property '%1' is not declared.") - .arg(m_currentName), value->location()); - handlePropertyError(error, m_params, m_logger); - } - } - - void handle(ItemValue *value) override - { - if (checkItemValue(value)) - handleItem(value->item()); - } - - bool checkItemValue(ItemValue *value) - { - // TODO: Remove once QBS-1030 is fixed. - if (parentItem()->type() == ItemType::Artifact) - return false; - - if (parentItem()->type() == ItemType::Properties) - return false; - - if (parentItem()->isOfTypeOrhasParentOfType(ItemType::Export)) { - // Export item prototypes do not have instantiated modules. - // The module instances are where the Export is used. - QBS_ASSERT(m_currentModuleInstance, return false); - auto hasCurrentModuleName = [this](const Item::Module &m) { - return m.name == m_currentModuleName; - }; - if (any_of(m_currentModuleInstance->modules(), hasCurrentModuleName)) - return true; - } - - // TODO: We really should have a dedicated item type for "pre-instantiated" item values - // and only use ModuleInstance for actual module instances. - const bool itemIsModuleInstance = value->item()->type() == ItemType::ModuleInstance - && value->item()->hasProperty(StringConstants::presentProperty()); - - if (!itemIsModuleInstance - && value->item()->type() != ItemType::ModulePrefix - && (!parentItem()->file() || !parentItem()->file()->idScope() - || !parentItem()->file()->idScope()->hasProperty(m_currentName)) - && !value->createdByPropertiesBlock()) { - CodeLocation location = value->location(); - for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i) - location = m_parentItems.at(i)->location(); - const ErrorInfo error(Tr::tr("Item '%1' is not declared. " - "Did you forget to add a Depends item?") - .arg(m_currentModuleName.toString()), location); - handlePropertyError(error, m_params, m_logger); - return false; - } - - return true; - } - - void handleItem(Item *item) - { - if (!m_handledItems.insert(item).second) - return; - if (m_disabledItems.contains(item) - || (item->type() == ItemType::ModuleInstance && !item->isPresentModule()) - || item->type() == ItemType::Properties - - // The Properties child of a SubProject item is not a regular item. - || item->type() == ItemType::PropertiesInSubProject) { - return; - } - - // If a module was found but its validate script failed, only the canonical - // module instance will have the "non-present" flag set, so we need to locate it. - if (item->type() == ItemType::ModuleInstance) { - const Item *productItem = nullptr; - for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) { - if ((*it)->type() == ItemType::Product) { - productItem = *it; - break; - } - } - if (productItem) { - for (const Item::Module &m : productItem->modules()) { - if (m.name == m_currentModuleName) { - if (!m.item->isPresentModule()) - return; - break; - } - } - } - } - - m_parentItems.push_back(item); - for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); - it != item->properties().constEnd(); ++it) { - if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() - && it.value()->type() == Value::ItemValueType) - continue; - const PropertyDeclaration decl = item->propertyDeclaration(it.key()); - if (decl.isValid()) { - if (!decl.isDeprecated()) - continue; - const DeprecationInfo &di = decl.deprecationInfo(); - QString message; - bool warningOnly; - if (decl.isExpired()) { - message = Tr::tr("The property '%1' can no longer be used. " - "It was removed in Qbs %2.") - .arg(decl.name(), di.removalVersion().toString()); - warningOnly = false; - } else { - message = Tr::tr("The property '%1' is deprecated and will be removed " - "in Qbs %2.").arg(decl.name(), di.removalVersion().toString()); - warningOnly = true; - } - ErrorInfo error(message, it.value()->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - if (warningOnly) - m_logger.printWarning(error); - else - handlePropertyError(error, m_params, m_logger); - continue; - } - m_currentName = it.key(); - const QualifiedId oldModuleName = m_currentModuleName; - if (parentItem()->type() != ItemType::ModulePrefix) - m_currentModuleName.clear(); - m_currentModuleName.push_back(m_currentName); - it.value()->apply(this); - m_currentModuleName = oldModuleName; - } - m_parentItems.pop_back(); - for (Item * const child : item->children()) { - switch (child->type()) { - case ItemType::Export: - case ItemType::Depends: - case ItemType::Parameter: - case ItemType::Parameters: - break; - case ItemType::Group: - if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance) - break; - Q_FALLTHROUGH(); - default: - handleItem(child); - } - } - - // Properties that don't refer to an existing module with a matching Depends item - // only exist in the prototype of an Export item, not in the instance. - // Example 1 - setting a property of an unknown module: Export { abc.def: true } - // Example 2 - setting a non-existing Export property: Export { blubb: true } - if (item->type() == ItemType::ModuleInstance && item->prototype()) { - Item *oldInstance = m_currentModuleInstance; - m_currentModuleInstance = item; - handleItem(item->prototype()); - m_currentModuleInstance = oldInstance; - } - } - - void handle(VariantValue *) override { /* only created internally - no need to check */ } - - Item *parentItem() const { return m_parentItems.back(); } -}; - -void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, - const QString &buildDirectory, const Set<QString> &referencedFilePaths) -{ - TopLevelProjectContext tlp; - tlp.buildDirectory = buildDirectory; - handleProject(loadResult, &tlp, projectItem, referencedFilePaths); - checkProjectNamesInOverrides(tlp); - collectProductsByName(tlp); - checkProductNamesInOverrides(); - - adjustDependenciesForMultiplexing(tlp); - - m_dependencyResolvingPass = 1; - for (ProjectContext * const projectContext : qAsConst(tlp.projects)) { - m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack); - for (ProductContext &productContext : projectContext->products) { - try { - setupProductDependencies(&productContext, Set<DeferredDependsContext>()); - } catch (const ErrorInfo &err) { - if (productContext.name.isEmpty()) - throw err; - handleProductError(err, &productContext); - } - // extraSearchPathsStack is changed during dependency resolution, check - // that we've rolled back all the changes - QBS_CHECK(m_reader->extraSearchPathsStack() == projectContext->searchPathsStack); - } - } - if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) { - collectProductsByType(tlp); - m_dependencyResolvingPass = 2; - - // Doing the normalization for the Export items themselves (as opposed to doing it only - // for the corresponding module instances) serves two purposes: - // (1) It makes recursive use of Depends.productTypes via Export items work; otherwise, - // we'd need an additional dependency resolving pass for every export level. - // (2) The "expanded" Depends items are available to the Exporter.qbs module. - for (Item * const exportItem : m_exportsWithDeferredDependsItems) - normalizeDependencies(nullptr, DeferredDependsContext(nullptr, exportItem)); - - for (const auto &deferredDependsData : m_productsWithDeferredDependsItems) { - ProductContext * const productContext = deferredDependsData.first; - m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); - try { - setupProductDependencies(productContext, deferredDependsData.second); - } catch (const ErrorInfo &err) { - handleProductError(err, productContext); - } - } - } - - ProductSortByDependencies productSorter(tlp); - productSorter.apply(); - for (ProductContext * const p : productSorter.sortedProducts()) { - try { - handleProduct(p); - if (p->name.startsWith(StringConstants::shadowProductPrefix())) - tlp.probes << p->info.probes; - } catch (const ErrorInfo &err) { - handleProductError(err, p); - } - } - - loadResult->projectProbes = tlp.probes; - loadResult->storedModuleProviderInfo = m_moduleProviderLoader->storedModuleProviderInfo(); - - m_reader->clearExtraSearchPathsStack(); - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimePropertyChecking : nullptr); - PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger); - check(projectItem); -} - -void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, - TopLevelProjectContext *topLevelProjectContext, Item *projectItem, - const Set<QString> &referencedFilePaths) -{ - auto p = std::make_unique<ProjectContext>(); - auto &projectContext = *p; - projectContext.topLevelProject = topLevelProjectContext; - projectContext.result = loadResult; - ItemValuePtr itemValue = ItemValue::create(projectItem); - projectContext.scope = Item::create(m_pool, ItemType::Scope); - projectContext.scope->setFile(projectItem->file()); - projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); - ProductContext dummyProductContext; - dummyProductContext.project = &projectContext; - dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree(); - projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem)); - overrideItemProperties(projectItem, StringConstants::projectPrefix(), - m_parameters.overriddenValuesTree()); - projectContext.name = m_evaluator->stringValue(projectItem, - StringConstants::nameProperty()); - if (projectContext.name.isEmpty()) { - projectContext.name = FileInfo::baseName(projectItem->location().filePath()); - projectItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(projectContext.name)); - } - overrideItemProperties(projectItem, - StringConstants::projectsOverridePrefix() + projectContext.name, - m_parameters.overriddenValuesTree()); - if (!checkItemCondition(projectItem)) { - m_disabledProjects.insert(projectContext.name); - return; - } - topLevelProjectContext->projects.push_back(p.release()); - SearchPathsManager searchPathsManager(m_reader.get(), readExtraSearchPaths(projectItem) - << projectItem->file()->dirPath()); - projectContext.searchPathsStack = m_reader->extraSearchPathsStack(); - projectContext.item = projectItem; - - const QString minVersionStr - = m_evaluator->stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), - QStringLiteral("1.3.0")); - const Version minVersion = Version::fromString(minVersionStr); - if (!minVersion.isValid()) { - throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " - "is not a valid version string.").arg(minVersionStr), projectItem->location()); - } - if (!m_qbsVersion.isValid()) - m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); - if (m_qbsVersion < minVersion) { - throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " - "this is qbs version %2.").arg(minVersion.toString(), - m_qbsVersion.toString())); - } - - for (Item * const child : projectItem->children()) - child->setScope(projectContext.scope); - - m_probesResolver->resolveProbes(&dummyProductContext, projectItem); - projectContext.topLevelProject->probes << dummyProductContext.info.probes; - - handleProfileItems(projectItem, &projectContext); - - QList<Item *> multiplexedProducts; - for (Item * const child : projectItem->children()) { - if (child->type() == ItemType::Product) - multiplexedProducts << multiplexProductItem(&dummyProductContext, child); - } - for (Item * const additionalProductItem : qAsConst(multiplexedProducts)) - Item::addChild(projectItem, additionalProductItem); - - const QList<Item *> originalChildren = projectItem->children(); - for (Item * const child : originalChildren) { - switch (child->type()) { - case ItemType::Product: - prepareProduct(&projectContext, child); - break; - case ItemType::SubProject: - handleSubProject(&projectContext, child, referencedFilePaths); - break; - case ItemType::Project: - copyProperties(projectItem, child); - handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths); - break; - default: - break; - } - } - - const QStringList refs = m_evaluator->stringListValue( - projectItem, StringConstants::referencesProperty()); - const CodeLocation referencingLocation - = projectItem->property(StringConstants::referencesProperty())->location(); - QList<Item *> additionalProjectChildren; - for (const QString &filePath : refs) { - try { - additionalProjectChildren << loadReferencedFile(filePath, referencingLocation, - referencedFilePaths, dummyProductContext); - } catch (const ErrorInfo &error) { - if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } - } - for (Item * const subItem : qAsConst(additionalProjectChildren)) { - Item::addChild(projectContext.item, subItem); - switch (subItem->type()) { - case ItemType::Product: - prepareProduct(&projectContext, subItem); - break; - case ItemType::Project: - copyProperties(projectItem, subItem); - handleProject(loadResult, topLevelProjectContext, subItem, - Set<QString>(referencedFilePaths) << subItem->file()->filePath()); - break; - default: - break; - } - } -} - -QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const -{ - const auto &mprow = table.at(row); - QVariantMap multiplexConfiguration; - for (size_t column = 0; column < mprow.size(); ++column) { - const QString &propertyName = properties.at(column); - const VariantValuePtr &mpvalue = mprow.at(column); - multiplexConfiguration.insert(propertyName, mpvalue->value()); - } - QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration) - .toJson(QJsonDocument::Compact) - .toBase64()); - // Cache for later use in:multiplexIdToVariantMap() - multiplexConfigurationsById->localData().insert(id, multiplexConfiguration); - return id; -} - -QVariantMap ModuleLoader::MultiplexInfo::multiplexIdToVariantMap(const QString &multiplexId) -{ - if (multiplexId.isEmpty()) - return QVariantMap(); - - QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId); - // We assume that MultiplexInfo::toIdString() has been called for this - // particular multiplex configuration. - QBS_CHECK(!result.isEmpty()); - return result; -} - -void qbs::Internal::ModuleLoader::ModuleLoader::dump(const ModuleLoader::MultiplexInfo &mpi) -{ - QStringList header; - for (const auto &str : mpi.properties) - header << str; - qDebug() << header; - - for (const auto &row : mpi.table) { - QVariantList values; - for (const auto &elem : row) { - values << elem->value(); - } - qDebug() << values; - } -} - -ModuleLoader::MultiplexTable ModuleLoader::combine(const MultiplexTable &table, - const MultiplexRow &values) -{ - MultiplexTable result; - if (table.empty()) { - result.resize(values.size()); - for (size_t i = 0; i < values.size(); ++i) { - MultiplexRow row; - row.resize(1); - row[0] = values.at(i); - result[i] = row; - } - } else { - for (const auto &row : table) { - for (const auto &value : values) { - MultiplexRow newRow = row; - newRow.push_back(value); - result.push_back(newRow); - } - } - } - return result; -} - -ModuleLoader::MultiplexInfo ModuleLoader::extractMultiplexInfo(Item *productItem, - Item *qbsModuleItem) -{ - static const QString mpmKey = QStringLiteral("multiplexMap"); - - const QScriptValue multiplexMap = m_evaluator->value(qbsModuleItem, mpmKey); - const QStringList multiplexByQbsProperties = m_evaluator->stringListValue( - productItem, StringConstants::multiplexByQbsPropertiesProperty()); - - MultiplexInfo multiplexInfo; - multiplexInfo.aggregate = m_evaluator->boolValue( - productItem, StringConstants::aggregateProperty()); - - const QString multiplexedType = m_evaluator->stringValue( - productItem, StringConstants::multiplexedTypeProperty()); - if (!multiplexedType.isEmpty()) - multiplexInfo.multiplexedType = VariantValue::create(multiplexedType); - - Set<QString> uniqueMultiplexByQbsProperties; - for (const QString &key : multiplexByQbsProperties) { - const QString mappedKey = multiplexMap.property(key).toString(); - 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 QScriptValue arr = m_evaluator->value(qbsModuleItem, key); - if (arr.isUndefined()) - continue; - if (!arr.isArray()) - throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key)); - - const quint32 arrlen = arr.property(StringConstants::lengthProperty()).toUInt32(); - if (arrlen == 0) - continue; - - MultiplexRow mprow; - mprow.resize(arrlen); - QVariantList entriesForKey; - for (quint32 i = 0; i < arrlen; ++i) { - const QVariant value = arr.property(i).toVariant(); - if (entriesForKey.contains(value)) { - throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.") - .arg(value.toString(), key), productItem->location()); - } - entriesForKey << value; - mprow[i] = VariantValue::create(value); - } - multiplexInfo.table = combine(multiplexInfo.table, mprow); - multiplexInfo.properties.push_back(mappedKey); - } - return multiplexInfo; -} - -template <typename T, typename F> -T ModuleLoader::callWithTemporaryBaseModule(ProductContext *productContext, const F &func) -{ - // Temporarily attach the qbs module here, in case we need to access one of its properties - // to evaluate properties. - const QString &qbsKey = StringConstants::qbsModule(); - Item *productItem = productContext->item; - ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later. - if (qbsValue) - qbsValue = qbsValue->clone(); - const Item::Module qbsModule = loadBaseModule(productContext, productItem); - productItem->addModule(qbsModule); - - auto &&result = func(qbsModule); - - // "Unload" the qbs module again. - if (qbsValue) - productItem->setProperty(qbsKey, qbsValue); - else - productItem->removeProperty(qbsKey); - productItem->removeModules(); - - return std::forward<T>(result); -} - -QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem) -{ - QString productName; - dummyContext->item = productItem; - auto extractMultiplexInfoFromProduct - = [this, productItem, &productName](const Item::Module &qbsModule) { - // Overriding the product item properties must be done here already, because multiplexing - // properties might depend on product properties. - const QString &nameKey = StringConstants::nameProperty(); - productName = m_evaluator->stringValue(productItem, nameKey); - if (productName.isEmpty()) { - productName = FileInfo::completeBaseName(productItem->file()->filePath()); - productItem->setProperty(nameKey, VariantValue::create(productName)); - } - overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName, - m_parameters.overriddenValuesTree()); - - return extractMultiplexInfo(productItem, qbsModule.item); - }; - const auto multiplexInfo - = callWithTemporaryBaseModule<const MultiplexInfo>(dummyContext, - extractMultiplexInfoFromProduct); - - if (multiplexInfo.table.size() > 1) - productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue()); - - VariantValuePtr productNameValue = VariantValue::create(productName); - - Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr; - QList<Item *> additionalProductItems; - std::vector<VariantValuePtr> multiplexConfigurationIdValues; - for (size_t row = 0; row < multiplexInfo.table.size(); ++row) { - Item *item = productItem; - const auto &mprow = multiplexInfo.table.at(row); - QBS_CHECK(mprow.size() == multiplexInfo.properties.size()); - if (row > 0) { - item = productItem->clone(); - additionalProductItems.push_back(item); - } - const QString multiplexConfigurationId = multiplexInfo.toIdString(row); - const VariantValuePtr multiplexConfigurationIdValue - = VariantValue::create(multiplexConfigurationId); - if (multiplexInfo.table.size() > 1 || aggregator) { - multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue); - item->setProperty(StringConstants::multiplexConfigurationIdProperty(), - multiplexConfigurationIdValue); - } - if (multiplexInfo.multiplexedType) - item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType); - for (size_t column = 0; column < mprow.size(); ++column) { - Item *qbsItem = moduleInstanceItem(item, StringConstants::qbsModule()); - const QString &propertyName = multiplexInfo.properties.at(column); - const VariantValuePtr &mpvalue = mprow.at(column); - qbsItem->setProperty(propertyName, mpvalue); - } - } - - if (aggregator) { - additionalProductItems << aggregator; - - // Add dependencies to all multiplexed instances. - for (const auto &v : multiplexConfigurationIdValues) { - Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends); - dependsItem->setProperty(StringConstants::nameProperty(), productNameValue); - dependsItem->setProperty(StringConstants::multiplexConfigurationIdProperty(), v); - dependsItem->setProperty(StringConstants::profilesProperty(), - VariantValue::create(QStringList())); - dependsItem->setFile(aggregator->file()); - dependsItem->setupForBuiltinType(m_logger); - Item::addChild(aggregator, dependsItem); - } - } - - return additionalProductItems; -} - -void ModuleLoader::normalizeDependencies(ProductContext *product, - const DeferredDependsContext &dependsContext) -{ - std::vector<Item *> dependsItemsToAdd; - std::vector<Item *> dependsItemsToRemove; - std::vector<Item *> deferredDependsItems; - for (Item *dependsItem : dependsContext.parentItem->children()) { - if (dependsItem->type() != ItemType::Depends) - continue; - bool productTypesIsSet; - const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem, - StringConstants::productTypesProperty(), &productTypesIsSet); - if (productTypesIsSet) { - bool nameIsSet; - m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), QString(), - &nameIsSet); - - // The second condition is for the case where the dependency comes from an Export item - // that has itself been normalized in the mean time. - if (nameIsSet && !dependsItem->variantProperty(StringConstants::nameProperty())) { - throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually " - "exclusive."), dependsItem->location()); - } - - bool submodulesPropertySet; - m_evaluator->stringListValue( dependsItem, StringConstants::submodulesProperty(), - &submodulesPropertySet); - if (submodulesPropertySet) { - throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are " - "mutually exclusive."), dependsItem->location()); - } - - // We ignore the "limitToSubProject" property for dependencies from Export items, - // because we cannot make it work consistently, as the importing product is not - // yet known when normalizing via an Export item. - const bool limitToSubProject = dependsContext.parentItem->type() == ItemType::Product - && m_evaluator->boolValue(dependsItem, - StringConstants::limitToSubProjectProperty()); - static const auto hasSameSubProject - = [](const ProductContext &product, const ProductContext &other) { - for (const Item *otherParent = other.item->parent(); otherParent; - otherParent = otherParent->parent()) { - if (otherParent == product.item->parent()) - return true; - } - return false; - }; - std::vector<const ProductContext *> matchingProducts; - for (const FileTag &typeTag : productTypes) { - const auto range = m_productsByType.equal_range(typeTag); - for (auto it = range.first; it != range.second; ++it) { - if (it->second != product - && (!product || it->second->name != product->name) - && (!limitToSubProject || hasSameSubProject(*product, *it->second))) { - matchingProducts.push_back(it->second); - } - } - } - if (matchingProducts.empty()) { - qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything." - << dependsItem->location(); - dependsItemsToRemove.push_back(dependsItem); - continue; - } - if (dependsContext.parentItem->type() != ItemType::Export) - deferredDependsItems.push_back(dependsItem); - for (std::size_t i = 1; i < matchingProducts.size(); ++i) { - Item * const dependsClone = dependsItem->clone(); - dependsClone->setProperty(StringConstants::nameProperty(), - VariantValue::create(matchingProducts.at(i)->name)); - dependsItemsToAdd.push_back(dependsClone); - if (dependsContext.parentItem->type() != ItemType::Export) - deferredDependsItems.push_back(dependsClone); - - } - dependsItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(matchingProducts.front()->name)); - } - } - for (Item * const newDependsItem : dependsItemsToAdd) - Item::addChild(dependsContext.parentItem, newDependsItem); - for (Item * const dependsItem : dependsItemsToRemove) - Item::removeChild(dependsContext.parentItem, dependsItem); - if (!deferredDependsItems.empty()) { - auto &allDeferredDependsItems - = product->deferredDependsItems[dependsContext.exportingProductItem]; - allDeferredDependsItems.insert(allDeferredDependsItems.end(), deferredDependsItems.cbegin(), - deferredDependsItems.cend()); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp) -{ - for (const ProjectContext * const project : tlp.projects) { - for (const ProductContext &product : project->products) - adjustDependenciesForMultiplexing(product); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product) -{ - for (Item *dependsItem : product.item->children()) { - if (dependsItem->type() == ItemType::Depends) - adjustDependenciesForMultiplexing(product, dependsItem); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const ProductContext &product, - Item *dependsItem) -{ - const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty()); - const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty(); - if (name == product.name) { - QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator. - return; - } - - bool profilesPropertyIsSet; - const QStringList profiles = m_evaluator->stringListValue(dependsItem, - StringConstants::profilesProperty(), &profilesPropertyIsSet); - - const auto productRange = m_productsByName.equal_range(name); - if (productRange.first == productRange.second) { - // Dependency is a module. Nothing to adjust. - return; - } - - std::vector<const ProductContext *> multiplexedDependencies; - bool hasNonMultiplexedDependency = false; - for (auto it = productRange.first; it != productRange.second; ++it) { - if (!it->second->multiplexConfigurationId.isEmpty()) - multiplexedDependencies.push_back(it->second); - else - hasNonMultiplexedDependency = true; - } - bool hasMultiplexedDependencies = !multiplexedDependencies.empty(); - - // These are the allowed cases: - // (1) Normal dependency with no multiplexing whatsoever. - // (2) Both product and dependency are multiplexed. - // (2a) The profiles property is not set, we want to depend on the best - // matching variant. - // (2b) The profiles property is set, we want to depend on all variants - // with a matching profile. - // (3) The product is not multiplexed, but the dependency is. - // (3a) The profiles property is not set, the dependency has an aggregator. - // We want to depend on the aggregator. - // (3b) The profiles property is not set, the dependency does not have an - // aggregator. We want to depend on all the multiplexed variants. - // (3c) The profiles property is set, we want to depend on all variants - // with a matching profile regardless of whether an aggregator exists or not. - // (4) The product is multiplexed, but the dependency is not. We don't have to adapt - // any Depends items. - // (5) The product is a "shadow product". In that case, we know which product - // it should have a dependency on, and we make sure we depend on that. - - // (1) and (4) - if (!hasMultiplexedDependencies) - return; - - // (3a) - if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet) - return; - - QStringList multiplexIds; - const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product); - const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name; - const auto productMultiplexConfig = - MultiplexInfo::multiplexIdToVariantMap(product.multiplexConfigurationId); - - for (const ProductContext *dependency : multiplexedDependencies) { - const bool depMatchesShadowProduct = isShadowProduct - && dependency->item == product.item->parent(); - const QString depMultiplexId = dependency->multiplexConfigurationId; - if (depMatchesShadowProduct) { // (5) - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - VariantValue::create(depMultiplexId)); - return; - } - if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a - if (dependency->multiplexConfigurationId == product.multiplexConfigurationId) { - const ValuePtr &multiplexId = product.item->property( - StringConstants::multiplexConfigurationIdProperty()); - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - multiplexId); - return; - - } - // Otherwise collect partial matches and decide later - const auto dependencyMultiplexConfig = MultiplexInfo::multiplexIdToVariantMap( - dependency->multiplexConfigurationId); - - if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig)) - multiplexIds << dependency->multiplexConfigurationId; - } else { - // (2b), (3b) or (3c) - const bool profileMatch = !profilesPropertyIsSet || profiles.empty() - || profiles.contains(dependency->profileName); - if (profileMatch) - multiplexIds << depMultiplexId; - } - } - if (multiplexIds.empty()) { - const QString productName = ResolvedProduct::fullDisplayName( - product.name, product.multiplexConfigurationId); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. " - "There are no eligible multiplex candidates.").arg(productName, - name), - dependsItem->location()); - } - - // In case of (2a), at most 1 match is allowed - if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) { - const QString productName = ResolvedProduct::fullDisplayName( - product.name, product.multiplexConfigurationId); - QStringList candidateNames; - for (const auto &id : qAsConst(multiplexIds)) - candidateNames << ResolvedProduct::fullDisplayName(name, id); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. " - "Eligible multiplex candidates: %3.").arg( - productName, name, candidateNames.join(QLatin1String(", "))), - dependsItem->location()); - } - - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - VariantValue::create(multiplexIds)); -} - -void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimePrepareProducts : nullptr); - checkCancelation(); - qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); - - ProductContext productContext; - productContext.item = productItem; - productContext.project = projectContext; - productContext.name = m_evaluator->stringValue(productItem, StringConstants::nameProperty()); - QBS_CHECK(!productContext.name.isEmpty()); - const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule()); - if (!!qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { - qbsItemValue->item()->setProperty(StringConstants::nameProperty(), - VariantValue::create(StringConstants::nameProperty())); - auto evaluateQbsProfileProperty = [this](const Item::Module &qbsModule) { - return m_evaluator->stringValue(qbsModule.item, - StringConstants::profileProperty(), QString()); - }; - productContext.profileName - = callWithTemporaryBaseModule<QString>(&productContext, - evaluateQbsProfileProperty); - } else { - productContext.profileName = m_parameters.topLevelProfile(); - } - productContext.multiplexConfigurationId = m_evaluator->stringValue( - productItem, StringConstants::multiplexConfigurationIdProperty()); - QBS_CHECK(!productContext.profileName.isEmpty()); - const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName); - if (it == projectContext->result->profileConfigs.constEnd()) { - const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles); - if (!profile.exists()) { - ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()), - productItem->location()); - handleProductError(error, &productContext); - return; - } - const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( - profile, m_parameters.configurationName()); - productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( - buildConfig, m_parameters.overriddenValues()); - projectContext->result->profileConfigs.insert(productContext.profileName, - productContext.moduleProperties); - } else { - productContext.moduleProperties = it.value().toMap(); - } - initProductProperties(productContext); - - ItemValuePtr itemValue = ItemValue::create(productItem); - productContext.scope = Item::create(m_pool, ItemType::Scope); - productContext.scope->setProperty(StringConstants::productVar(), itemValue); - productContext.scope->setFile(productItem->file()); - productContext.scope->setScope(productContext.project->scope); - - const bool hasExportItems = mergeExportItems(productContext); - - setScopeForDescendants(productItem, productContext.scope); - - projectContext->products.push_back(productContext); - - if (!hasExportItems || getShadowProductInfo(productContext).first) - return; - - // This "shadow product" exists only to pull in a dependency on the actual product - // and nothing else, thus providing us with the pure environment that we need to - // evaluate the product's exported properties in isolation in the project resolver. - Item * const importer = Item::create(productItem->pool(), ItemType::Product); - importer->setProperty(QStringLiteral("name"), - VariantValue::create(StringConstants::shadowProductPrefix() - + productContext.name)); - importer->setFile(productItem->file()); - importer->setLocation(productItem->location()); - importer->setScope(projectContext->scope); - importer->setupForBuiltinType(m_logger); - Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends); - dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); - dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); - dependsItem->setFile(importer->file()); - dependsItem->setLocation(importer->location()); - dependsItem->setupForBuiltinType(m_logger); - Item::addChild(importer, dependsItem); - Item::addChild(productItem, importer); - prepareProduct(projectContext, importer); -} - -void ModuleLoader::setupProductDependencies(ProductContext *productContext, - const Set<DeferredDependsContext> &deferredDependsContext) -{ - if (m_dependencyResolvingPass == 2) { - for (const DeferredDependsContext &ctx : deferredDependsContext) - normalizeDependencies(productContext, ctx); - for (const auto &deferralData : productContext->deferredDependsItems) { - for (Item * const deferredDependsItem : deferralData.second) { - - // Dependencies from Export items are handled in addProductModuleDependencies(). - if (deferredDependsItem->parent() == productContext->item) - adjustDependenciesForMultiplexing(*productContext, deferredDependsItem); - } - } - } - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimeProductDependencies : nullptr); - checkCancelation(); - Item *item = productContext->item; - qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name - << productContext->item->location(); - - if (m_dependencyResolvingPass == 1) - setSearchPathsForProduct(productContext); - - // Module providers may push some extra search paths which we will be cleared - // by this SearchPathsManager. However, they will be also added to productContext->searchPaths - // so second pass will take that into account - SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths); - - DependsContext dependsContext; - dependsContext.product = productContext; - dependsContext.productDependencies = &productContext->info.usedProducts; - resolveDependencies(&dependsContext, item, productContext); - if (m_dependencyResolvingPass == 2 - || !containsKey(m_productsWithDeferredDependsItems, productContext)) { - addProductModuleDependencies(productContext); - } - productContext->project->result->productInfos[item] = productContext->info; -} - -// Leaf modules first. -// TODO: Can this be merged with addTransitiveDependencies? Looks suspiciously similar. -void ModuleLoader::createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules) -{ - if (std::find_if(modules.cbegin(), modules.cend(), - [parentModule](const Item::Module &m) { return m.name == parentModule.name;}) - != modules.cend()) { - return; - } - for (const Item::Module &dep : parentModule.item->modules()) - createSortedModuleList(dep, modules); - modules.push_back(parentModule); -} - -Item::Modules ModuleLoader::modulesSortedByDependency(const Item *productItem) -{ - QBS_CHECK(productItem->type() == ItemType::Product); - Item::Modules sortedModules; - const Item::Modules &unsortedModules = productItem->modules(); - for (const Item::Module &module : unsortedModules) - createSortedModuleList(module, sortedModules); - QBS_CHECK(sortedModules.size() == unsortedModules.size()); - - // Make sure the top-level items stay the same. - for (Item::Module &s : sortedModules) { - for (const Item::Module &u : unsortedModules) { - if (s.name == u.name) { - s.item = u.item; - break; - } - } - } - return sortedModules; -} - - -template<typename T> bool insertIntoSet(Set<T> &set, const T &value) -{ - const auto insertionResult = set.insert(value); - return insertionResult.second; -} - -void ModuleLoader::setupReverseModuleDependencies(const Item::Module &module, - ModuleDependencies &deps, - QualifiedIdSet &seenModules) -{ - if (!insertIntoSet(seenModules, module.name)) - return; - for (const Item::Module &m : module.item->modules()) { - deps[m.name].insert(module.name); - setupReverseModuleDependencies(m, deps, seenModules); - } -} - -ModuleLoader::ModuleDependencies ModuleLoader::setupReverseModuleDependencies(const Item *product) -{ - ModuleDependencies deps; - QualifiedIdSet seenModules; - for (const Item::Module &m : product->modules()) - setupReverseModuleDependencies(m, deps, seenModules); - return deps; -} - -void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() ? &m_elapsedTimeHandleProducts : nullptr); - if (productContext->info.delayedError.hasError()) - return; - - Item * const item = productContext->item; - - m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); - SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths); - addTransitiveDependencies(productContext); - - // It is important that dependent modules are merged after their dependency, because - // the dependent module's merger potentially needs to replace module items that were - // set by the dependency module's merger (namely, scopes of defining items; see - // ModuleMerger::replaceItemInScopes()). - Item::Modules topSortedModules = modulesSortedByDependency(item); - ModuleMerger::merge(m_logger, item, productContext->name, &topSortedModules); - - // Re-sort the modules by name. This is more stable; see QBS-818. - // The list of modules in the product now has the same order as before, - // only the items have been replaced by their merged counterparts. - Item::Modules lexicographicallySortedModules = topSortedModules; - std::sort(lexicographicallySortedModules.begin(), lexicographicallySortedModules.end()); - item->setModules(lexicographicallySortedModules); - - for (const Item::Module &module : topSortedModules) { - if (!module.item->isPresentModule()) - continue; - try { - m_probesResolver->resolveProbes(productContext, module.item); - if (module.versionRange.minimum.isValid() - || module.versionRange.maximum.isValid()) { - if (module.versionRange.maximum.isValid() - && module.versionRange.minimum >= module.versionRange.maximum) { - throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module " - "'%3'").arg(module.versionRange.minimum.toString(), - module.versionRange.maximum.toString(), - module.name.toString())); - } - const Version moduleVersion = Version::fromString( - m_evaluator->stringValue(module.item, - StringConstants::versionProperty())); - if (moduleVersion < module.versionRange.minimum) { - throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " - "at least %3.").arg(module.name.toString(), - moduleVersion.toString(), - module.versionRange.minimum.toString())); - } - if (module.versionRange.maximum.isValid() - && moduleVersion >= module.versionRange.maximum) { - throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " - "lower than %3.").arg(module.name.toString(), - moduleVersion.toString(), - module.versionRange.maximum.toString())); - } - } - } catch (const ErrorInfo &error) { - handleModuleSetupError(productContext, module, error); - if (productContext->info.delayedError.hasError()) - return; - } - } - - m_probesResolver->resolveProbes(productContext, item); - - // Module validation must happen in an extra pass, after all Probes have been resolved. - EvalCacheEnabler cacheEnabler(m_evaluator); - for (const Item::Module &module : topSortedModules) { - if (!module.item->isPresentModule()) - continue; - try { - m_evaluator->boolValue(module.item, StringConstants::validateProperty()); - for (const auto &dep : module.item->modules()) { - if (dep.requiredValue && !dep.item->isPresentModule()) { - throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not " - "loaded successfully") - .arg(module.name.toString(), dep.name.toString())); - } - } - } catch (const ErrorInfo &error) { - handleModuleSetupError(productContext, module, error); - if (productContext->info.delayedError.hasError()) - return; - } - } - - if (!checkItemCondition(item)) { - const auto &exportsData = productContext->project->topLevelProject->productModules; - for (auto it = exportsData.find(productContext->name); - it != exportsData.end() && it.key() == productContext->name; ++it) { - if (it.value().multiplexId == productContext->multiplexConfigurationId) { - createNonPresentModule(productContext->name, QStringLiteral("disabled"), - it.value().exportItem); - break; - } - } - } - - checkDependencyParameterDeclarations(productContext); - copyGroupsFromModulesToProduct(*productContext); - - ModuleDependencies reverseModuleDeps; - for (Item * const child : item->children()) { - if (child->type() == ItemType::Group) { - if (reverseModuleDeps.empty()) - reverseModuleDeps = setupReverseModuleDependencies(item); - handleGroup(productContext, child, reverseModuleDeps); - } - } - productContext->project->result->productInfos[item] = productContext->info; -} - -static Item *rootPrototype(Item *item) -{ - Item *modulePrototype = item; - while (modulePrototype->prototype()) - modulePrototype = modulePrototype->prototype(); - return modulePrototype; -} - -class DependencyParameterDeclarationCheck -{ -public: - DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem, - const QHash<const Item *, Item::PropertyDeclarationMap> &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()); - } - - auto decls = m_parameterDeclarations.value(rootPrototype(m->item)); - - if (!decls.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 *m_productItem; - const QHash<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations; -}; - -void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext *productContext) const -{ - DependencyParameterDeclarationCheck dpdc(productContext->name, productContext->item, - m_parameterDeclarations); - for (const Item::Module &dep : productContext->item->modules()) { - if (!dep.parameters.empty()) - dpdc(dep.parameters); - } -} - -void ModuleLoader::handleModuleSetupError(ModuleLoader::ProductContext *productContext, - const Item::Module &module, const ErrorInfo &error) -{ - if (module.required) { - handleProductError(error, productContext); - } else { - qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString() - << "found, but not usable in product" << productContext->name - << error.toString(); - createNonPresentModule(module.name.toString(), QStringLiteral("failed validation"), - module.item); - } -} - -void ModuleLoader::initProductProperties(const ProductContext &product) -{ - QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, - product.multiplexConfigurationId); - buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); - product.item->setProperty(StringConstants::buildDirectoryProperty(), - VariantValue::create(buildDir)); - const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); - product.item->setProperty(StringConstants::sourceDirectoryProperty(), - VariantValue::create(sourceDir)); -} - -void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem, - const Set<QString> &referencedFilePaths) -{ - qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); - - Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); - if (!checkItemCondition(projectItem)) - return; - if (propertiesItem) { - propertiesItem->setScope(projectItem); - if (!checkItemCondition(propertiesItem)) - return; - } - - Item *loadedItem; - QString subProjectFilePath; - try { - const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); - const QString relativeFilePath - = m_evaluator->stringValue(projectItem, StringConstants::filePathProperty()); - subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); - if (referencedFilePaths.contains(subProjectFilePath)) - throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") - .arg(relativeFilePath), projectItem->location()); - loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location()); - } catch (const ErrorInfo &error) { - if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - return; - } - - loadedItem = wrapInProjectIfNecessary(loadedItem); - const bool inheritProperties = m_evaluator->boolValue( - projectItem, StringConstants::inheritPropertiesProperty()); - - if (inheritProperties) - copyProperties(projectItem->parent(), loadedItem); - if (propertiesItem) { - const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); - for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin(); - it != overriddenProperties.constEnd(); ++it) { - loadedItem->setProperty(it.key(), it.value()); - } - } - - Item::addChild(projectItem, loadedItem); - projectItem->setScope(projectContext->scope); - handleProject(projectContext->result, projectContext->topLevelProject, loadedItem, - Set<QString>(referencedFilePaths) << subProjectFilePath); -} - -QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, - ModuleLoader::ProductContext &dummyContext) -{ - QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), - relativePath); - if (FileInfo(absReferencePath).isDir()) { - QString qbsFilePath; - - QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); - while (dit.hasNext()) { - if (!qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " - "qbs file.").arg(absReferencePath), referencingLocation); - } - qbsFilePath = dit.next(); - } - if (qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") - .arg(absReferencePath), referencingLocation); - } - absReferencePath = qbsFilePath; - } - if (referencedFilePaths.contains(absReferencePath)) - throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), - referencingLocation); - Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation); - if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { - ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") - .arg(subItem->typeName())); - error.append(Tr::tr("Item is defined here."), subItem->location()); - error.append(Tr::tr("File is referenced here."), referencingLocation); - throw error; - } - subItem->setScope(dummyContext.project->scope); - subItem->setParent(dummyContext.project->item); - QList<Item *> loadedItems; - loadedItems << subItem; - if (subItem->type() == ItemType::Product) { - handleProfileItems(subItem, dummyContext.project); - loadedItems << multiplexProductItem(&dummyContext, subItem); - } - return loadedItems; -} - -void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies) -{ - checkCancelation(); - propagateModulesFromParent(productContext, groupItem, reverseDepencencies); - checkItemCondition(groupItem); - for (Item * const child : groupItem->children()) { - if (child->type() == ItemType::Group) - handleGroup(productContext, child, reverseDepencencies); - } -} - -void ModuleLoader::handleAllPropertyOptionsItems(Item *item) -{ - QList<Item *> childItems = item->children(); - auto childIt = childItems.begin(); - while (childIt != childItems.end()) { - Item * const child = *childIt; - if (child->type() == ItemType::PropertyOptions) { - handlePropertyOptions(child); - childIt = childItems.erase(childIt); - } else { - handleAllPropertyOptionsItems(child); - ++childIt; - } - } - item->setChildren(childItems); -} - -void ModuleLoader::handlePropertyOptions(Item *optionsItem) -{ - const QString name = m_evaluator->stringValue(optionsItem, StringConstants::nameProperty()); - if (name.isEmpty()) { - throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"), - optionsItem->location()); - } - const QString description = m_evaluator->stringValue( - optionsItem, StringConstants::descriptionProperty()); - const auto removalVersion = Version::fromString(m_evaluator->stringValue(optionsItem, - StringConstants::removalVersionProperty())); - const auto allowedValues = m_evaluator->stringListValue( - optionsItem, StringConstants::allowedValuesProperty()); - - PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name); - if (!decl.isValid()) { - decl.setName(name); - decl.setType(PropertyDeclaration::Variant); - } - decl.setDescription(description); - if (removalVersion.isValid()) { - DeprecationInfo di(removalVersion, description); - decl.setDeprecationInfo(di); - } - decl.setAllowedValues(allowedValues); - const ValuePtr property = optionsItem->parent()->property(name); - if (!property && !decl.isExpired()) { - throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'") - .arg(name), optionsItem->location()); - } - if (property && decl.isExpired()) { - ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but " - "is still present.") - .arg(name, removalVersion.toString()), - property->location()); - e.append(Tr::tr("Removal version for '%1' specified here.").arg(name), - optionsItem->location()); - m_logger.printWarning(e); - } - optionsItem->parent()->setPropertyDeclaration(name, decl); -} - -static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) -{ - if (value->type() == Value::ItemValueType) { - const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value); - const Item * const valueItem = itemValue->item(); - Item * const subItem = dst->itemProperty(name, itemValue)->item(); - for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin(); - it != valueItem->properties().constEnd(); ++it) - mergeProperty(subItem, it.key(), it.value()); - } else { - // If the property already exists set up the base value. - if (value->type() == Value::JSSourceValueType) { - const auto jsValue = static_cast<JSSourceValue *>(value.get()); - if (jsValue->isBuiltinDefaultValue()) - return; - const ValuePtr baseValue = dst->property(name); - if (baseValue) { - QBS_CHECK(baseValue->type() == Value::JSSourceValueType); - const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>( - baseValue->clone()); - jsValue->setBaseValue(jsBaseValue); - std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives(); - jsValue->clearAlternatives(); - for (JSSourceValue::Alternative &a : alternatives) { - a.value->setBaseValue(jsBaseValue); - jsValue->addAlternative(a); - } - } - } - dst->setProperty(name, value); - } -} - -bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext) -{ - class ScopeHandler { - public: - ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) - : m_exportItem(exportItem) - { - if (!*cachedScopeItem) - *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope); - Item * const scope = *cachedScopeItem; - QBS_CHECK(productContext.item->file()); - scope->setFile(productContext.item->file()); - scope->setScope(productContext.item); - productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); - productContext.scope->copyProperty(StringConstants::productVar(), scope); - QBS_CHECK(!exportItem->scope()); - exportItem->setScope(scope); - } - ~ScopeHandler() { m_exportItem->setScope(nullptr); } - - private: - Item * const m_exportItem; - } scopeHandler(exportItem, productContext, &m_tempScopeItem); - return checkItemCondition(exportItem); -} - -void ModuleLoader::printProfilingInfo() -{ - if (!m_parameters.logElapsedTime()) - return; - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Project file loading and parsing took %1.") - .arg(elapsedTimeString(m_reader->elapsedTime())); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Preparing products took %1.") - .arg(elapsedTimeString(m_elapsedTimePrepareProducts)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Setting up product dependencies took %1.") - .arg(elapsedTimeString(m_elapsedTimeProductDependencies)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("Running module providers took %1.") - .arg(elapsedTimeString(m_elapsedTimeModuleProviders)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("Setting up transitive product dependencies took %1.") - .arg(elapsedTimeString(m_elapsedTimeTransitiveDependencies)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Handling products took %1.") - .arg(elapsedTimeString(m_elapsedTimeHandleProducts)); - m_probesResolver->printProfilingInfo(); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Property checking took %1.") - .arg(elapsedTimeString(m_elapsedTimePropertyChecking)); -} - -static void mergeParameters(QVariantMap &dst, const QVariantMap &src) -{ - for (auto it = src.begin(); it != src.end(); ++it) { - if (it.value().userType() == QMetaType::QVariantMap) { - QVariant &vdst = dst[it.key()]; - QVariantMap mdst = vdst.toMap(); - mergeParameters(mdst, it.value().toMap()); - vdst = mdst; - } else { - dst[it.key()] = it.value(); - } - } -} - -static void adjustParametersScopes(Item *item, Item *scope) -{ - if (item->type() == ItemType::ModuleParameters) { - item->setScope(scope); - return; - } - - for (const auto &value : item->properties()) { - if (value->type() != Value::ItemValueType) - continue; - adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope); - } -} - -bool ModuleLoader::mergeExportItems(const ProductContext &productContext) -{ - std::vector<Item *> exportItems; - QList<Item *> children = productContext.item->children(); - - auto isExport = [](Item *item) { return item->type() == ItemType::Export; }; - std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport); - qbs::Internal::removeIf(children, isExport); - - // Note that we do not return if there are no Export items: The "merged" item becomes the - // "product module", which always needs to exist, regardless of whether the product sources - // actually contain an Export item or not. - if (!exportItems.empty()) - productContext.item->setChildren(children); - - Item *merged = Item::create(productContext.item->pool(), ItemType::Export); - const QString &nameKey = StringConstants::nameProperty(); - const ValuePtr nameValue = VariantValue::create(productContext.name); - merged->setProperty(nameKey, nameValue); - Set<FileContextConstPtr> filesWithExportItem; - ProductModuleInfo pmi; - bool hasDependenciesOnProductType = false; - for (Item * const exportItem : qAsConst(exportItems)) { - checkCancelation(); - if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) - throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), - exportItem->location()); - exportItem->setProperty(nameKey, nameValue); - if (!checkExportItemCondition(exportItem, productContext)) - continue; - filesWithExportItem += exportItem->file(); - for (Item * const child : exportItem->children()) { - if (child->type() == ItemType::Parameters) { - adjustParametersScopes(child, child); - mergeParameters(pmi.defaultParameters, - m_evaluator->scriptValue(child).toVariant().toMap()); - } else { - if (child->type() == ItemType::Depends) { - bool productTypesIsSet; - m_evaluator->stringValue(child, StringConstants::productTypesProperty(), - QString(), &productTypesIsSet); - if (productTypesIsSet) - hasDependenciesOnProductType = true; - } - Item::addChild(merged, child); - } - } - const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); - for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { - const PropertyDeclaration &newDecl = it.value(); - const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); - if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { - ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " - "'%1' with different type.").arg(it.key()), exportItem->location()); - handlePropertyError(error, m_parameters, m_logger); - } - merged->setPropertyDeclaration(newDecl.name(), newDecl); - } - for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); - it != exportItem->properties().constEnd(); ++it) { - mergeProperty(merged, it.key(), it.value()); - } - } - merged->setFile(exportItems.empty() - ? productContext.item->file() : exportItems.back()->file()); - merged->setLocation(exportItems.empty() - ? productContext.item->location() : exportItems.back()->location()); - Item::addChild(productContext.item, merged); - merged->setupForBuiltinType(m_logger); - pmi.exportItem = merged; - pmi.multiplexId = productContext.multiplexConfigurationId; - productContext.project->topLevelProject->productModules.insert(productContext.name, pmi); - if (hasDependenciesOnProductType) - m_exportsWithDeferredDependsItems.insert(merged); - return !exportItems.empty(); -} - -Item *ModuleLoader::loadItemFromFile(const QString &filePath, - const CodeLocation &referencingLocation) -{ - Item *item = m_reader->readFile(filePath, referencingLocation); - handleAllPropertyOptionsItems(item); - return item; -} - -void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext) -{ - const std::vector<Item *> profileItems = collectProfileItems(item, projectContext); - for (Item * const profileItem : profileItems) { - try { - handleProfile(profileItem); - } catch (const ErrorInfo &e) { - handlePropertyError(e, m_parameters, m_logger); - } - } -} - -std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext) -{ - QList<Item *> childItems = item->children(); - std::vector<Item *> profileItems; - Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr; - for (auto it = childItems.begin(); it != childItems.end();) { - Item * const childItem = *it; - if (childItem->type() == ItemType::Profile) { - if (!scope) { - const ItemValuePtr itemValue = ItemValue::create(item); - scope = Item::create(m_pool, ItemType::Scope); - scope->setProperty(StringConstants::productVar(), itemValue); - scope->setFile(item->file()); - scope->setScope(projectContext->scope); - } - childItem->setScope(scope); - profileItems.push_back(childItem); - it = childItems.erase(it); - } else { - if (childItem->type() == ItemType::Product) { - for (Item * const profileItem : collectProfileItems(childItem, projectContext)) - profileItems.push_back(profileItem); - } - ++it; - } - } - if (!profileItems.empty()) - item->setChildren(childItems); - return profileItems; -} - -void ModuleLoader::evaluateProfileValues(const QualifiedId &namePrefix, Item *item, - Item *profileItem, QVariantMap &values) -{ - const Item::PropertyMap &props = item->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QualifiedId name = namePrefix; - name << it.key(); - switch (it.value()->type()) { - case Value::ItemValueType: - evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(), - profileItem, values); - break; - case Value::VariantValueType: - values.insert(name.join(QLatin1Char('.')), - std::static_pointer_cast<VariantValue>(it.value())->value()); - break; - case Value::JSSourceValueType: - item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type - if (item != profileItem) - item->setScope(profileItem); - values.insert(name.join(QLatin1Char('.')), - m_evaluator->value(item, it.key()).toVariant()); - break; - } - } -} - -void ModuleLoader::handleProfile(Item *profileItem) -{ - QVariantMap values; - evaluateProfileValues(QualifiedId(), profileItem, profileItem, values); - const bool condition = values.take(StringConstants::conditionProperty()).toBool(); - if (!condition) - return; - const QString profileName = values.take(StringConstants::nameProperty()).toString(); - if (profileName.isEmpty()) - throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location()); - if (profileName == Profile::fallbackName()) { - throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.") - .arg(profileName), profileItem->location()); - } - if (m_localProfiles.contains(profileName)) { - throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName), - profileItem->location()); - } - m_localProfiles.insert(profileName, values); -} - -void ModuleLoader::collectNameFromOverride(const QString &overrideString) -{ - static const auto extract = [](const QString &prefix, const QString &overrideString) { - if (!overrideString.startsWith(prefix)) - return QString(); - const int startPos = prefix.length(); - const int endPos = overrideString.lastIndexOf(StringConstants::dot()); - if (endPos == -1) - return QString(); - return overrideString.mid(startPos, endPos - startPos); - }; - const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString); - if (!projectName.isEmpty()) { - m_projectNamesUsedInOverrides.insert(projectName); - return; - } - const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString); - if (!productName.isEmpty()) { - m_productNamesUsedInOverrides.insert(productName.left( - productName.indexOf(StringConstants::dot()))); - return; - } -} - -void ModuleLoader::checkProjectNamesInOverrides(const ModuleLoader::TopLevelProjectContext &tlp) -{ - for (const QString &projectNameInOverride : m_projectNamesUsedInOverrides) { - if (m_disabledProjects.contains(projectNameInOverride)) - continue; - bool found = false; - for (const ProjectContext * const p : tlp.projects) { - if (p->name == projectNameInOverride) { - found = true; - break; - } - } - if (!found) { - handlePropertyError(Tr::tr("Unknown project '%1' in property override.") - .arg(projectNameInOverride), m_parameters, m_logger); - } - } -} - -void ModuleLoader::checkProductNamesInOverrides() -{ - for (const QString &productNameInOverride : m_productNamesUsedInOverrides) { - if (m_erroneousProducts.contains(productNameInOverride)) - continue; - bool found = false; - for (const auto &kv : m_productsByName) { - // In an override string such as "a.b.c:d, we cannot tell whether we have a product - // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take - // care not to emit false positives here. - if (kv.first == productNameInOverride - || kv.first.startsWith(productNameInOverride + StringConstants::dot())) { - found = true; - break; - } - } - if (!found) { - handlePropertyError(Tr::tr("Unknown product '%1' in property override.") - .arg(productNameInOverride), m_parameters, m_logger); - } - } -} - -void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *product) -{ - product->searchPaths = readExtraSearchPaths(product->item); - Settings settings(m_parameters.settingsDirectory()); - const QVariantMap profileContents = product->project->result->profileConfigs - .value(product->profileName).toMap(); - const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths(); - const QStringList ¤tSearchPaths = m_reader->allSearchPaths(); - for (const QString &p : prefsSearchPaths) { - if (!currentSearchPaths.contains(p) && FileInfo(p).exists()) - product->searchPaths << p; - } -} - -ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo( - const ModuleLoader::ProductContext &product) const -{ - const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix()); - return std::make_pair(isShadowProduct, isShadowProduct - ? product.name.mid(StringConstants::shadowProductPrefix().size()) - : QString()); -} - -void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject) -{ - for (ProjectContext * const project : topLevelProject.projects) { - for (ProductContext &product : project->products) - m_productsByName.insert({ product.name, &product }); - } -} - -void ModuleLoader::collectProductsByType(const ModuleLoader::TopLevelProjectContext &topLevelProject) -{ - for (ProjectContext * const project : topLevelProject.projects) { - for (ProductContext &product : project->products) { - try { - const FileTags productTags - = m_evaluator->fileTagsValue(product.item, StringConstants::typeProperty()); - for (const FileTag &tag : productTags) - m_productsByType.insert({ tag, &product}); - } catch (const ErrorInfo &) { - qCDebug(lcModuleLoader) << "product" << product.name << "has complex type " - " and won't get an entry in the type map"; - } - } - } -} - -void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies) -{ - QBS_CHECK(groupItem->type() == ItemType::Group); - QHash<QualifiedId, Item *> moduleInstancesForGroup; - - // Step 1: Instantiate the product's modules for the group. - for (Item::Module m : groupItem->parent()->modules()) { - Item *targetItem = moduleInstanceItem(groupItem, m.name); - targetItem->setPrototype(m.item); - - Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope); - moduleScope->setFile(groupItem->file()); - moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids - moduleScope->setScope(groupItem); - targetItem->setScope(moduleScope); - - targetItem->setFile(m.item->file()); - - // "parent" should point to the group/artifact parent - targetItem->setParent(groupItem->parent()); - - targetItem->setOuterItem(m.item); - - m.item = targetItem; - groupItem->addModule(m); - moduleInstancesForGroup.insert(m.name, targetItem); - } - - // Step 2: Make the inter-module references point to the instances created in step 1. - for (const Item::Module &module : groupItem->modules()) { - Item::Modules adaptedModules; - const Item::Modules &oldModules = module.item->prototype()->modules(); - for (Item::Module depMod : oldModules) { - depMod.item = moduleInstancesForGroup.value(depMod.name); - adaptedModules << depMod; - if (depMod.name.front() == module.name.front()) - continue; - const ItemValuePtr &modulePrefix = groupItem->itemProperty(depMod.name.front()); - QBS_CHECK(modulePrefix); - module.item->setProperty(depMod.name.front(), modulePrefix); - } - module.item->setModules(adaptedModules); - } - - const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(groupItem); - if (propsSetInGroup.empty()) - return; - productContext->info.modulePropertiesSetInGroups - .insert(std::make_pair(groupItem, propsSetInGroup)); - - // Step 3: Adapt defining items in values. This is potentially necessary if module properties - // get assigned on the group level. - for (const Item::Module &module : groupItem->modules()) { - const QualifiedIdSet &dependents = reverseDepencencies.value(module.name); - Item::Modules dependentModules; - dependentModules.reserve(int(dependents.size())); - for (const QualifiedId &depName : dependents) { - Item * const itemOfDependent = moduleInstancesForGroup.value(depName); - QBS_CHECK(itemOfDependent); - Item::Module depMod; - depMod.name = depName; - depMod.item = itemOfDependent; - dependentModules << depMod; - } - adjustDefiningItemsInGroupModuleInstances(module, dependentModules); - } -} - -static Item *createReplacementForDefiningItem(const Item *definingItem, ItemType type) -{ - Item *replacement = Item::create(definingItem->pool(), type); - replacement->setLocation(definingItem->location()); - definingItem->copyProperty(StringConstants::nameProperty(), replacement); - return replacement; -} - -void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, - const Item::Modules &dependentModules) -{ - if (!module.item->isPresentModule()) - return; - - // There are three cases: - // a) The defining item is the "main" module instance, i.e. the one instantiated in the - // product directly (or a parent group). - // b) The defining item refers to the module prototype (or the replacement of it - // created in the module merger [for products] or in this function [for parent groups]). - // c) The defining item is a different instance of the module, i.e. it was instantiated - // in some other module. - - std::unordered_map<Item *, Item *> definingItemReplacements; - - Item *modulePrototype = rootPrototype(module.item->prototype()); - QBS_CHECK(modulePrototype->type() == ItemType::Module - || modulePrototype->type() == ItemType::Export); - - const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations(); - for (const auto &decl : propDecls) { - const QString &propName = decl.name(); - - // Module properties assigned in the group are not relevant here, as nothing - // gets inherited in that case. In particular, setting a list property - // overwrites the value from the product's (or parent group's) instance completely, - // rather than appending to it (concatenation happens via outer.concat()). - ValueConstPtr propValue = module.item->ownProperty(propName); - if (propValue) - continue; - - // Find the nearest prototype instance that has the value assigned. - // The result is either an instance of a parent group (or the parent group's - // parent group and so on) or the instance of the product or the module prototype. - // In the latter case, we don't have to do anything. - const Item *instanceWithProperty = module.item; - int prototypeChainLen = 0; - do { - instanceWithProperty = instanceWithProperty->prototype(); - QBS_CHECK(instanceWithProperty); - ++prototypeChainLen; - propValue = instanceWithProperty->ownProperty(propName); - } while (!propValue); - QBS_CHECK(propValue); - - if (propValue->type() != Value::JSSourceValueType) - continue; - - bool hasDefiningItem = false; - for (ValueConstPtr v = propValue; v && !hasDefiningItem; v = v->next()) - hasDefiningItem = v->definingItem(); - if (!hasDefiningItem) - continue; - - const ValuePtr clonedValue = propValue->clone(); - for (ValuePtr v = clonedValue; v; v = v->next()) { - QBS_CHECK(v->definingItem()); - - Item *& replacement = definingItemReplacements[v->definingItem()]; - static const QString caseA = QStringLiteral("__group_case_a"); - if (v->definingItem() == instanceWithProperty - || v->definingItem()->variantProperty(caseA)) { - // Case a) - // For values whose defining item is the product's (or parent group's) instance, - // we take its scope and replace references to module instances with those from the - // group's instance. This handles cases like the following: - // Product { - // name: "theProduct" - // aModule.listProp: [name, otherModule.stringProp] - // Group { name: "theGroup"; otherModule.stringProp: name } - // ... - // } - // In the above example, aModule.listProp is set to ["theProduct", "theGroup"] - // (plus potential values from the prototype and other module instances, - // which are different Value objects in the "next chain"). - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - v->definingItem()->type()); - Item * const scope = Item::create(v->definingItem()->pool(), ItemType::Scope); - scope->setProperties(module.item->scope()->properties()); - Item * const scopeScope - = Item::create(v->definingItem()->pool(), ItemType::Scope); - scopeScope->setProperties(v->definingItem()->scope()->scope()->properties()); - scope->setScope(scopeScope); - replacement->setScope(scope); - const Item::PropertyMap &groupScopeProperties - = module.item->scope()->scope()->properties(); - for (auto propIt = groupScopeProperties.begin(); - propIt != groupScopeProperties.end(); ++propIt) { - if (propIt.value()->type() == Value::ItemValueType) - scopeScope->setProperty(propIt.key(), propIt.value()); - } - } - replacement->setPropertyDeclaration(propName, decl); - replacement->setProperty(propName, v); - replacement->setProperty(caseA, VariantValue::invalidValue()); - } else if (v->definingItem()->type() == ItemType::Module) { - // Case b) - // For values whose defining item is the module prototype, we change the scope to - // the group's instance, analogous to what we do in - // ModuleMerger::appendPrototypeValueToNextChain(). - QBS_CHECK(!decl.isScalar()); - QBS_CHECK(!v->next()); - Item *& replacement = definingItemReplacements[v->definingItem()]; - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - ItemType::Module); - replacement->setScope(module.item); - } - QBS_CHECK(!replacement->hasOwnProperty(caseA)); - qCDebug(lcModuleLoader).noquote().nospace() - << "replacing defining item for prototype; module is " - << module.name.toString() << module.item - << ", property is " << propName - << ", old defining item was " << v->definingItem() - << " with scope" << v->definingItem()->scope() - << ", new defining item is" << replacement - << " with scope" << replacement->scope(); - if (v->type() == Value::JSSourceValueType) { - qCDebug(lcModuleLoader) << "value source code is" - << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString(); - } - replacement->setPropertyDeclaration(propName, decl); - replacement->setProperty(propName, v); - } else { - // Look for instance scopes of other module instances in defining items and - // replace the affected values. - // This is case c) as introduced above. See ModuleMerger::replaceItemInScopes() - // for a detailed explanation. - - QBS_CHECK(v->definingItem()->scope() && v->definingItem()->scope()->scope()); - bool found = false; - for (const Item::Module &depMod : dependentModules) { - const Item *depModPrototype = depMod.item->prototype(); - for (int i = 1; i < prototypeChainLen; ++i) - depModPrototype = depModPrototype->prototype(); - if (v->definingItem()->scope()->scope() != depModPrototype) - continue; - - found = true; - Item *& replacement = definingItemReplacements[v->definingItem()]; - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - v->definingItem()->type()); - replacement->setProperties(v->definingItem()->properties()); - for (const auto &decl : v->definingItem()->propertyDeclarations()) - replacement->setPropertyDeclaration(decl.name(), decl); - replacement->setPrototype(v->definingItem()->prototype()); - replacement->setScope(Item::create(v->definingItem()->pool(), - ItemType::Scope)); - replacement->scope()->setScope(depMod.item); - } - QBS_CHECK(!replacement->hasOwnProperty(caseA)); - qCDebug(lcModuleLoader) << "reset instance scope of module" - << depMod.name.toString() << "in property" - << propName << "of module" << module.name; - } - QBS_CHECK(found); - } - QBS_CHECK(replacement); - v->setDefiningItem(replacement); - } - module.item->setProperty(propName, clonedValue); - } -} - -void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item, - ProductContext *productContext) -{ - QBS_CHECK(m_dependencyResolvingPass == 1 || m_dependencyResolvingPass == 2); - - if (!productContext || m_dependencyResolvingPass == 1) { - const Item::Module baseModule = loadBaseModule(dependsContext->product, item); - item->addModule(baseModule); - } - - // Resolve all Depends items. - ItemModuleList loadedModules; - QList<Item *> dependsItemPerLoadedModule; - ProductDependencies productDependencies; - const auto handleDependsItem = [&](Item *child) { - if (child->type() != ItemType::Depends) - return; - - int lastModulesCount = loadedModules.size(); - try { - resolveDependsItem(dependsContext, child->parent(), child, &loadedModules, - &productDependencies); - } catch (const ErrorInfo &e) { - if (!productContext) - throw; - handleProductError(e, productContext); - } - for (int i = lastModulesCount; i < loadedModules.size(); ++i) - dependsItemPerLoadedModule.push_back(child); - - }; - if (productContext && m_dependencyResolvingPass == 2) { - for (const auto &deferData : productContext->deferredDependsItems) { - dependsContext->exportingProductItem = deferData.first; - for (Item * const dependsItem : deferData.second) - handleDependsItem(dependsItem); - } - } else { - for (Item * const child : item->children()) - handleDependsItem(child); - } - QBS_CHECK(loadedModules.size() == dependsItemPerLoadedModule.size()); - - Item *lastDependsItem = nullptr; - for (Item * const dependsItem : dependsItemPerLoadedModule) { - if (dependsItem == lastDependsItem) - continue; - adjustParametersScopes(dependsItem, dependsItem); - forwardParameterDeclarations(dependsItem, loadedModules); - lastDependsItem = dependsItem; - } - - for (int i = 0; i < loadedModules.size(); ++i) { - Item::Module &module = loadedModules[i]; - mergeParameters(module.parameters, extractParameters(dependsItemPerLoadedModule.at(i))); - item->addModule(module); - - const QString moduleName = module.name.toString(); - std::for_each(productDependencies.begin(), productDependencies.end(), - [&module, &moduleName] (ModuleLoaderResult::ProductInfo::Dependency &dep) { - if (dep.name == moduleName) - dep.parameters = module.parameters; - }); - } - - dependsContext->productDependencies->insert( - dependsContext->productDependencies->end(), - productDependencies.cbegin(), productDependencies.cend()); -} - -class RequiredChainManager -{ -public: - RequiredChainManager(std::vector<bool> &requiredChain, bool required) - : m_requiredChain(requiredChain) - { - m_requiredChain.push_back(required); - } - - ~RequiredChainManager() { m_requiredChain.pop_back(); } - -private: - std::vector<bool> &m_requiredChain; -}; - -void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem, - Item *dependsItem, ItemModuleList *moduleResults, - ProductDependencies *productResults) -{ - checkCancelation(); - if (!checkItemCondition(dependsItem)) { - qCDebug(lcModuleLoader) << "Depends item disabled, ignoring."; - return; - } - bool nameIsSet; - const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), - QString(), &nameIsSet); - bool submodulesPropertySet; - const QStringList submodules = m_evaluator->stringListValue( - dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet); - if (submodules.empty() && submodulesPropertySet) { - qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list."; - return; - } - if (Q_UNLIKELY(submodules.size() > 1 && !dependsItem->id().isEmpty())) { - QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); - throw ErrorInfo(msg, dependsItem->location()); - } - const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled() - && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty()) - ? FallbackMode::Enabled : FallbackMode::Disabled; - - QList<QualifiedId> moduleNames; - const QualifiedId nameParts = QualifiedId::fromString(name); - if (submodules.empty()) { - // Ignore explicit dependencies on the base module, which has already been loaded. - if (name == StringConstants::qbsModule()) - return; - - moduleNames << nameParts; - } else { - for (const QString &submodule : submodules) - moduleNames << nameParts + QualifiedId::fromString(submodule); - } - - Item::Module result; - bool productTypesIsSet; - m_evaluator->stringValue(dependsItem, StringConstants::productTypesProperty(), - QString(), &productTypesIsSet); - if (m_dependencyResolvingPass == 1 && productTypesIsSet) { - qCDebug(lcModuleLoader) << "queuing product" << dependsContext->product->name - << "for a second dependencies resolving pass"; - m_productsWithDeferredDependsItems[dependsContext->product].insert( - DeferredDependsContext(dependsContext->exportingProductItem, parentItem)); - return; - } - - const bool isRequiredValue = - m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty()); - const bool isRequired = !productTypesIsSet - && isRequiredValue - && !contains(m_requiredChain, false); - const Version minVersion = Version::fromString( - m_evaluator->stringValue(dependsItem, - StringConstants::versionAtLeastProperty())); - const Version maxVersion = Version::fromString( - m_evaluator->stringValue(dependsItem, StringConstants::versionBelowProperty())); - const VersionRange versionRange(minVersion, maxVersion); - QStringList multiplexConfigurationIds = m_evaluator->stringListValue( - dependsItem, - StringConstants::multiplexConfigurationIdsProperty()); - if (multiplexConfigurationIds.empty()) - multiplexConfigurationIds << QString(); - - for (const QualifiedId &moduleName : qAsConst(moduleNames)) { - // Don't load the same module twice. Duplicate Depends statements can easily - // happen due to inheritance. - const auto it = std::find_if(moduleResults->begin(), moduleResults->end(), - [moduleName](const Item::Module &m) { return m.name == moduleName; }); - if (it != moduleResults->end()) { - it->required = it->required || isRequired; - it->requiredValue = it->requiredValue || isRequiredValue; - it->fallbackEnabled = it->fallbackEnabled && fallbackMode == FallbackMode::Enabled; - it->versionRange.narrowDown(versionRange); - continue; - } - - QVariantMap defaultParameters; - Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem, - parentItem, dependsItem->location(), dependsItem->id(), - moduleName, multiplexConfigurationIds.first(), fallbackMode, - isRequired, &result.isProduct, &defaultParameters); - if (!moduleItem) { - const QString productName = ResolvedProduct::fullDisplayName( - dependsContext->product->name, - dependsContext->product->multiplexConfigurationId); - if (!multiplexConfigurationIds.first().isEmpty()) { - const QString depName = ResolvedProduct::fullDisplayName( - moduleName.toString(), multiplexConfigurationIds.first()); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not " - "fulfilled.").arg(productName, depName)); - } - ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.") - .arg(moduleName.toString(), productName), dependsItem->location()); - throw e; - } - if (result.isProduct && !m_dependsChain.empty() && !m_dependsChain.back().isProduct) { - throw ErrorInfo(Tr::tr("Invalid dependency on product '%1': Modules cannot depend on " - "products. You may want to turn your module into a product and " - "add the dependency in that product's Export item.") - .arg(moduleName.toString()), dependsItem->location()); - } - qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString(); - result.name = moduleName; - result.item = moduleItem; - result.requiredValue = isRequiredValue; - result.required = isRequired; - result.fallbackEnabled = fallbackMode == FallbackMode::Enabled; - result.parameters = defaultParameters; - result.versionRange = versionRange; - moduleResults->push_back(result); - if (result.isProduct) { - qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString(); - bool profilesPropertyWasSet = false; - QStringList profiles = m_evaluator->stringListValue(dependsItem, - StringConstants::profilesProperty(), - &profilesPropertyWasSet); - if (profiles.empty()) { - if (profilesPropertyWasSet) - profiles.push_back(StringConstants::star()); - else - profiles.push_back(QString()); - } - for (const QString &profile : qAsConst(profiles)) { - for (const QString &multiplexId : qAsConst(multiplexConfigurationIds)) { - ModuleLoaderResult::ProductInfo::Dependency dependency; - dependency.name = moduleName.toString(); - dependency.profile = profile; - dependency.multiplexConfigurationId = multiplexId; - dependency.isRequired = isRequired; - productResults->push_back(dependency); - } - } - } - } -} - -void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem, - const ItemModuleList &modules) -{ - for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - forwardParameterDeclarations(it.key(), - std::static_pointer_cast<ItemValue>(it.value())->item(), - modules); - } -} - -void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, - const ItemModuleList &modules) -{ - auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) { - return m.name == moduleName; - }); - if (it != modules.end()) { - item->setPropertyDeclarations(m_parameterDeclarations.value(rootPrototype(it->item))); - } 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<ItemValue>(it.value())->item(), - modules); - } - } -} - -void ModuleLoader::resolveParameterDeclarations(const Item *module) -{ - Item::PropertyDeclarationMap decls; - const auto &moduleChildren = module->children(); - for (Item *param : moduleChildren) { - if (param->type() != ItemType::Parameter) - continue; - const auto ¶mDecls = param->propertyDeclarations(); - for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) - decls.insert(it.key(), it.value()); - } - m_parameterDeclarations.insert(module, decls); -} - -static bool isItemValue(const ValuePtr &v) -{ - return v->type() == Value::ItemValueType; -} - -static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties) -{ - Item::PropertyMap result; - auto itEnd = properties.end(); - for (auto it = properties.begin(); it != itEnd; ++it) { - if (isItemValue(it.value())) - result.insert(it.key(), it.value()); - } - return result; -} - -static QVariantMap safeToVariant(const QScriptValue &v) -{ - QVariantMap result; - QScriptValueIterator it(v); - while (it.hasNext()) { - it.next(); - QScriptValue u = it.value(); - if (u.isError()) - throw ErrorInfo(u.toString()); - result[it.name()] = (u.isObject() && !u.isArray() && !u.isRegExp()) - ? safeToVariant(u) : it.value().toVariant(); - } - return result; -} - -QVariantMap ModuleLoader::extractParameters(Item *dependsItem) const -{ - QVariantMap result; - const Item::PropertyMap &itemProperties = filterItemProperties( - rootPrototype(dependsItem)->properties()); - if (itemProperties.empty()) - return result; - - auto origProperties = dependsItem->properties(); - dependsItem->setProperties(itemProperties); - QScriptValue sv = m_evaluator->scriptValue(dependsItem); - try { - result = safeToVariant(sv); - } catch (const ErrorInfo &exception) { - auto ei = exception; - ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location()); - throw ei; - } - dependsItem->setProperties(origProperties); - return result; -} - -[[noreturn]] static void throwModuleNamePrefixError(const QualifiedId &shortName, - const QualifiedId &longName, const CodeLocation &codeLocation) -{ - throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the " - "name of module '%2', which is not allowed") - .arg(shortName.toString(), longName.toString()), codeLocation); -} - -Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName) -{ - QBS_CHECK(!moduleName.empty()); - Item *instance = containerItem; - for (int i = 0; i < moduleName.size(); ++i) { - const QString &moduleNameSegment = moduleName.at(i); - const ValuePtr v = instance->ownProperty(moduleName.at(i)); - if (v && v->type() == Value::ItemValueType) { - instance = std::static_pointer_cast<ItemValue>(v)->item(); - } else { - const ItemType itemType = i < moduleName.size() - 1 ? ItemType::ModulePrefix - : ItemType::ModuleInstance; - auto newItem = Item::create(m_pool, itemType); - instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); - instance = newItem; - } - if (i < moduleName.size() - 1) { - if (instance->type() == ItemType::ModuleInstance) { - QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1)); - throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation()); - } - QBS_CHECK(instance->type() == ItemType::ModulePrefix); - } - } - QBS_CHECK(instance != containerItem); - return instance; -} - -ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext, - const QString &name, const QString &multiplexId, bool &productNameMatch) -{ - auto &exportsData = productContext->project->topLevelProject->productModules; - const auto firstIt = exportsData.find(name); - productNameMatch = firstIt != exportsData.end(); - for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) { - if (it.value().multiplexId == multiplexId) - return &it.value(); - } - if (multiplexId.isEmpty() && firstIt != exportsData.end()) - return &firstIt.value(); - return nullptr; -} - -ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext, - const QString &name) -{ - auto itEnd = projectContext->products.end(); - auto it = std::find_if(projectContext->products.begin(), itEnd, - [&name] (const ProductContext &ctx) { - return ctx.name == name; - }); - return it == itEnd ? nullptr : &*it; -} - -ModuleLoader::ProductContext *ModuleLoader::product(TopLevelProjectContext *tlpContext, - const QString &name) -{ - ProductContext *result = nullptr; - for (auto prj : tlpContext->projects) { - result = product(prj, name); - if (result) - break; - } - return result; -} - -class ModuleLoader::DependsChainManager -{ -public: - DependsChainManager(std::vector<DependsChainEntry> &dependsChain, const QualifiedId &module, - const CodeLocation &dependsLocation) - : m_dependsChain(dependsChain) - { - const bool alreadyInChain = Internal::any_of(dependsChain, - [&module](const DependsChainEntry &e) { - return e.name == module; - }); - if (alreadyInChain) { - ErrorInfo error; - error.append(Tr::tr("Cyclic dependencies detected:")); - for (const DependsChainEntry &e : qAsConst(m_dependsChain)) - error.append(e.name.toString(), e.location); - error.append(module.toString(), dependsLocation); - throw error; - } - m_dependsChain.emplace_back(module, dependsLocation); - } - - ~DependsChainManager() { m_dependsChain.pop_back(); } - -private: - std::vector<DependsChainEntry> &m_dependsChain; -}; - -static bool isBaseModule(QStringView fullModuleName) -{ - return fullModuleName == StringConstants::qbsModule(); -} - -class DelayedPropertyChanger -{ -public: - ~DelayedPropertyChanger() - { - applyNow(); - } - - void setLater(Item *item, const QString &name, const ValuePtr &value) - { - QBS_CHECK(m_item == nullptr); - m_item = item; - m_name = name; - m_value = value; - } - - void removeLater(Item *item, const QString &name) - { - QBS_CHECK(m_item == nullptr); - m_item = item; - m_name = name; - } - - void applyNow() - { - if (!m_item || m_name.isEmpty()) - return; - if (m_value) - m_item->setProperty(m_name, m_value); - else - m_item->removeProperty(m_name); - m_item = nullptr; - m_name.clear(); - m_value.reset(); - } - -private: - Item *m_item = nullptr; - QString m_name; - ValuePtr m_value; -}; - -Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem, - Item *item, const CodeLocation &dependsItemLocation, - const QString &moduleId, const QualifiedId &moduleName, - const QString &multiplexId, FallbackMode fallbackMode, - bool isRequired, bool *isProductDependency, - QVariantMap *defaultParameters) -{ - qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId; - - RequiredChainManager requiredChainManager(m_requiredChain, isRequired); - DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation); - - Item *moduleInstance = moduleId.isEmpty() - ? moduleInstanceItem(item, moduleName) - : moduleInstanceItem(item, QStringList(moduleId)); - if (moduleInstance->scope()) - return moduleInstance; // already handled - - if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) { - for (const Item::Module &m : item->modules()) { - if (m.name.front() == moduleName.front()) - throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation); - } - } - QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance); - - // Prepare module instance for evaluating Module.condition. - DelayedPropertyChanger delayedPropertyChanger; - const QString &qbsModuleName = StringConstants::qbsModule(); - const auto fullName = moduleName.toString(); - if (!isBaseModule(fullName)) { - ItemValuePtr qbsProp = productContext->item->itemProperty(qbsModuleName); - if (qbsProp) { - ValuePtr qbsModuleValue = moduleInstance->ownProperty(qbsModuleName); - if (qbsModuleValue) - delayedPropertyChanger.setLater(moduleInstance, qbsModuleName, qbsModuleValue); - else - delayedPropertyChanger.removeLater(moduleInstance, qbsModuleName); - moduleInstance->setProperty(qbsModuleName, qbsProp); - } - } - - SearchPathsManager searchPathsManager(m_reader.get()); // paths can be added by providers - Item *modulePrototype = nullptr; - ProductModuleInfo * const pmi = productModule(productContext, fullName, - multiplexId, *isProductDependency); - if (pmi) { - m_dependsChain.back().isProduct = true; - modulePrototype = pmi->exportItem; - if (defaultParameters) - *defaultParameters = pmi->defaultParameters; - } else if (!*isProductDependency) { - modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, - moduleName, fallbackMode, isRequired, moduleInstance); - } - delayedPropertyChanger.applyNow(); - if (!modulePrototype) - return nullptr; - - searchPathsManager.reset(); // deps must be processed in a clean state - - instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype, - moduleName, pmi); - return moduleInstance; -} - -struct PrioritizedItem -{ - PrioritizedItem(Item *item, int priority, int searchPathIndex) - : item(item), priority(priority), searchPathIndex(searchPathIndex) - { - } - - Item *item = nullptr; - int priority = 0; - int searchPathIndex = 0; -}; - -static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates, - const QString &moduleName) -{ - auto maxIt = std::max_element(candidates.begin(), candidates.end(), - [] (const PrioritizedItem &a, const PrioritizedItem &b) { - if (a.priority < b.priority) - return true; - if (a.priority > b.priority) - return false; - return a.searchPathIndex > b.searchPathIndex; - }); - - size_t nmax = std::count_if(candidates.begin(), candidates.end(), - [maxIt] (const PrioritizedItem &i) { - return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex; - }); - - if (nmax > 1) { - ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.") - .arg(moduleName)); - for (size_t i = 0; i < candidates.size(); ++i) { - const auto candidate = candidates.at(i); - if (candidate.priority == maxIt->priority) { - //: The %1 denotes the number of the candidate. - e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location()); - } - } - throw e; - } - - return maxIt->item; -} - -Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, - const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - FallbackMode fallbackMode, bool isRequired, Item *moduleInstance) -{ - auto existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName); - - if (existingPaths.isEmpty()) { // no suitable names found, try to use providers - AccumulatingTimer providersTimer( - m_parameters.logElapsedTime() ? &m_elapsedTimeModuleProviders : nullptr); - auto result = m_moduleProviderLoader->executeModuleProvider( - *productContext, - dependsItemLocation, - moduleName, - fallbackMode); - if (result.providerAddedSearchPaths) { - qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() - << "with newly added search paths from module provider"; - existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName); - } - } - - const QString fullName = moduleName.toString(); - bool triedToLoadModule = false; - std::vector<PrioritizedItem> candidates; - candidates.reserve(size_t(existingPaths.size())); - for (int i = 0; i < existingPaths.size(); ++i) { - const QString &dirPath = existingPaths.at(i); - QStringList &moduleFileNames = getModuleFileNames(dirPath); - for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) { - const QString &filePath = *it; - const auto [module, triedToLoad] = loadModuleFile( - productContext, fullName, filePath, moduleInstance); - if (module) - candidates.emplace_back(module, 0, i); - if (!triedToLoad) - it = moduleFileNames.erase(it); - else - ++it; - triedToLoadModule = triedToLoadModule || triedToLoad; - } - } - - if (candidates.empty()) { - if (!isRequired) - return createNonPresentModule(fullName, QStringLiteral("not found"), nullptr); - if (Q_UNLIKELY(triedToLoadModule)) { - throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), - dependsItemLocation); - } - return nullptr; - } - - Item *moduleItem; - if (candidates.size() == 1) { - moduleItem = candidates.at(0).item; - } else { - for (auto &candidate : candidates) { - candidate.priority = m_evaluator->intValue(candidate.item, - StringConstants::priorityProperty(), - candidate.priority); - } - moduleItem = chooseModuleCandidate(candidates, fullName); - } - - const auto it = productContext->unknownProfilePropertyErrors.find(moduleItem); - if (it != productContext->unknownProfilePropertyErrors.cend()) { - const QString fullProductName = ResolvedProduct::fullDisplayName - (productContext->name, productContext->multiplexConfigurationId); - ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values " - "in profile '%3':").arg(fullName, fullProductName, - productContext->profileName)); - for (const ErrorInfo &e : it->second) - error.append(e.toString()); - handlePropertyError(error, m_parameters, m_logger); - } - return moduleItem; -} - -QStringList &ModuleLoader::getModuleFileNames(const QString &dirPath) -{ - QStringList &moduleFileNames = m_moduleDirListCache[dirPath]; - if (moduleFileNames.empty()) { - QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards()); - while (dirIter.hasNext()) - moduleFileNames += dirIter.next(); - } - return moduleFileNames; -} - -static Item *findDeepestModuleInstance(Item *instance) -{ - while (instance->prototype() && instance->prototype()->type() == ItemType::ModuleInstance) - instance = instance->prototype(); - return instance; -} - -std::pair<Item *, bool> ModuleLoader::loadModuleFile( - ProductContext *productContext, const QString &fullModuleName, - const QString &filePath, Item *moduleInstance) -{ - checkCancelation(); - - qCDebug(lcModuleLoader) << "loadModuleFile" << fullModuleName << "from" << filePath; - - const auto [module, triedToLoad] = - getModulePrototype(productContext, fullModuleName, filePath); - if (!module) - return {nullptr, triedToLoad}; - - const auto key = std::make_pair(module, productContext); - const auto it = m_modulePrototypeEnabledInfo.find(key); - if (it != m_modulePrototypeEnabledInfo.end()) { - qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; - return {it.value() ? module : nullptr, triedToLoad}; - } - - // Set the name before evaluating any properties. EvaluatorScriptClass reads the module name. - module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName)); - - Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); - Item *origDeepestModuleInstancePrototype = deepestModuleInstance->prototype(); - deepestModuleInstance->setPrototype(module); - bool enabled = checkItemCondition(moduleInstance, module); - deepestModuleInstance->setPrototype(origDeepestModuleInstancePrototype); - if (!enabled) { - qCDebug(lcModuleLoader) << "condition of module" << fullModuleName << "is false"; - m_modulePrototypeEnabledInfo.insert(key, false); - return {nullptr, triedToLoad}; - } - - if (isBaseModule(fullModuleName)) - setupBaseModulePrototype(module); - else - resolveParameterDeclarations(module); - - m_modulePrototypeEnabledInfo.insert(key, true); - return {module, triedToLoad}; -} - -// Returns the module prototype item and a boolean indicating if we tried to load it from the file -std::pair<Item *, bool> ModuleLoader::getModulePrototype(ProductContext *productContext, - const QString &fullModuleName, const QString &filePath) -{ - auto &prototypeList = m_modulePrototypes[filePath]; - for (const auto &prototype : prototypeList) { - if (prototype.second == productContext->profileName) { - qCDebug(lcModuleLoader) << "prototype cache hit (level 1)"; - return {prototype.first, true}; - } - } - Item * const module = loadItemFromFile(filePath, CodeLocation()); - if (module->type() != ItemType::Module) { - qCDebug(lcModuleLoader).nospace() - << "Alleged module " << fullModuleName << " has type '" - << module->typeName() << "', so it's not a module after all."; - return {nullptr, false}; - } - prototypeList.emplace_back(module, productContext->profileName); - - // Module properties that are defined in the profile are used as default values. - // This is the reason we need to have different items per profile. - const QVariantMap profileModuleProperties - = productContext->moduleProperties.value(fullModuleName).toMap(); - for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) { - if (Q_UNLIKELY(!module->hasProperty(it.key()))) { - productContext->unknownProfilePropertyErrors[module].emplace_back - (Tr::tr("Unknown property: %1.%2").arg(fullModuleName, it.key())); - continue; - } - const PropertyDeclaration decl = module->propertyDeclaration(it.key()); - VariantValuePtr v = VariantValue::create( - PropertyDeclaration::convertToPropertyType(it.value(), decl.type(), - QStringList(fullModuleName), it.key())); - module->setProperty(it.key(), v); - } - - return {module, true}; -} - -Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) -{ - const QualifiedId baseModuleName(StringConstants::qbsModule()); - Item::Module baseModuleDesc; - baseModuleDesc.name = baseModuleName; - baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(), - baseModuleName, QString(), FallbackMode::Disabled, true, - &baseModuleDesc.isProduct, nullptr); - if (productContext->item) { - const Item * const qbsInstanceItem - = moduleInstanceItem(productContext->item, baseModuleName); - const Item::PropertyMap &props = qbsInstanceItem->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - if (it.value()->type() == Value::VariantValueType) - baseModuleDesc.item->setProperty(it.key(), it.value()); - } - } - QBS_CHECK(!baseModuleDesc.isProduct); - if (Q_UNLIKELY(!baseModuleDesc.item)) - throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); - return baseModuleDesc; -} - -void ModuleLoader::setupBaseModulePrototype(Item *prototype) -{ - prototype->setProperty(QStringLiteral("hostPlatform"), - VariantValue::create(HostOsInfo::hostOSIdentifier())); - prototype->setProperty(QStringLiteral("hostArchitecture"), - VariantValue::create(HostOsInfo::hostOSArchitecture())); - prototype->setProperty(QStringLiteral("libexecPath"), - VariantValue::create(m_parameters.libexecPath())); - - const Version qbsVersion = LanguageInfo::qbsVersion(); - prototype->setProperty(QStringLiteral("versionMajor"), - VariantValue::create(qbsVersion.majorVersion())); - prototype->setProperty(QStringLiteral("versionMinor"), - VariantValue::create(qbsVersion.minorVersion())); - prototype->setProperty(QStringLiteral("versionPatch"), - VariantValue::create(qbsVersion.patchLevel())); -} - -static void collectItemsWithId_impl(Item *item, QList<Item *> *result) -{ - if (!item->id().isEmpty()) - result->push_back(item); - for (Item * const child : item->children()) - collectItemsWithId_impl(child, result); -} - -static QList<Item *> collectItemsWithId(Item *item) -{ - QList<Item *> result; - collectItemsWithId_impl(item, &result); - return result; -} - -static std::vector<std::pair<QualifiedId, ItemValuePtr>> instanceItemProperties(Item *item) -{ - std::vector<std::pair<QualifiedId, ItemValuePtr>> result; - QualifiedId name; - const auto func = [&] (Item *item, const auto &f) -> void { - for (auto it = item->properties().begin(), end = item->properties().end(); - it != end; ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - ItemValuePtr itemValue = std::static_pointer_cast<ItemValue>(it.value()); - if (!itemValue->item()) - continue; - name.push_back(it.key()); - if (itemValue->item()->type() == ItemType::ModulePrefix) - f(itemValue->item(), f); - else - result.emplace_back(name, itemValue); - name.removeLast(); - } - }; - func(item, func); - return result; -} - -void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct, - Item *instanceScope, Item *moduleInstance, Item *modulePrototype, - const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo) -{ - Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); - deepestModuleInstance->setPrototype(modulePrototype); - const QString fullName = moduleName.toString(); - const QString generalOverrideKey = QStringLiteral("modules.") + fullName; - const QString perProductOverrideKey = StringConstants::productsOverridePrefix() - + productContext->name + QLatin1Char('.') + fullName; - for (Item *instance = moduleInstance; instance; instance = instance->prototype()) { - overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree()); - if (fullName == QStringLiteral("qbs")) - overrideItemProperties(instance, fullName, m_parameters.overriddenValuesTree()); - overrideItemProperties(instance, perProductOverrideKey, - m_parameters.overriddenValuesTree()); - if (instance == deepestModuleInstance) - break; - } - - moduleInstance->setFile(modulePrototype->file()); - moduleInstance->setLocation(modulePrototype->location()); - QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance); - - // create module scope - Item *moduleScope = Item::create(m_pool, ItemType::Scope); - QBS_CHECK(instanceScope->file()); - moduleScope->setFile(instanceScope->file()); - moduleScope->setScope(instanceScope); - QBS_CHECK(productContext->project->scope); - productContext->project->scope->copyProperty(StringConstants::projectVar(), moduleScope); - if (productContext->scope) - productContext->scope->copyProperty(StringConstants::productVar(), moduleScope); - else - QBS_CHECK(fullName == StringConstants::qbsModule()); // Dummy product. - - if (productModuleInfo) { - exportingProduct = productModuleInfo->exportItem->parent(); - QBS_CHECK(exportingProduct); - QBS_CHECK(exportingProduct->type() == ItemType::Product); - } - - if (exportingProduct) { - const auto exportingProductItemValue = ItemValue::create(exportingProduct); - moduleScope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue); - - const auto importingProductItemValue = ItemValue::create(productContext->item); - moduleScope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue); - - moduleScope->setProperty(StringConstants::projectVar(), - ItemValue::create(exportingProduct->parent())); - - PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(), - PropertyDeclaration::String, QString(), - PropertyDeclaration::PropertyNotAvailableInConfig); - moduleInstance->setPropertyDeclaration(pd.name(), pd); - ValuePtr v = exportingProduct - ->property(StringConstants::sourceDirectoryProperty())->clone(); - moduleInstance->setProperty(pd.name(), v); - } - moduleInstance->setScope(moduleScope); - - QHash<Item *, Item *> prototypeInstanceMap; - prototypeInstanceMap[modulePrototype] = moduleInstance; - - // create instances for every child of the prototype - createChildInstances(moduleInstance, modulePrototype, &prototypeInstanceMap); - - // create ids from from the prototype in the instance - if (modulePrototype->file()->idScope()) { - const auto items = collectItemsWithId(modulePrototype); - for (Item * const itemWithId : items) { - Item *idProto = itemWithId; - Item *idInstance = prototypeInstanceMap.value(idProto); - QBS_ASSERT(idInstance, continue); - ItemValuePtr idInstanceValue = ItemValue::create(idInstance); - moduleScope->setProperty(itemWithId->id(), idInstanceValue); - } - } - - // For foo.bar in modulePrototype create an item foo in moduleInstance. - for (const auto &[propertyName, itemValue] : instanceItemProperties(modulePrototype)) { - if (itemValue->item()->properties().empty()) - continue; - qCDebug(lcModuleLoader) << "The prototype of " << moduleName - << " sets properties on " << propertyName.toString(); - Item *item = moduleInstanceItem(moduleInstance, propertyName); - item->setPrototype(itemValue->item()); - if (itemValue->createdByPropertiesBlock()) { - ItemValuePtr itemValue = moduleInstance->itemProperty(propertyName.front()); - for (int i = 1; i < propertyName.size(); ++i) - itemValue = itemValue->item()->itemProperty(propertyName.at(i)); - itemValue->setCreatedByPropertiesBlock(true); - } - } - - // Resolve dependencies of this module instance. - DependsContext dependsContext; - dependsContext.product = productContext; - dependsContext.exportingProductItem = exportingProduct; - QBS_ASSERT(moduleInstance->modules().empty(), moduleInstance->removeModules()); - if (productModuleInfo) { - dependsContext.productDependencies = &productContext->productModuleDependencies[fullName]; - resolveDependencies(&dependsContext, moduleInstance); - } else if (!isBaseModule(fullName)) { - dependsContext.productDependencies = &productContext->info.usedProducts; - resolveDependencies(&dependsContext, moduleInstance); - } - - // Check readonly properties. - const auto end = moduleInstance->properties().cend(); - for (auto it = moduleInstance->properties().cbegin(); it != end; ++it) { - const PropertyDeclaration &pd = moduleInstance->propertyDeclaration(it.key()); - if (!pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) - continue; - throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), - moduleInstance->property(pd.name())->location()); - } -} - -void ModuleLoader::createChildInstances(Item *instance, Item *prototype, - QHash<Item *, Item *> *prototypeInstanceMap) const -{ - instance->childrenReserve(instance->children().size() + prototype->children().size()); - - for (Item * const childPrototype : prototype->children()) { - Item *childInstance = Item::create(m_pool, childPrototype->type()); - prototypeInstanceMap->insert(childPrototype, childInstance); - childInstance->setPrototype(childPrototype); - childInstance->setFile(childPrototype->file()); - childInstance->setId(childPrototype->id()); - childInstance->setLocation(childPrototype->location()); - childInstance->setScope(instance->scope()); - Item::addChild(instance, childInstance); - createChildInstances(childInstance, childPrototype, prototypeInstanceMap); - } -} - -void ModuleLoader::checkCancelation() const -{ - if (m_progressObserver && m_progressObserver->canceled()) { - throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") - .arg(TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree()))); - } -} - -bool ModuleLoader::checkItemCondition(Item *item, Item *itemToDisable) -{ - if (m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return true; - m_disabledItems += itemToDisable ? itemToDisable : item; - return false; -} - -QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) -{ - QStringList result; - const QStringList paths = m_evaluator->stringListValue( - item, StringConstants::qbsSearchPathsProperty(), wasSet); - const JSSourceValueConstPtr prop = item->sourceProperty( - StringConstants::qbsSearchPathsProperty()); - - // Value can come from within a project file or as an overridden value from the user - // (e.g command line). - const QString basePath = FileInfo::path(prop ? prop->file()->filePath() - : m_parameters.projectFilePath()); - for (const QString &path : paths) - result += FileInfo::resolvePath(basePath, path); - return result; -} - -void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) -{ - if (!sourceProject) - return; - const QList<PropertyDeclaration> builtinProjectProperties = BuiltinDeclarations::instance() - .declarationsForType(ItemType::Project).properties(); - Set<QString> builtinProjectPropertyNames; - for (const PropertyDeclaration &p : builtinProjectProperties) - builtinProjectPropertyNames << p.name(); - - for (Item::PropertyDeclarationMap::ConstIterator it - = sourceProject->propertyDeclarations().constBegin(); - it != sourceProject->propertyDeclarations().constEnd(); ++it) { - - // We must not inherit built-in properties such as "name", - // but there are exceptions. - if (it.key() == StringConstants::qbsSearchPathsProperty() - || it.key() == StringConstants::profileProperty() - || it.key() == StringConstants::buildDirectoryProperty() - || it.key() == StringConstants::sourceDirectoryProperty() - || it.key() == StringConstants::minimumQbsVersionProperty()) { - const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); - QBS_ASSERT(v, continue); - if (v->sourceCode() == StringConstants::undefinedValue()) - sourceProject->copyProperty(it.key(), targetProject); - continue; - } - - if (builtinProjectPropertyNames.contains(it.key())) - continue; - - if (targetProject->hasOwnProperty(it.key())) - continue; // Ignore stuff the target project already has. - - targetProject->setPropertyDeclaration(it.key(), it.value()); - sourceProject->copyProperty(it.key(), targetProject); - } -} - -Item *ModuleLoader::wrapInProjectIfNecessary(Item *item) -{ - if (item->type() == ItemType::Project) - return item; - Item *prj = Item::create(item->pool(), ItemType::Project); - Item::addChild(prj, item); - prj->setFile(item->file()); - prj->setLocation(item->location()); - prj->setupForBuiltinType(m_logger); - return prj; -} - -QString ModuleLoader::findExistingModulePath(const QString &searchPath, - const QualifiedId &moduleName) -{ - // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the - // modules and search paths we've already processed - auto &moduleInfo = m_existingModulePathCache[{searchPath, moduleName}]; - if (moduleInfo) - return *moduleInfo; - - QString dirPath = searchPath + QStringLiteral("/modules"); - - for (const QString &moduleNamePart : moduleName) { - dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); - if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) { - return *(moduleInfo = QString()); - } - } - - return *(moduleInfo = dirPath); -} - -QStringList ModuleLoader::findExistingModulePaths( - const QStringList &searchPaths, const QualifiedId &moduleName) -{ - QStringList result; - result.reserve(searchPaths.size()); - for (const auto &path: searchPaths) { - const QString dirPath = findExistingModulePath(path, moduleName); - if (!dirPath.isEmpty()) - result.append(dirPath); - } - return result; -} - -void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) -{ - for (Item * const child : item->children()) { - child->setScope(scope); - setScopeForDescendants(child, scope); - } -} - -void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey, - const QVariantMap &buildConfig) -{ - const QVariant buildConfigValue = buildConfig.value(buildConfigKey); - if (buildConfigValue.isNull()) - return; - item->overrideProperties(buildConfigValue.toMap(), buildConfigKey, m_parameters, m_logger); -} - -void ModuleLoader::collectAllModules(Item *item, std::vector<Item::Module> *modules) -{ - for (const Item::Module &m : item->modules()) { - if (moduleRepresentsDisabledProduct(m)) - m.item->removeModules(); - auto it = std::find_if(modules->begin(), modules->end(), - [m] (const Item::Module &m2) { return m.name == m2.name; }); - if (it != modules->end()) { - // If a module is required somewhere, it is required in the top-level item. - if (m.required) - it->required = true; - it->versionRange.narrowDown(m.versionRange); - it->fallbackEnabled = it->fallbackEnabled && m.fallbackEnabled; - continue; - } - modules->push_back(m); - collectAllModules(m.item, modules); - } -} - -std::vector<Item::Module> ModuleLoader::allModules(Item *item) -{ - std::vector<Item::Module> lst; - collectAllModules(item, &lst); - return lst; -} - -bool ModuleLoader::moduleRepresentsDisabledProduct(const Item::Module &module) -{ - if (!module.isProduct) - return false; - const Item *exportItem = module.item->prototype(); - while (exportItem && exportItem->type() != ItemType::Export) - exportItem = exportItem->prototype(); - QBS_CHECK(exportItem); - Item * const productItem = exportItem->parent(); - QBS_CHECK(productItem->type() == ItemType::Product); - return m_disabledItems.contains(productItem) || !checkItemCondition(productItem); -} - -void ModuleLoader::addProductModuleDependencies(ProductContext *productContext, const QString &name) -{ - auto deps = productContext->productModuleDependencies.at(name); - QList<ModuleLoaderResult::ProductInfo::Dependency> depsToAdd; - const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty(); - for (auto &dep : deps) { - const auto productRange = m_productsByName.equal_range(dep.name); - std::vector<const ProductContext *> dependencies; - bool hasNonMultiplexedDependency = false; - for (auto it = productRange.first; it != productRange.second; ++it) { - if (!it->second->multiplexConfigurationId.isEmpty()) { - dependencies.push_back(it->second); - if (productIsMultiplexed && dep.profile.isEmpty()) - break; - } else { - hasNonMultiplexedDependency = true; - break; - } - } - - if (hasNonMultiplexedDependency) { - depsToAdd.push_back(dep); - continue; - } - - for (std::size_t i = 0; i < dependencies.size(); ++i) { - const bool profileMatch = dep.profile.isEmpty() - || dep.profile == StringConstants::star() - || dep.profile == dependencies.at(i)->profileName; - if (i == 0) { - if (productIsMultiplexed && dep.profile.isEmpty()) { - const ValuePtr &multiplexConfigIdProp = productContext->item->property( - StringConstants::multiplexConfigurationIdProperty()); - dep.multiplexConfigurationId = std::static_pointer_cast<VariantValue>( - multiplexConfigIdProp)->value().toString(); - depsToAdd.push_back(dep); - break; - } - if (profileMatch) { - dep.multiplexConfigurationId = dependencies.at(i)->multiplexConfigurationId; - depsToAdd.push_back(dep); - } - } else if (profileMatch) { - ModuleLoaderResult::ProductInfo::Dependency newDependency = dep; - newDependency.multiplexConfigurationId - = dependencies.at(i)->multiplexConfigurationId; - depsToAdd << newDependency; - } - } - } - productContext->info.usedProducts.insert(productContext->info.usedProducts.end(), - depsToAdd.cbegin(), depsToAdd.cend()); -} - -static void collectProductModuleDependencies(Item *item, Set<QualifiedId> &allDeps) -{ - for (const Item::Module &m : item->modules()) { - if (m.isProduct && allDeps.insert(m.name).second) - collectProductModuleDependencies(m.item, allDeps); - } -} - -void ModuleLoader::addProductModuleDependencies(ModuleLoader::ProductContext *ctx) -{ - Set<QualifiedId> deps; - collectProductModuleDependencies(ctx->item, deps); - for (const QualifiedId &dep : deps) - addProductModuleDependencies(ctx, dep.toString()); -} - -void ModuleLoader::addTransitiveDependencies(ProductContext *ctx) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimeTransitiveDependencies : nullptr); - qCDebug(lcModuleLoader) << "addTransitiveDependencies"; - - std::vector<Item::Module> transitiveDeps = allModules(ctx->item); - std::sort(transitiveDeps.begin(), transitiveDeps.end()); - for (const Item::Module &m : ctx->item->modules()) { - auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m); - QBS_CHECK(it != transitiveDeps.end() && it->name == m.name); - transitiveDeps.erase(it); - } - for (const Item::Module &module : qAsConst(transitiveDeps)) { - if (module.isProduct) { - ctx->item->addModule(module); - } else { - const FallbackMode fallbackMode = module.fallbackEnabled - ? FallbackMode::Enabled : FallbackMode::Disabled; - Item::Module dep; - dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(), - module.name, QString(), fallbackMode, - module.required, &dep.isProduct, &dep.parameters); - if (!dep.item) { - throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive " - "dependencies for product '%2'.").arg(module.name.toString(), - ctx->name), - ctx->item->location()); - } - dep.name = module.name; - dep.required = module.required; - dep.versionRange = module.versionRange; - dep.fallbackEnabled = fallbackMode == FallbackMode::Enabled; - ctx->item->addModule(dep); - } - } -} - -Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module) -{ - qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")." - << "Creating dummy module for presence check."; - if (!module) { - module = Item::create(m_pool, ItemType::ModuleInstance); - module->setFile(FileContext::create()); - module->setProperty(StringConstants::nameProperty(), VariantValue::create(name)); - } - module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue()); - return module; -} - -void ModuleLoader::handleProductError(const ErrorInfo &error, - ModuleLoader::ProductContext *productContext) -{ - const bool alreadyHadError = productContext->info.delayedError.hasError(); - if (!alreadyHadError) { - productContext->info.delayedError.append(Tr::tr("Error while handling product '%1':") - .arg(productContext->name), - productContext->item->location()); - } - if (error.isInternalError()) { - if (alreadyHadError) { - qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString() - << "in product" << productContext->name; - return; - } - for (const auto &kv : productContext->productModuleDependencies) { - const auto rangeForName = m_productsByName.equal_range(kv.first); - for (auto rangeIt = rangeForName.first; rangeIt != rangeForName.second; ++rangeIt) { - const ProductContext * const dep = rangeIt->second; - if (dep->info.delayedError.hasError()) { - qCDebug(lcModuleLoader()) << "ignoring internal error" << error.toString() - << "in product" << productContext->name - << "assumed to be caused by erroneous dependency" - << dep->name; - return; - } - } - } - } - const auto errorItems = error.items(); - for (const ErrorItem &ei : errorItems) - productContext->info.delayedError.append(ei.description(), ei.codeLocation()); - productContext->project->result->productInfos[productContext->item] = productContext->info; - m_disabledItems << productContext->item; - m_erroneousProducts.insert(productContext->name); -} - -static void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix, - QualifiedIdSet &properties) -{ - const Item::PropertyMap &props = iv->item()->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - switch (it.value()->type()) { - case Value::JSSourceValueType: - properties << (QualifiedId(prefix) << it.key()); - break; - case Value::ItemValueType: - if (iv->item()->type() == ItemType::ModulePrefix) { - gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), - QualifiedId(prefix) << it.key(), properties); - } - break; - default: - break; - } - } -} - -QualifiedIdSet ModuleLoader::gatherModulePropertiesSetInGroup(const Item *group) -{ - QualifiedIdSet propsSetInGroup; - const Item::PropertyMap &props = group->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - if (it.value()->type() == Value::ItemValueType) { - gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), - QualifiedId(it.key()), propsSetInGroup); - } - } - return propsSetInGroup; -} - -void ModuleLoader::markModuleTargetGroups(Item *group, const Item::Module &module) -{ - QBS_CHECK(group->type() == ItemType::Group); - if (m_evaluator->boolValue(group, StringConstants::filesAreTargetsProperty())) { - group->setProperty(StringConstants::modulePropertyInternal(), - VariantValue::create(module.name.toString())); - } - for (Item * const child : group->children()) - markModuleTargetGroups(child, module); -} - -void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext, - const Item::Module &module, - const Item *modulePrototype) -{ - for (Item * const child : modulePrototype->children()) { - if (child->type() == ItemType::Group) { - Item * const clonedGroup = child->clone(); - clonedGroup->setScope(productContext.scope); - setScopeForDescendants(clonedGroup, productContext.scope); - Item::addChild(productContext.item, clonedGroup); - markModuleTargetGroups(clonedGroup, module); - } - } -} - -void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext) -{ - for (const Item::Module &module : productContext.item->modules()) { - Item *prototype = module.item; - bool modulePassedValidation; - while ((modulePassedValidation = prototype->isPresentModule()) && prototype->prototype()) - prototype = prototype->prototype(); - if (modulePassedValidation) - copyGroupsFromModuleToProduct(productContext, module, prototype); - } -} - -QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const -{ - return ResolvedProduct::uniqueName(name, multiplexConfigurationId); -} - -QString ModuleLoader::ProductContext::uniqueName() const -{ - return ResolvedProduct::uniqueName(name, multiplexConfigurationId); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h deleted file mode 100644 index db0b51cae..000000000 --- a/src/lib/corelib/language/moduleloader.h +++ /dev/null @@ -1,459 +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_MODULELOADER_H -#define QBS_MODULELOADER_H - -#include "filetags.h" -#include "forward_decls.h" -#include "item.h" -#include "itempool.h" -#include "moduleproviderinfo.h" -#include <logging/logger.h> -#include <tools/filetime.h> -#include <tools/qttools.h> -#include <tools/set.h> -#include <tools/setupprojectparameters.h> -#include <tools/version.h> - -#include <QtCore/qmap.h> -#include <QtCore/qstringlist.h> -#include <QtCore/qvariant.h> - -#include <map> -#include <memory> -#include <optional> -#include <unordered_map> -#include <utility> -#include <vector> - -namespace qbs { - -class CodeLocation; -class Settings; - -namespace Internal { - -class Evaluator; -class Item; -class ItemReader; -class ModuleProviderLoader; -class ProbesResolver; -class ProgressObserver; -class QualifiedId; -class SearchPathsManager; - -using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>; - -struct ModuleLoaderResult -{ - ModuleLoaderResult() - : itemPool(new ItemPool), root(nullptr) - {} - - struct ProductInfo - { - struct Dependency - { - QString name; - QString profile; // "*" <=> Match all profiles. - QString multiplexConfigurationId; - QVariantMap parameters; - bool limitToSubProject = false; - bool isRequired = true; - - QString uniqueName() const; - }; - - std::vector<ProbeConstPtr> probes; - std::vector<Dependency> usedProducts; - ModulePropertiesPerGroup modulePropertiesSetInGroups; - ErrorInfo delayedError; - }; - - std::shared_ptr<ItemPool> itemPool; - Item *root; - std::unordered_map<Item *, ProductInfo> productInfos; - std::vector<ProbeConstPtr> projectProbes; - StoredModuleProviderInfo storedModuleProviderInfo; - Set<QString> qbsFiles; - QVariantMap profileConfigs; -}; - -/* - * Loader stage II. Responsible for - * - loading modules and module dependencies, - * - project references, - * - Probe items. - */ -class ModuleLoader -{ -public: - ModuleLoader(Evaluator *evaluator, Logger &logger); - ~ModuleLoader(); - - void setProgressObserver(ProgressObserver *progressObserver); - void setSearchPaths(const QStringList &searchPaths); - void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes); - void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); - void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } - void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo); - Evaluator *evaluator() const { return m_evaluator; } - - ModuleLoaderResult load(const SetupProjectParameters ¶meters); - -private: - friend class ModuleProviderLoader; - friend class ProbesResolver; - class ProductSortByDependencies; - - class ContextBase - { - public: - ContextBase() - : item(nullptr), scope(nullptr) - {} - - Item *item; - Item *scope; - QString name; - }; - - class ProjectContext; - - using ProductDependencies = std::vector<ModuleLoaderResult::ProductInfo::Dependency>; - - // This is the data we need to store at the point where a dependency is deferred - // in order to properly resolve the dependency in pass 2. - struct DeferredDependsContext { - DeferredDependsContext(Item *exportingProduct, Item *parent) - : exportingProductItem(exportingProduct), parentItem(parent) {} - Item *exportingProductItem = nullptr; - Item *parentItem = nullptr; - bool operator==(const DeferredDependsContext &other) const - { - return exportingProductItem == other.exportingProductItem - && parentItem == other.parentItem; - } - bool operator<(const DeferredDependsContext &other) const - { - return parentItem < other.parentItem; - } - }; - - class ProductContext : public ContextBase - { - public: - ProjectContext *project = nullptr; - ModuleLoaderResult::ProductInfo info; - QString profileName; - QString multiplexConfigurationId; - QVariantMap moduleProperties; - std::map<QString, ProductDependencies> productModuleDependencies; - std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors; - QStringList searchPaths; - - std::optional<QVariantMap> theModuleProviderConfig; - - // The key corresponds to DeferredDependsContext.exportingProductItem, which is the - // only value from that data structure that we still need here. - std::unordered_map<Item *, std::vector<Item *>> deferredDependsItems; - - QString uniqueName() const; - }; - - class TopLevelProjectContext; - - class ProjectContext : public ContextBase - { - public: - TopLevelProjectContext *topLevelProject = nullptr; - ModuleLoaderResult *result = nullptr; - std::vector<ProductContext> products; - std::vector<QStringList> searchPathsStack; - }; - - struct ProductModuleInfo - { - Item *exportItem = nullptr; - QString multiplexId; - QVariantMap defaultParameters; - }; - - class TopLevelProjectContext - { - Q_DISABLE_COPY(TopLevelProjectContext) - public: - TopLevelProjectContext() = default; - ~TopLevelProjectContext() { qDeleteAll(projects); } - - std::vector<ProjectContext *> projects; - QMultiHash<QString, ProductModuleInfo> productModules; - std::vector<ProbeConstPtr> probes; - QString buildDirectory; - }; - - class DependsContext - { - public: - ProductContext *product = nullptr; - Item *exportingProductItem = nullptr; - ProductDependencies *productDependencies = nullptr; - }; - - void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, - const QString &buildDirectory, const Set<QString> &referencedFilePaths); - void handleProject(ModuleLoaderResult *loadResult, - TopLevelProjectContext *topLevelProjectContext, Item *projectItem, - const Set<QString> &referencedFilePaths); - - using MultiplexRow = std::vector<VariantValuePtr>; - using MultiplexTable = std::vector<MultiplexRow>; - - struct MultiplexInfo - { - std::vector<QString> properties; - MultiplexTable table; - bool aggregate = false; - VariantValuePtr multiplexedType; - - QString toIdString(size_t row) const; - static QVariantMap multiplexIdToVariantMap(const QString &multiplexId); - }; - - void dump(const MultiplexInfo &mpi); - static MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values); - MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem); - QList<Item *> multiplexProductItem(ProductContext *dummyContext, Item *productItem); - void normalizeDependencies(ProductContext *product, - const DeferredDependsContext &dependsContext); - void adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp); - void adjustDependenciesForMultiplexing(const ProductContext &product); - void adjustDependenciesForMultiplexing(const ProductContext &product, Item *dependsItem); - - void prepareProduct(ProjectContext *projectContext, Item *productItem); - void setupProductDependencies(ProductContext *productContext, - const Set<DeferredDependsContext> &deferredDependsContext); - void handleProduct(ProductContext *productContext); - void checkDependencyParameterDeclarations(const ProductContext *productContext) const; - void handleModuleSetupError(ProductContext *productContext, const Item::Module &module, - const ErrorInfo &error); - void initProductProperties(const ProductContext &product); - void handleSubProject(ProjectContext *projectContext, Item *projectItem, - const Set<QString> &referencedFilePaths); - QList<Item *> loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, - ProductContext &dummyContext); - void handleAllPropertyOptionsItems(Item *item); - void handlePropertyOptions(Item *optionsItem); - - using ModuleDependencies = QHash<QualifiedId, QualifiedIdSet>; - void setupReverseModuleDependencies(const Item::Module &module, ModuleDependencies &deps, - QualifiedIdSet &seenModules); - ModuleDependencies setupReverseModuleDependencies(const Item *product); - void handleGroup(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies); - void propagateModulesFromParent(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies); - void adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, - const Item::Modules &dependentModules); - - bool mergeExportItems(const ProductContext &productContext); - void resolveDependencies(DependsContext *dependsContext, Item *item, - ProductContext *productContext = nullptr); - class ItemModuleList; - void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem, - ItemModuleList *moduleResults, ProductDependencies *productResults); - void forwardParameterDeclarations(const Item *dependsItem, const ItemModuleList &modules); - void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, - const ItemModuleList &modules); - void resolveParameterDeclarations(const Item *module); - QVariantMap extractParameters(Item *dependsItem) const; - Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName); - static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name, - const QString &multiplexId, bool &productNameMatch); - static ProductContext *product(ProjectContext *projectContext, const QString &name); - static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name); - - enum class FallbackMode { Enabled, Disabled }; - Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item, - const CodeLocation &dependsItemLocation, const QString &moduleId, - const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode, - bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters); - Item *searchAndLoadModuleFile(ProductContext *productContext, - const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - FallbackMode fallbackMode, bool isRequired, Item *moduleInstance); - QStringList &getModuleFileNames(const QString &dirPath); - std::pair<Item *, bool> loadModuleFile( - ProductContext *productContext, const QString &fullModuleName, - const QString &filePath, Item *moduleInstance); - std::pair<Item *, bool> getModulePrototype(ProductContext *productContext, - const QString &fullModuleName, const QString &filePath); - Item::Module loadBaseModule(ProductContext *productContext, Item *item); - void setupBaseModulePrototype(Item *prototype); - template <typename T, typename F> - T callWithTemporaryBaseModule(ProductContext *productContext, const F &func); - void instantiateModule(ProductContext *productContext, Item *exportingProductItem, - Item *instanceScope, Item *moduleInstance, Item *modulePrototype, - const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo); - void createChildInstances(Item *instance, Item *prototype, - QHash<Item *, Item *> *prototypeInstanceMap) const; - void checkCancelation() const; - bool checkItemCondition(Item *item, Item *itemToDisable = nullptr); - QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr); - void copyProperties(const Item *sourceProject, Item *targetProject); - Item *wrapInProjectIfNecessary(Item *item); - QString findExistingModulePath(const QString &searchPath, const QualifiedId &moduleName); - QStringList findExistingModulePaths( - const QStringList &searchPaths, const QualifiedId &moduleName); - - static void setScopeForDescendants(Item *item, Item *scope); - void overrideItemProperties(Item *item, const QString &buildConfigKey, - const QVariantMap &buildConfig); - void addProductModuleDependencies(ProductContext *ctx, const QString &name); - void addProductModuleDependencies(ProductContext *ctx); - void addTransitiveDependencies(ProductContext *ctx); - Item *createNonPresentModule(const QString &name, const QString &reason, Item *module); - void copyGroupsFromModuleToProduct(const ProductContext &productContext, - const Item::Module &module, const Item *modulePrototype); - void copyGroupsFromModulesToProduct(const ProductContext &productContext); - void markModuleTargetGroups(Item *group, const Item::Module &module); - bool checkExportItemCondition(Item *exportItem, const ProductContext &productContext); - - void printProfilingInfo(); - void handleProductError(const ErrorInfo &error, ProductContext *productContext); - QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group); - Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation); - void collectProductsByName(const TopLevelProjectContext &topLevelProject); - void collectProductsByType(const TopLevelProjectContext &topLevelProject); - - void handleProfileItems(Item *item, ProjectContext *projectContext); - std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext); - void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, - QVariantMap &values); - void handleProfile(Item *profileItem); - void collectNameFromOverride(const QString &overrideString); - void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp); - void checkProductNamesInOverrides(); - void setSearchPathsForProduct(ProductContext *product); - - Item::Modules modulesSortedByDependency(const Item *productItem); - void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules); - void collectAllModules(Item *item, std::vector<Item::Module> *modules); - std::vector<Item::Module> allModules(Item *item); - bool moduleRepresentsDisabledProduct(const Item::Module &module); - - using ShadowProductInfo = std::pair<bool, QString>; - ShadowProductInfo getShadowProductInfo(const ProductContext &product) const; - - ItemPool *m_pool; - Logger &m_logger; - ProgressObserver *m_progressObserver; - const std::unique_ptr<ItemReader> m_reader; - Evaluator *m_evaluator; - const std::unique_ptr<ProbesResolver> m_probesResolver; - const std::unique_ptr<ModuleProviderLoader> m_moduleProviderLoader; - QMap<QString, QStringList> m_moduleDirListCache; - QHash<std::pair<QString, QualifiedId>, std::optional<QString>> m_existingModulePathCache; - - // The keys are file paths, the values are module prototype items accompanied by a profile. - std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> m_modulePrototypes; - - // The keys are module prototypes and products, the values specify whether the module's - // condition is true for that product. - QHash<std::pair<Item *, ProductContext *>, bool> m_modulePrototypeEnabledInfo; - - QHash<const Item *, Item::PropertyDeclarationMap> m_parameterDeclarations; - Set<Item *> m_disabledItems; - std::vector<bool> m_requiredChain; - - struct DependsChainEntry - { - DependsChainEntry(QualifiedId name, const CodeLocation &location) - : name(std::move(name)), location(location) - { - } - - QualifiedId name; - CodeLocation location; - bool isProduct = false; - }; - class DependsChainManager; - std::vector<DependsChainEntry> m_dependsChain; - - FileTime m_lastResolveTime; - QVariantMap m_storedProfiles; - QVariantMap m_localProfiles; - std::multimap<QString, const ProductContext *> m_productsByName; - std::multimap<FileTag, const ProductContext *> m_productsByType; - - std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems; - Set<Item *> m_exportsWithDeferredDependsItems; - - SetupProjectParameters m_parameters; - std::unique_ptr<Settings> m_settings; - Version m_qbsVersion; - Item *m_tempScopeItem = nullptr; - - qint64 m_elapsedTimeProbes = 0; - qint64 m_elapsedTimePrepareProducts = 0; - qint64 m_elapsedTimeProductDependencies = 0; - qint64 m_elapsedTimeModuleProviders = 0; - qint64 m_elapsedTimeTransitiveDependencies = 0; - qint64 m_elapsedTimeHandleProducts = 0; - qint64 m_elapsedTimePropertyChecking = 0; - Set<QString> m_projectNamesUsedInOverrides; - Set<QString> m_productNamesUsedInOverrides; - Set<QString> m_disabledProjects; - Set<QString> m_erroneousProducts; - - int m_dependencyResolvingPass = 0; -}; - -} // namespace Internal -} // namespace qbs - -QT_BEGIN_NAMESPACE -Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE); -QT_END_NAMESPACE - -#endif // QBS_MODULELOADER_H diff --git a/src/lib/corelib/language/modulemerger.cpp b/src/lib/corelib/language/modulemerger.cpp deleted file mode 100644 index 6ad8e2259..000000000 --- a/src/lib/corelib/language/modulemerger.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "modulemerger.h" - -#include "value.h" - -#include <logging/translator.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -namespace qbs { -namespace Internal { - -ModuleMerger::ModuleMerger(Logger &logger, Item *productItem, const QString &productName, - const Item::Modules::iterator &modulesBegin, - const Item::Modules::iterator &modulesEnd) - : m_logger(logger) - , m_productItem(productItem) - , m_mergedModule(*modulesBegin) - , m_isBaseModule(m_mergedModule.name.first() == StringConstants::qbsModule()) - , m_isShadowProduct(productName.startsWith(StringConstants::shadowProductPrefix())) - , m_modulesBegin(std::next(modulesBegin)) - , m_modulesEnd(modulesEnd) -{ - QBS_CHECK(modulesBegin->item->type() == ItemType::ModuleInstance); -} - -void ModuleMerger::replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace) -{ - QBS_CHECK(!moduleName.empty()); - QBS_CHECK(containerItem != m_mergedModule.item); - const QString moduleNamePrefix = moduleName.takeFirst(); - const Item::PropertyMap &properties = containerItem->properties(); - for (auto it = properties.begin(); it != properties.end(); ++it) { - if (it.key() != moduleNamePrefix) - continue; - Value * const val = it.value().get(); - QBS_CHECK(val); - QBS_CHECK(val->type() == Value::ItemValueType); - const auto itemVal = static_cast<ItemValue *>(val); - if (moduleName.empty()) { - QBS_CHECK(itemVal->item() == toReplace); - itemVal->setItem(m_mergedModule.item); - } else { - replaceItemInValues(moduleName, itemVal->item(), toReplace); - } - } -} - -void ModuleMerger::start() -{ - // Iterate over any module that our product depends on. These modules - // may depend on m_mergedModule and contribute property assignments. - Item::PropertyMap props; - for (auto module = m_modulesBegin; module != m_modulesEnd; module++) - mergeModule(&props, *module); - - // Module property assignments in the product have the highest priority - // and are thus prepended. - Item::Module m; - m.item = m_productItem; - mergeModule(&props, m); - - // The module's prototype is the essential unmodified module as loaded - // from the cache. - Item *moduleProto = m_mergedModule.item->prototype(); - while (moduleProto->prototype()) - moduleProto = moduleProto->prototype(); - - // The prototype item might contain default values which get appended in - // case of list properties. Scalar properties will only be set if not - // already specified above. - Item::PropertyMap mergedProps = m_mergedModule.item->properties(); - for (auto it = props.constBegin(); it != props.constEnd(); ++it) { - appendPrototypeValueToNextChain(moduleProto, it.key(), it.value()); - mergedProps[it.key()] = it.value(); - } - - m_mergedModule.item->setProperties(mergedProps); - - // Update all sibling instances of the to-be-merged module to behave identical - // to the merged module. - for (Item *moduleInstanceContainer : qAsConst(m_moduleInstanceContainers)) { - Item::Modules modules; - for (const Item::Module &dep : moduleInstanceContainer->modules()) { - const bool isTheModule = dep.name == m_mergedModule.name; - Item::Module m = dep; - if (isTheModule && m.item != m_mergedModule.item) { - QBS_CHECK(m.item->type() == ItemType::ModuleInstance); - replaceItemInValues(m.name, moduleInstanceContainer, m.item); - m.item = m_mergedModule.item; - m.required = m_mergedModule.required; - m.versionRange = m_mergedModule.versionRange; - m.fallbackEnabled = m_mergedModule.fallbackEnabled; - } - modules << m; - } - moduleInstanceContainer->setModules(modules); - } -} - -void ModuleMerger::mergeModule(Item::PropertyMap *dstProps, const Item::Module &module) -{ - const Item::Module *dep = findModule(module.item, m_mergedModule.name); - if (!dep) - return; - - const bool mergingProductItem = (module.item == m_productItem); - Item *srcItem = dep->item; - Item *origSrcItem = srcItem; - do { - if (m_seenInstances.insert(srcItem).second) { - for (auto it = srcItem->properties().constBegin(); - it != srcItem->properties().constEnd(); ++it) { - const ValuePtr &srcVal = it.value(); - if (srcVal->type() == Value::ItemValueType) - continue; - if (it.key() == StringConstants::qbsSourceDirPropertyInternal()) - continue; - const PropertyDeclaration srcDecl = srcItem->propertyDeclaration(it.key()); - if (!srcDecl.isValid()) - continue; - - // Scalar variant values could stem from product multiplexing, in which case - // the merged qbs module instance needs to get that value. - if (srcVal->type() == Value::VariantValueType - && (!srcDecl.isScalar() || !m_isBaseModule)) { - continue; - } - - ValuePtr clonedSrcVal = srcVal->clone(); - clonedSrcVal->setDefiningItem(origSrcItem); - - ValuePtr &dstVal = (*dstProps)[it.key()]; - if (dstVal) { - if (srcDecl.isScalar()) { - // Scalar properties get replaced. - if ((dstVal->type() == Value::JSSourceValueType) - && (srcVal->type() == Value::JSSourceValueType)) { - // Warn only about conflicting source code values - const JSSourceValuePtr dstJsVal = - std::static_pointer_cast<JSSourceValue>(dstVal); - const JSSourceValuePtr srcJsVal = - std::static_pointer_cast<JSSourceValue>(srcVal); - const bool overriddenInProduct = - m_mergedModule.item->properties().contains(it.key()); - - if (dstJsVal->sourceCode() != srcJsVal->sourceCode() - && !mergingProductItem && !overriddenInProduct - && !m_isShadowProduct) { - m_logger.qbsWarning() - << Tr::tr("Conflicting scalar values at %1 and %2.").arg( - dstJsVal->location().toString(), - srcJsVal->location().toString()); - } - } - } else { - // List properties get prepended - QBS_CHECK(!clonedSrcVal->next()); - clonedSrcVal->setNext(dstVal); - } - } - dstVal = clonedSrcVal; - } - } - srcItem = srcItem->prototype(); - } while (srcItem && srcItem->type() == ItemType::ModuleInstance); - - // Update dependency constraints - if (dep->required) - m_mergedModule.required = true; - // if one dep has fallback disabled, disable it for the merged module - m_mergedModule.fallbackEnabled = m_mergedModule.fallbackEnabled && dep->fallbackEnabled; - m_mergedModule.versionRange.narrowDown(dep->versionRange); - - // We need to touch the unmerged module instances later once more - m_moduleInstanceContainers << module.item; -} - -void ModuleMerger::appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, - const ValuePtr &sv) -{ - const PropertyDeclaration pd = m_mergedModule.item->propertyDeclaration(propertyName); - if (pd.isScalar()) - return; - if (!m_clonedModulePrototype) { - m_clonedModulePrototype = Item::create(moduleProto->pool(), ItemType::Module); - m_clonedModulePrototype->setScope(m_mergedModule.item); - m_clonedModulePrototype->setLocation(moduleProto->location()); - moduleProto->copyProperty(StringConstants::nameProperty(), m_clonedModulePrototype); - } - const ValuePtr &protoValue = moduleProto->property(propertyName); - QBS_CHECK(protoValue); - const ValuePtr clonedValue = protoValue->clone(); - lastInNextChain(sv)->setNext(clonedValue); - clonedValue->setDefiningItem(m_clonedModulePrototype); - m_clonedModulePrototype->setPropertyDeclaration(propertyName, pd); - m_clonedModulePrototype->setProperty(propertyName, clonedValue); -} - -ValuePtr ModuleMerger::lastInNextChain(const ValuePtr &v) -{ - ValuePtr n = v; - while (n->next()) - n = n->next(); - return n; -} - -const Item::Module *ModuleMerger::findModule(const Item *item, const QualifiedId &name) -{ - for (const auto &module : item->modules()) { - if (module.name == name) - return &module; - } - return nullptr; -} - -void ModuleMerger::merge(Logger &logger, Item *product, const QString &productName, - Item::Modules *topSortedModules) -{ - for (auto it = topSortedModules->begin(); it != topSortedModules->end(); ++it) - ModuleMerger(logger, product, productName, it, topSortedModules->end()).start(); -} - - - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/modulemerger.h b/src/lib/corelib/language/modulemerger.h deleted file mode 100644 index 469dc86c4..000000000 --- a/src/lib/corelib/language/modulemerger.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_MODULEMERGER_H -#define QBS_MODULEMERGER_H - -#include "item.h" -#include "qualifiedid.h" - -#include <logging/logger.h> -#include <tools/set.h> -#include <tools/version.h> - -#include <QtCore/qhash.h> - -namespace qbs { -namespace Internal { - -class ModuleMerger { -public: - static void merge(Logger &logger, Item *productItem, const QString &productName, - Item::Modules *topSortedModules); - -private: - ModuleMerger(Logger &logger, Item *productItem, const QString &productName, - const Item::Modules::iterator &modulesBegin, - const Item::Modules::iterator &modulesEnd); - - void appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, - const ValuePtr &sv); - void mergeModule(Item::PropertyMap *props, const Item::Module &m); - void replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace); - void start(); - - static ValuePtr lastInNextChain(const ValuePtr &v); - static const Item::Module *findModule(const Item *item, const QualifiedId &name); - - Logger &m_logger; - Item * const m_productItem; - Item::Module &m_mergedModule; - Item *m_clonedModulePrototype = nullptr; - Set<const Item *> m_seenInstances; - Set<Item *> m_moduleInstanceContainers; - const bool m_isBaseModule; - const bool m_isShadowProduct; - const Item::Modules::iterator m_modulesBegin; - const Item::Modules::iterator m_modulesEnd; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_MODULEMERGER_H diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h index 8ed6f008d..c35ed220a 100644 --- a/src/lib/corelib/language/moduleproviderinfo.h +++ b/src/lib/corelib/language/moduleproviderinfo.h @@ -83,18 +83,25 @@ public: QualifiedId name; QVariantMap config; QString providerFile; + bool isEager{true}; QStringList searchPaths; + QHash<QString, QStringList> searchPathsByModule; bool transientOutput = false; // Not to be serialized. }; -using ModuleProviderInfoList = std::vector<ModuleProviderInfo>; +using ModuleProvidersCacheKey = std::tuple< + QString /*name*/, + QString /*moduleName*/, + QVariantMap /*config*/, + QVariantMap /*qbsModule*/, + int /*lookup*/ +>; +using ModuleProvidersCache = QHash<ModuleProvidersCacheKey, ModuleProviderInfo>; // Persistent info stored between sessions -struct StoredModuleProviderInfo +class StoredModuleProviderInfo { - using CacheKey = std::tuple<QString /*name*/, QVariantMap /*config*/, int /*lookup*/>; - using ModuleProvidersCache = QHash<CacheKey, ModuleProviderInfo>; - +public: ModuleProvidersCache providers; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) diff --git a/src/lib/corelib/language/moduleproviderloader.cpp b/src/lib/corelib/language/moduleproviderloader.cpp deleted file mode 100644 index 49c77b7fc..000000000 --- a/src/lib/corelib/language/moduleproviderloader.cpp +++ /dev/null @@ -1,316 +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 "itemreader.h" -#include "moduleloader.h" -#include "probesresolver.h" - -#include <language/scriptengine.h> -#include <language/value.h> - -#include <logging/categories.h> -#include <logging/translator.h> - -#include <tools/fileinfo.h> -#include <tools/jsliterals.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -#include <QtCore/qtemporaryfile.h> - -namespace qbs { -namespace Internal { - -ModuleProviderLoader::ModuleProviderLoader(ItemReader *reader, Evaluator *evaluator, - ProbesResolver *probesResolver, Logger &logger) - : m_reader(reader) - , m_evaluator(evaluator) - , m_probesResolver(probesResolver) - , m_logger(logger) -{ -} - -ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvider( - ProductContext &productContext, - const CodeLocation &dependsItemLocation, - const QualifiedId &moduleName, - FallbackMode fallbackMode) -{ - ModuleProviderLoader::ModuleProviderResult result; - std::vector<Provider> providersToRun; - qCDebug(lcModuleLoader) << "Module" << moduleName.toString() - << "not found, checking for module providers"; - const auto providerNames = getModuleProviders(productContext.item); - if (providerNames) { - providersToRun = transformed<std::vector<Provider>>(*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 = findModuleProvider(providersToRun, productContext, dependsItemLocation); - - if (fallbackMode == FallbackMode::Enabled - && !result.providerFound - && !providerNames) { - qCDebug(lcModuleLoader) << "Specific module provider not found for" - << moduleName.toString() << ", setting up fallback."; - result = findModuleProvider( - {{moduleName, ModuleProviderLookup::Fallback}}, - productContext, - dependsItemLocation); - } - - return result; -} - -ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::findModuleProvider( - const std::vector<Provider> &providers, - ProductContext &product, - const CodeLocation &dependsItemLocation) -{ - if (providers.empty()) - return {}; - QStringList allSearchPaths; - ModuleProviderResult result; - for (const auto &[name, lookupType] : providers) { - const QVariantMap config = moduleProviderConfig(product).value(name.toString()).toMap(); - ModuleProviderInfo &info = - m_storedModuleProviderInfo.providers[{name.toString(), config, 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; - info.searchPaths = getProviderSearchPaths( - name, info.providerFile, product, config, dependsItemLocation); - 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 << "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; - - m_reader->pushExtraSearchPaths(allSearchPaths); - result.providerAddedSearchPaths = true; - - return result; -} - -QVariantMap ModuleProviderLoader::moduleProviderConfig( - ProductContext &product) -{ - if (product.theModuleProviderConfig) - return *product.theModuleProviderConfig; - QVariantMap providerConfig; - const ItemValueConstPtr configItemValue = - product.item->itemProperty(StringConstants::moduleProviders()); - if (configItemValue) { - const std::function<void(const Item *, QualifiedId)> 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<ItemValue *>(it.value().get())->item(); - childItem->setScope(item->scope()); - collectMap(childItem, QualifiedId(name) << it.key()); - continue; - } - case Value::JSSourceValueType: - value = m_evaluator->value(item, it.key()).toVariant(); - break; - case Value::VariantValueType: - value = static_cast<VariantValue *>(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.item); - 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 *(product.theModuleProviderConfig = std::move(providerConfig)); -} - -std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item) -{ - while (item) { - const auto providers = - m_evaluator->optionalStringListValue(item, StringConstants::qbsModuleProviders()); - if (providers) { - return transformed<std::vector<QualifiedId>>(*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 {}; -} - -QStringList ModuleProviderLoader::getProviderSearchPaths( - const QualifiedId &name, - const QString &providerFile, - ProductContext &product, - const QVariantMap &moduleConfig, - const CodeLocation &location) -{ - 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.project->item->variantProperty( - StringConstants::buildDirectoryProperty())->value().toString(); - const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); - 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(moduleConfig) << ')' << 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->readFile(dummyItemFile.fileName(), location); - 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->setParent(product.item); - providerItem->overrideProperties(moduleConfig, name.toString(), m_parameters, m_logger); - - m_probesResolver->resolveProbes(&product, providerItem); - - EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider); - return m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths")); -} - -} // 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 b0571a548..000000000 --- a/src/lib/corelib/language/moduleproviderloader.h +++ /dev/null @@ -1,134 +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 "moduleloader.h" -#include "moduleproviderinfo.h" -#include "probesresolver.h" - -#include <QtCore/qmap.h> -#include <QtCore/qvariant.h> - -namespace qbs { -namespace Internal { - -class Logger; - -class ModuleProviderLoader -{ -public: - using ProductContext = ModuleLoader::ProductContext; - using FallbackMode = ModuleLoader::FallbackMode; - explicit ModuleProviderLoader(ItemReader *itemReader, Evaluator *evaluator, - ProbesResolver *probesResolver, Logger &logger); - - enum class ModuleProviderLookup { Scoped, Named, Fallback }; - - struct Provider - { - QualifiedId name; - ModuleProviderLookup lookup; - }; - - struct ModuleProviderResult - { - ModuleProviderResult() = default; - ModuleProviderResult(bool ran, bool added) - : providerFound(ran), providerAddedSearchPaths(added) {} - bool providerFound = false; - bool providerAddedSearchPaths = false; - }; - - const StoredModuleProviderInfo &storedModuleProviderInfo() const - { - return m_storedModuleProviderInfo; - } - - void setStoredModuleProviderInfo(StoredModuleProviderInfo moduleProviderInfo) - { - m_storedModuleProviderInfo = std::move(moduleProviderInfo); - } - - void setProjectParameters(SetupProjectParameters parameters) - { - m_parameters = std::move(parameters); - } - - ModuleProviderResult executeModuleProvider( - ProductContext &productContext, - const CodeLocation &dependsItemLocation, - const QualifiedId &moduleName, - FallbackMode fallbackMode); - ModuleProviderResult findModuleProvider( - const std::vector<Provider> &providers, - ProductContext &product, - const CodeLocation &dependsItemLocation); - QVariantMap moduleProviderConfig(ProductContext &product); - - const Set<QString> &tempQbsFiles() const { return m_tempQbsFiles; } - - std::optional<std::vector<QualifiedId>> getModuleProviders(Item *item); - -private: - QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType); - QStringList getProviderSearchPaths( - const QualifiedId &name, - const QString &providerFile, - ProductContext &product, - const QVariantMap &moduleConfig, - const CodeLocation &location); - -private: - ItemReader *const m_reader{nullptr}; - Evaluator *const m_evaluator{nullptr}; - ProbesResolver *const m_probesResolver{nullptr}; - - SetupProjectParameters m_parameters; - Logger &m_logger; - StoredModuleProviderInfo m_storedModuleProviderInfo; - Set<QString> m_tempQbsFiles; -}; - -} // namespace Internal -} // namespace qbs - -#endif // MODULEPROVIDERLOADER_H diff --git a/src/lib/corelib/language/preparescriptobserver.cpp b/src/lib/corelib/language/preparescriptobserver.cpp index 632cbfb51..11536e74d 100644 --- a/src/lib/corelib/language/preparescriptobserver.cpp +++ b/src/lib/corelib/language/preparescriptobserver.cpp @@ -48,8 +48,6 @@ #include <tools/stlutils.h> #include <tools/stringconstants.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { @@ -58,22 +56,23 @@ PrepareScriptObserver::PrepareScriptObserver(ScriptEngine *engine, UnobserveMode { } -void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) +void PrepareScriptObserver::onPropertyRead(const JSValue &object, const QString &name, + const JSValue &value) { - const auto objectId = object.objectId(); + JSContext * const ctx = engine()->context(); + const auto objectId = jsObjectId(object); const auto projectIt = m_projectObjectIds.find(objectId); if (projectIt != m_projectObjectIds.cend()) { engine()->addPropertyRequestedInScript( - Property(projectIt->second, QString(), name, value.toVariant(), + Property(projectIt->second, QString(), name, getJsVariant(ctx, value), Property::PropertyInProject)); return; } if (m_importIds.contains(objectId)) { - engine()->addImportRequestedInScript(object.objectId()); + engine()->addImportRequestedInScript(jsObjectId(object)); return; } - const auto exportsIt = m_exportsObjectIds.find(value.objectId()); + const auto exportsIt = m_exportsObjectIds.find(jsObjectId(object)); if (exportsIt != m_exportsObjectIds.cend()) { engine()->addRequestedExport(exportsIt->second); return; @@ -81,13 +80,14 @@ void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QSt const auto it = m_parameterObjects.find(objectId); if (it != m_parameterObjects.cend()) { engine()->addPropertyRequestedInScript( - Property(it->second.first, it->second.second, name, value.toVariant(), + Property(it->second.first, it->second.second, name, getJsVariant(ctx, value), Property::PropertyInParameters)); } if (name == StringConstants::fileTagsProperty() && m_artifactIds.contains(objectId)) { - const Artifact * const artifact = attachedPointer<Artifact>(object); + const Artifact * const artifact = attachedPointer<Artifact>(object, + engine()->dataWithPtrClass()); QBS_CHECK(artifact); - const Property p(artifact->product->uniqueName(), QString(), name, value.toVariant(), + const Property p(artifact->product->uniqueName(), QString(), name, getJsVariant(ctx, value), Property::PropertyInArtifact); engine()->addPropertyRequestedFromArtifact(artifact, p); } diff --git a/src/lib/corelib/language/preparescriptobserver.h b/src/lib/corelib/language/preparescriptobserver.h index 36e395efc..5dc54cbb6 100644 --- a/src/lib/corelib/language/preparescriptobserver.h +++ b/src/lib/corelib/language/preparescriptobserver.h @@ -44,6 +44,8 @@ #include <tools/set.h> +#include <quickjs.h> + #include <QtCore/qstring.h> #include <unordered_map> @@ -69,7 +71,7 @@ public: } void addArtifactId(qint64 artifactId) { m_artifactIds.insert(artifactId); } - bool addImportId(qint64 importId) { return m_importIds.insert(importId).second; } + bool addImportId(quintptr importId) { return m_importIds.insert(importId).second; } void clearImportIds() { m_importIds.clear(); } void addParameterObjectId(qint64 id, const QString &productName, const QString &depName, const QualifiedId &moduleName) @@ -79,14 +81,13 @@ public: m_parameterObjects.insert(std::make_pair(id, value)); } -private: - void onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) override; + void onPropertyRead(const JSValue &object, const QString &name, const JSValue &value) override; +private: std::unordered_map<qint64, QString> m_projectObjectIds; std::unordered_map<qint64, std::pair<QString, QString>> m_parameterObjects; std::unordered_map<qint64, const ResolvedProduct *> m_exportsObjectIds; - Set<qint64> m_importIds; + Set<quintptr> m_importIds; Set<qint64> m_artifactIds; }; diff --git a/src/lib/corelib/language/probesresolver.cpp b/src/lib/corelib/language/probesresolver.cpp deleted file mode 100644 index 059080155..000000000 --- a/src/lib/corelib/language/probesresolver.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Copyright (C) 2022 Raphaël Cotty <raphael.cotty@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 "probesresolver.h" - -#include "builtindeclarations.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "itemreader.h" -#include "language.h" -#include "modulemerger.h" -#include "qualifiedid.h" -#include "scriptengine.h" -#include "value.h" - -#include <api/languageinfo.h> -#include <language/language.h> -#include <logging/categories.h> -#include <logging/logger.h> -#include <logging/translator.h> -#include <tools/profiling.h> -#include <tools/stringconstants.h> - -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(Evaluator *evaluator, Logger &logger) - : m_evaluator(evaluator) - , m_logger(logger) -{ -} - -void ProbesResolver::setProjectParameters(SetupProjectParameters parameters) -{ - m_parameters = std::move(parameters); - m_elapsedTimeProbes = m_probesEncountered = m_probesRun = m_probesCachedCurrent - = m_probesCachedOld = 0; -} - -void ProbesResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) -{ - m_oldProjectProbes.clear(); - for (const ProbeConstPtr& probe : oldProbes) - m_oldProjectProbes[probe->globalId()] << probe; -} - -void ProbesResolver::setOldProductProbes( - const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) -{ - m_oldProductProbes = oldProbes; -} - -void ProbesResolver::resolveProbes(ModuleLoader::ProductContext *productContext, Item *item) -{ - AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr); - EvalContextSwitcher evalContextSwitcher(m_evaluator->engine(), EvalContext::ProbeExecution); - for (Item * const child : item->children()) - if (child->type() == ItemType::Probe) - resolveProbe(productContext, item, child); -} - -void ProbesResolver::resolveProbe(ModuleLoader::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<QString, QScriptValue>; - std::vector<ProbeProperty> probeBindings; - 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 QScriptValue value = m_evaluator->value(probe, name); - probeBindings << ProbeProperty(name, value); - if (name != StringConstants::conditionProperty()) - initialProperties.insert(name, value.toVariant()); - } - } - ScriptEngine * const engine = m_evaluator->engine(); - QScriptValue configureScope; - 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 { - const QString &uniqueProductName = productContext->uniqueName(); - resolvedProbe - = findOldProductProbe(uniqueProductName, 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; - } - std::vector<QString> 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()); - engine->currentContext()->pushScope(fileCtxScopes.fileScope); - engine->currentContext()->pushScope(fileCtxScopes.importScope); - configureScope = engine->newObject(); - for (const ProbeProperty &b : probeBindings) - configureScope.setProperty(b.first, b.second); - engine->currentContext()->pushScope(configureScope); - engine->clearRequestedProperties(); - QScriptValue sv = engine->evaluate(configureScript->sourceCodeForEvaluation()); - engine->currentContext()->popScope(); - engine->currentContext()->popScope(); - engine->currentContext()->popScope(); - engine->releaseResourcesOfScriptObjects(); - if (Q_UNLIKELY(engine->hasErrorOrException(sv))) - throw ErrorInfo(engine->lastErrorString(sv), configureScript->location()); - 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) { - QScriptValue v = configureScope.property(b.first); - m_evaluator->convertToPropertyType(probe->propertyDeclaration( - b.first), probe->location(), v); - if (Q_UNLIKELY(engine->hasErrorOrException(v))) - throw ErrorInfo(engine->lastError(v)); - newValue = v.toVariant(); - } else { - newValue = initialProperties.value(b.first); - } - } - if (newValue != b.second.toVariant()) - 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; - } - productContext->info.probes << 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<ProbeConstPtr> &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() -{ - if (!m_parameters.logElapsedTime()) - return; - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("Running Probes took %1.") - .arg(elapsedTimeString(m_elapsedTimeProbes)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("%1 probes encountered, %2 configure scripts executed, " - "%3 re-used from current run, %4 re-used from earlier run.") - .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 1aeec27ce..000000000 --- a/src/lib/corelib/language/probesresolver.h +++ /dev/null @@ -1,92 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Copyright (C) 2022 Raphaël Cotty <raphael.cotty@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 PROBESRESOLVER_H -#define PROBESRESOLVER_H - -#include "moduleloader.h" - -namespace qbs { -namespace Internal { - -class ProbesResolver -{ -public: - explicit ProbesResolver(Evaluator *evaluator, Logger &logger); - void setProjectParameters(SetupProjectParameters parameters); - void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes); - void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); - void resolveProbes(ModuleLoader::ProductContext *productContext, Item *item); - void resolveProbe(ModuleLoader::ProductContext *productContext, Item *parent, Item *probe); - void printProfilingInfo(); - -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; - - qint64 m_elapsedTimeProbes = 0; - quint64 m_probesEncountered = 0; - quint64 m_probesRun = 0; - quint64 m_probesCachedCurrent = 0; - quint64 m_probesCachedOld = 0; - - SetupProjectParameters m_parameters; - Evaluator *m_evaluator = nullptr; - Logger &m_logger; - QHash<QString, std::vector<ProbeConstPtr>> m_oldProjectProbes; - QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes; - FileTime m_lastResolveTime; - QHash<CodeLocation, std::vector<ProbeConstPtr>> m_currentProbes; -}; - -} // namespace Internal -} // namespace qbs - -#endif // PROBESRESOLVER_H diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp deleted file mode 100644 index d7ae11aaf..000000000 --- a/src/lib/corelib/language/projectresolver.cpp +++ /dev/null @@ -1,1920 +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 <jsextensions/jsextensions.h> -#include <jsextensions/moduleproperties.h> -#include <logging/categories.h> -#include <logging/translator.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/joblimits.h> -#include <tools/jsliterals.h> -#include <tools/profiling.h> -#include <tools/progressobserver.h> -#include <tools/scripttools.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/setupprojectparameters.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdir.h> -#include <QtCore/qregularexpression.h> - -#include <algorithm> -#include <memory> -#include <queue> - -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<FileTaggerConstPtr> fileTaggers; - std::vector<RulePtr> rules; - JobLimits jobLimits; - ResolvedModulePtr dummyModule; -}; - -struct ProjectResolver::ProductContext -{ - ResolvedProductPtr product; - QString buildDirectory; - Item *item = nullptr; - using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>; - QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter; - ProjectResolver::FileLocations sourceArtifactLocations; - GroupConstPtr currentGroup; -}; - -struct ProjectResolver::ModuleContext -{ - ResolvedModulePtr module; - JobLimits jobLimits; -}; - -class CancelException { }; - - -ProjectResolver::ProjectResolver(Evaluator *evaluator, ModuleLoaderResult 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<ResolvedProductPtr> 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<const JSSourceValue>( - 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<QString> subProjectNames; - Set<ResolvedProjectPtr> 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())); - const 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(projectContext); - 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); - projectProperties.insert(it.key(), m_evaluator->value(item, it.key()).toVariant()); - } - 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); - try { - resolveProductFully(item, projectContext); - } catch (const ErrorInfo &e) { - 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_productsByName.insert(product->uniqueName(), product); - product->enabled = product->enabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item]; - if (pi.delayedError.hasError()) { - ErrorInfo errorInfo; - - // First item is "main error", gets prepended again in the catch clause. - const QList<ErrorItem> &items = pi.delayedError.items(); - for (int i = 1; i < items.size(); ++i) - errorInfo.append(items.at(i)); - - pi.delayedError.clear(); - throw errorInfo; - } - 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->scriptValue(item), product.get()); - - QList<Item *> 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_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.isProduct, 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) -{ - product->fileTags = m_evaluator->fileTagsValue(item, StringConstants::typeProperty()); - for (const Item::Module &m : item->modules()) { - if (m.item->isPresentModule()) { - product->fileTags += m_evaluator->fileTagsValue(m.item, - StringConstants::additionalProductTypesProperty()); - } - } - item->setProperty(StringConstants::typeProperty(), - VariantValue::create(sorted(product->fileTags.toStringList()))); -} - -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<QualifiedId> initialProps, - const PropertyDependencies &deps) -{ - std::deque<QualifiedId> 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<std::deque<QualifiedId>>(propsSetInGroup), - m_evaluator->propertyDependencies()); - - // Step 3: Evaluate all these properties and replace their values in the map - QVariantMap modulesMap = currentValues; - QHash<QString, QStringList> 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_evaluator->setPathPropertiesBaseDir(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)); - } - m_evaluator->clearPathPropertiesBaseDir(); - 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>(); - SourceWildCards *wildcards = group->wildcards.get(); - wildcards->group = group.get(); - wildcards->excludePatterns = m_evaluator->stringListValue( - item, StringConstants::excludeFilesProperty()); - wildcards->patterns = patterns; - const Set<QString> 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<QVariant(const QVariantMap &, const QVariant &)> mapper - = [&stringListMapper, &mapper]( - const QVariantMap &mappings, const QVariant &value) -> QVariant { - switch (static_cast<QMetaType::Type>(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<QString> directDepNames; - for (const Item::Module &m : importingProductItem->modules()) { - if (m.name.toString() == exportingProduct->name) { - for (const Item::Module &dep : m.item->modules()) { - if (dep.isProduct) - directDepNames.push_back(dep.name.toString()); - } - break; - } - } - const ModuleLoaderResult::ProductInfo &importingProductInfo - = mapValue(m_loadResult.productInfos, importingProductItem); - const ProductDependencyInfos &depInfos - = getProductDependencies(dummyProduct, importingProductInfo); - for (const auto &dep : depInfos.dependencies) { - if (dep.product == exportingProduct) - continue; - - // Filter out indirect dependencies. - // TODO: Depends items using "profile" or "productTypes" will not work. - if (!contains(directDepNames, dep.product->name)) - continue; - - if (!contains(exportingProduct->exportedModule.productDependencies, - dep.product->uniqueName())) { - exportingProduct->exportedModule.productDependencies.push_back( - dep.product->uniqueName()); - } - if (!dep.parameters.isEmpty()) { - exportingProduct->exportedModule.dependencyParameters.insert(dep.product, - dep.parameters); - } - } - 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<ExportedProperty> &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<ItemValue>(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<VariantValue>(v)->value()); - } else { - QBS_CHECK(v->type() == Value::JSSourceValueType); - const JSSourceValue * const sv = static_cast<JSSourceValue *>(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<ExportedItem> ProjectResolver::resolveExportChild(const Item *item, - const ExportedModule &module) -{ - std::unique_ptr<ExportedItem> 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<QString, ValuePtr>::const_iterator it = obj->properties().constBegin(); - it != obj->properties().constEnd(); ++it) - { - if (it.value()->type() != Value::ItemValueType) - continue; - resolveRuleArtifactBinding(artifact, - std::static_pointer_cast<ItemValue>(it.value())->item(), - QStringList(it.key()), &seenBindings); - } - } -} - -void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, - Item *item, - const QStringList &namePrefix, - QualifiedIdSet *seenBindings) -{ - for (QMap<QString, ValuePtr>::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<ItemValue>(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<JSSourceValue>(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<FileTaggerConstPtr> &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); -} - -ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies( - const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo) -{ - ProductDependencyInfos result; - result.dependencies.reserve(productInfo.usedProducts.size()); - for (const auto &dependency : productInfo.usedProducts) { - QBS_CHECK(!dependency.name.isEmpty()); - if (dependency.profile == StringConstants::star()) { - for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) { - if (p->name != dependency.name || p == product || !p->enabled - || (dependency.limitToSubProject && !product->isInParentProject(p))) { - continue; - } - result.dependencies.emplace_back(p, dependency.parameters); - } - } else { - ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName()); - const QString depDisplayName = ResolvedProduct::fullDisplayName(dependency.name, - dependency.multiplexConfigurationId); - if (!usedProduct) { - throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.") - .arg(product->fullDisplayName(), depDisplayName), - product->location); - } - if (!dependency.profile.isEmpty() && usedProduct->profile() != dependency.profile) { - usedProduct.reset(); - for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) { - if (p->name == dependency.name && p->profile() == dependency.profile) { - usedProduct = p; - break; - } - } - if (!usedProduct) { - throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist " - "for the requested profile '%3'.") - .arg(product->fullDisplayName(), depDisplayName, - dependency.profile), - product->location); - } - } - if (!usedProduct->enabled) { - if (!dependency.isRequired) - continue; - ErrorInfo e; - e.append(Tr::tr("Product '%1' depends on '%2',") - .arg(product->name, usedProduct->name), product->location); - e.append(Tr::tr("but product '%1' is disabled.").arg(usedProduct->name), - usedProduct->location); - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw e; - result.hasDisabledDependency = true; - } - result.dependencies.emplace_back(usedProduct, dependency.parameters); - } - } - return result; -} - -void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector<SourceArtifactPtr> &artifacts) -{ - 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(Item * item, Item *newScope) : m_item(item), m_oldScope(item->scope()) - { - item->setScope(newScope); - } - ~TempScopeSetter() { m_item->setScope(m_oldScope); } -private: - Item * const m_item; - Item * const m_oldScope; -}; - -void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance) -{ - if (!productModuleInstance->isPresentModule()) - return; - Item * const exportItem = productModuleInstance->prototype(); - QBS_CHECK(exportItem && exportItem->type() == ItemType::Export); - TempScopeSetter tempScopeSetter(exportItem, productModuleInstance->scope()); - 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 { - 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.isProduct || 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; - TempScopeSetter tempScopeSetter(modulePrototype, module.item->scope()); - 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); -} - -static bool hasDependencyCycle(Set<ResolvedProduct *> *checked, - Set<ResolvedProduct *> *branch, - const ResolvedProductPtr &product, - ErrorInfo *error) -{ - if (branch->contains(product.get())) - return true; - if (checked->contains(product.get())) - return false; - checked->insert(product.get()); - branch->insert(product.get()); - for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { - if (hasDependencyCycle(checked, branch, dep, error)) { - error->prepend(dep->name, dep->location); - return true; - } - } - branch->remove(product.get()); - return false; -} - -using DependencyMap = QHash<ResolvedProduct *, Set<ResolvedProduct *>>; -void gatherDependencies(ResolvedProduct *product, DependencyMap &dependencies) -{ - if (dependencies.contains(product)) - return; - // Hold locally because the QHash references aren't stable in Qt6. - Set<ResolvedProduct *> productDeps = dependencies[product]; - for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { - productDeps << dep.get(); - gatherDependencies(dep.get(), dependencies); - productDeps += dependencies.value(dep.get()); - } - // Now that we gathered the dependencies, put them in the map. - dependencies[product] = std::move(productDeps); -} - - - -static DependencyMap allDependencies(const std::vector<ResolvedProductPtr> &products) -{ - DependencyMap dependencies; - for (const ResolvedProductPtr &product : products) - gatherDependencies(product.get(), dependencies); - return dependencies; -} - -void ProjectResolver::resolveProductDependencies(const ProjectContext &projectContext) -{ - // Resolve all inter-product dependencies. - const std::vector<ResolvedProductPtr> allProducts = projectContext.project->allProducts(); - bool disabledDependency = false; - for (const ResolvedProductPtr &rproduct : allProducts) { - if (!rproduct->enabled) - continue; - Item *productItem = m_productItemMap.value(rproduct); - const ModuleLoaderResult::ProductInfo &productInfo - = mapValue(m_loadResult.productInfos, productItem); - const ProductDependencyInfos &depInfos = getProductDependencies(rproduct, productInfo); - if (depInfos.hasDisabledDependency) - disabledDependency = true; - for (const auto &dep : depInfos.dependencies) { - if (!contains(rproduct->dependencies, dep.product)) - rproduct->dependencies.push_back(dep.product); - if (!dep.parameters.empty()) - rproduct->dependencyParameters.insert(dep.product, dep.parameters); - } - } - - // Check for cyclic dependencies. - Set<ResolvedProduct *> checked; - for (const ResolvedProductPtr &rproduct : allProducts) { - Set<ResolvedProduct *> branch; - ErrorInfo error; - if (hasDependencyCycle(&checked, &branch, rproduct, &error)) { - error.prepend(rproduct->name, rproduct->location); - error.prepend(Tr::tr("Cyclic dependencies detected.")); - throw error; - } - } - - // Mark all products as disabled that have a disabled dependency. - if (disabledDependency && m_setupParams.productErrorMode() == ErrorHandlingMode::Relaxed) { - const DependencyMap allDeps = allDependencies(allProducts); - DependencyMap allDepsReversed; - for (auto it = allDeps.constBegin(); it != allDeps.constEnd(); ++it) { - for (ResolvedProduct *dep : qAsConst(it.value())) - allDepsReversed[dep] << it.key(); - } - for (auto it = allDepsReversed.constBegin(); it != allDepsReversed.constEnd(); ++it) { - if (it.key()->enabled) - continue; - for (ResolvedProduct * const dependingProduct : qAsConst(it.value())) { - if (dependingProduct->enabled) { - m_logger.qbsWarning() << Tr::tr("Disabling product '%1', because it depends on " - "disabled product '%2'.") - .arg(dependingProduct->name, it.key()->name); - dependingProduct->enabled = false; - } - } - } - } -} - -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<QString, ValuePtr>::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) -{ - 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 QScriptValue scriptValue = m_evaluator->property(item, propName); - if (checkErrors && Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(scriptValue))) { - throw ErrorInfo(m_evaluator->engine()->lastError(scriptValue, - propValue->location())); - } - - // NOTE: Loses type information if scriptValue.isUndefined == true, - // as such QScriptValues become invalid QVariants. - QVariant v; - if (scriptValue.isFunction()) { - v = scriptValue.toString(); - } else { - v = scriptValue.toVariant(); - QVariantMap m = v.toMap(); - if (m.contains(StringConstants::importScopeNamePropertyInternal())) { - QVariantMap tmp = m; - m = scriptValue.prototype().toVariant().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<VariantValue>(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<ItemValue>(value)->item(); - if (itemValueItem->type() == ItemType::ModuleInstance) { - struct EvalPreparer { - EvalPreparer(Item *valueItem, Item *moduleInstance, const QualifiedId &moduleName) - : valueItem(valueItem), oldScope(valueItem->scope()), - hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) - { - valueItem->setScope(moduleInstance); - if (!hadName) { - // EvaluatorScriptClass expects a name here. - valueItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(moduleName.toString())); - } - } - ~EvalPreparer() - { - valueItem->setScope(oldScope); - if (!hadName) - valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr()); - } - Item * const valueItem; - Item * const oldScope; - const bool hadName; - }; - EvalPreparer ep(itemValueItem, moduleInstance, moduleName); - 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_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory); - product->moduleProperties->setValue(evaluateModuleValues(m_productContext->item)); - product->productProperties = evaluateProperties(m_productContext->item, m_productContext->item, - QVariantMap(), true, true); - m_evaluator->clearPathPropertiesBaseDir(); -} - -void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, - 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 52d835535..000000000 --- a/src/lib/corelib/language/projectresolver.h +++ /dev/null @@ -1,208 +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 "itemtype.h" -#include "moduleloader.h" -#include "qualifiedid.h" - -#include <logging/logger.h> -#include <tools/set.h> - -#include <QtCore/qhash.h> -#include <QtCore/qmap.h> -#include <QtCore/qstringlist.h> - -#include <utility> -#include <vector> - -namespace qbs { -class JobLimits; -namespace Internal { - -class Evaluator; -class Item; -class ProgressObserver; -class ScriptEngine; - -class ProjectResolver -{ -public: - ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult, - SetupProjectParameters setupParameters, Logger &logger); - ~ProjectResolver(); - - void setProgressObserver(ProgressObserver *observer); - TopLevelProjectPtr resolve(); - - static void applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product); - - using FileLocations = QHash<std::pair<QString, QString>, 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<ExportedItem> 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(const ProjectContext &projectContext); - 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; - }; - - struct ProductDependencyInfos - { - std::vector<ProductDependencyInfo> dependencies; - bool hasDisabledDependency = false; - }; - - ProductDependencyInfos getProductDependencies(const ResolvedProductConstPtr &product, - const ModuleLoaderResult::ProductInfo &productInfo); - QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, - const PropertyDeclaration &decl) const; - QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const; - static void matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector<SourceArtifactPtr> &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<ExportedProperty> &properties); - - Evaluator *m_evaluator = nullptr; - Logger &m_logger; - ScriptEngine *m_engine = nullptr; - ProgressObserver *m_progressObserver = nullptr; - ProductContext *m_productContext = nullptr; - ModuleContext *m_moduleContext = nullptr; - QMap<QString, ResolvedProductPtr> m_productsByName; - QHash<FileTag, QList<ResolvedProductPtr> > m_productsByType; - QHash<ResolvedProductPtr, Item *> m_productItemMap; - mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap; - mutable QHash<CodeLocation, ScriptFunctionPtr> m_scriptFunctionMap; - mutable QHash<std::pair<QStringView, QStringList>, QString> m_scriptFunctions; - mutable QHash<QStringView, QString> m_sourceCode; - const SetupProjectParameters m_setupParams; - ModuleLoaderResult m_loadResult; - Set<CodeLocation> m_groupLocationWarnings; - std::vector<std::pair<ResolvedProductPtr, Item *>> m_productExportInfo; - std::vector<ErrorInfo> 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<ItemType, ItemFuncPtr>; - void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); -}; - -} // namespace Internal -} // namespace qbs - -#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp index 2dbe41afd..d56ab3bb0 100644 --- a/src/lib/corelib/language/propertydeclaration.cpp +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -40,11 +40,16 @@ #include "propertydeclaration.h" #include "deprecationinfo.h" -#include <api/languageinfo.h> +#include "filecontext.h" +#include "item.h" +#include "qualifiedid.h" +#include "value.h" +#include <api/languageinfo.h> +#include <loader/loaderutils.h> #include <logging/translator.h> - #include <tools/error.h> +#include <tools/setupprojectparameters.h> #include <tools/qttools.h> #include <tools/stringconstants.h> @@ -101,7 +106,6 @@ public: DeprecationInfo deprecationInfo; }; - PropertyDeclaration::PropertyDeclaration() : d(new PropertyDeclarationData) { @@ -275,7 +279,12 @@ void PropertyDeclaration::setDeprecationInfo(const DeprecationInfo &deprecationI d->deprecationInfo = deprecationInfo; } -// see also: EvaluatorScriptClass::convertToPropertyType() +ErrorInfo PropertyDeclaration::checkForDeprecation(DeprecationWarningMode mode, + const CodeLocation &loc, Logger &logger) const +{ + return deprecationInfo().checkForDeprecation(mode, name(), loc, false, logger); +} + QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t, const QStringList &namePrefix, const QString &key) { @@ -299,5 +308,207 @@ QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t, return c; } +QVariant PropertyDeclaration::typedNullValue() const +{ + switch (type()) { + case PropertyDeclaration::Boolean: + return typedNullVariant<bool>(); + case PropertyDeclaration::Integer: + return typedNullVariant<int>(); + case PropertyDeclaration::VariantList: + return typedNullVariant<QVariantList>(); + case PropertyDeclaration::String: + case PropertyDeclaration::Path: + return typedNullVariant<QString>(); + case PropertyDeclaration::StringList: + case PropertyDeclaration::PathList: + return typedNullVariant<QStringList>(); + default: + return {}; + } +} + +bool PropertyDeclaration::shouldCheckAllowedValues() const +{ + return isValid() + && (d->type == PropertyDeclaration::String || d->type == PropertyDeclaration::StringList) + && !d->allowedValues.empty(); +} + +void PropertyDeclaration::checkAllowedValues( + const QVariant &value, + const CodeLocation &loc, + const QString &key, + LoaderState &loaderState) const +{ + const auto type = d->type; + if (!shouldCheckAllowedValues()) + return; + + if (value.isNull()) + return; + + const auto &allowedValues = d->allowedValues; + + const auto checkValue = [&loc, &allowedValues, &key, &loaderState](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, loaderState.parameters(), loaderState.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()); + } +} + +namespace { +class PropertyDeclarationCheck : public ValueHandler +{ +public: + PropertyDeclarationCheck(LoaderState &loaderState) : m_loaderState(loaderState) {} + void operator()(Item *item) + { + m_checkingProject = item->type() == ItemType::Project; + handleItem(item); + } + +private: + void handle(JSSourceValue *value) override + { + if (!value->createdByPropertiesBlock()) { + const ErrorInfo error(Tr::tr("Property '%1' is not declared.") + .arg(m_currentName), value->location()); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); + } + } + void handle(ItemValue *value) override + { + if (checkItemValue(value)) + handleItem(value->item()); + } + bool checkItemValue(ItemValue *value) + { + // TODO: Remove once QBS-1030 is fixed. + if (parentItem()->type() == ItemType::Artifact) + return false; + + if (parentItem()->type() == ItemType::Properties) + return false; + + // TODO: Check where the in-between module instances come from. + if (value->item()->type() == ItemType::ModuleInstancePlaceholder) { + for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) { + if ((*it)->type() == ItemType::Group) + return false; + if ((*it)->type() == ItemType::ModulePrefix) + continue; + break; + } + } + + if (value->item()->type() != ItemType::ModuleInstance + && value->item()->type() != ItemType::ModulePrefix + && (!parentItem()->file() || !parentItem()->file()->idScope() + || !parentItem()->file()->idScope()->hasProperty(m_currentName)) + && !value->createdByPropertiesBlock()) { + CodeLocation location = value->location(); + for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i) + location = m_parentItems.at(i)->location(); + const ErrorInfo error(Tr::tr("Item '%1' is not declared. " + "Did you forget to add a Depends item?") + .arg(m_currentModuleName.toString()), location); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); + return false; + } + + return true; + } + void handleItem(Item *item) + { + if (m_checkingProject && item->type() == ItemType::Product) + return; + if (!m_handledItems.insert(item).second) + return; + if (item->type() == ItemType::Module + || item->type() == ItemType::Export + || (item->type() == ItemType::ModuleInstance && !item->isPresentModule()) + || item->type() == ItemType::Properties + + // The Properties child of a SubProject item is not a regular item. + || item->type() == ItemType::PropertiesInSubProject + + || m_loaderState.topLevelProject().isDisabledItem(item)) { + return; + } + + m_parentItems.push_back(item); + for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) { + if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() + && it.value()->type() == Value::ItemValueType) + continue; + const PropertyDeclaration decl = item->propertyDeclaration(it.key()); + if (decl.isValid()) { + const ErrorInfo deprecationError = decl.checkForDeprecation( + m_loaderState.parameters().deprecationWarningMode(), it.value()->location(), + m_loaderState.logger()); + if (deprecationError.hasError()) { + handlePropertyError(deprecationError, m_loaderState.parameters(), + m_loaderState.logger()); + } + continue; + } + m_currentName = it.key(); + const QualifiedId oldModuleName = m_currentModuleName; + if (parentItem()->type() != ItemType::ModulePrefix) + m_currentModuleName.clear(); + m_currentModuleName.push_back(m_currentName); + it.value()->apply(this); + m_currentModuleName = oldModuleName; + } + m_parentItems.pop_back(); + for (Item * const child : item->children()) { + switch (child->type()) { + case ItemType::Export: + case ItemType::Depends: + case ItemType::Parameter: + case ItemType::Parameters: + break; + case ItemType::Group: + if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance) + break; + Q_FALLTHROUGH(); + default: + handleItem(child); + } + } + } + void handle(VariantValue *) override { /* only created internally - no need to check */ } + + Item *parentItem() const { return m_parentItems.back(); } + + LoaderState &m_loaderState; + Set<Item *> m_handledItems; + std::vector<Item *> m_parentItems; + QualifiedId m_currentModuleName; + QString m_currentName; + bool m_checkingProject = false; +}; +} // namespace + +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState) +{ + (PropertyDeclarationCheck(loaderState))(topLevelItem); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h index 137315d14..79a39ecbd 100644 --- a/src/lib/corelib/language/propertydeclaration.h +++ b/src/lib/corelib/language/propertydeclaration.h @@ -40,6 +40,8 @@ #ifndef QBS_PROPERTYDECLARATION_H #define QBS_PROPERTYDECLARATION_H +#include <tools/deprecationwarningmode.h> + #include <QtCore/qshareddata.h> #include <QtCore/qstring.h> @@ -48,9 +50,14 @@ class QVariant; QT_END_NAMESPACE namespace qbs { +class CodeLocation; +class ErrorInfo; namespace Internal { class DeprecationInfo; class PropertyDeclarationData; +class Item; +class LoaderState; +class Logger; class PropertyDeclaration { @@ -116,14 +123,27 @@ public: bool isExpired() const; const DeprecationInfo &deprecationInfo() const; void setDeprecationInfo(const DeprecationInfo &deprecationInfo); + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const CodeLocation &loc, + Logger &logger) const; static QVariant convertToPropertyType( const QVariant &v, Type t, const QStringList &namePrefix, const QString &key); + QVariant typedNullValue() const; + + bool shouldCheckAllowedValues() const; + void checkAllowedValues( + const QVariant &value, + const CodeLocation &loc, + const QString &key, + LoaderState &loaderState) const; private: QSharedDataPointer<PropertyDeclarationData> d; }; +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState); + + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h index 83e18ba48..af551cf6f 100644 --- a/src/lib/corelib/language/propertymapinternal.h +++ b/src/lib/corelib/language/propertymapinternal.h @@ -77,7 +77,7 @@ private: inline bool operator==(const PropertyMapInternal &lhs, const PropertyMapInternal &rhs) { - return lhs.m_value == rhs.m_value; + return qVariantsEqual(lhs.m_value, rhs.m_value); } QVariant QBS_AUTOTEST_EXPORT moduleProperty(const QVariantMap &properties, diff --git a/src/lib/corelib/language/qualifiedid.cpp b/src/lib/corelib/language/qualifiedid.cpp index 9eb0e9463..87248ac21 100644 --- a/src/lib/corelib/language/qualifiedid.cpp +++ b/src/lib/corelib/language/qualifiedid.cpp @@ -58,7 +58,7 @@ QualifiedId::QualifiedId(const QStringList &nameParts) QualifiedId QualifiedId::fromString(const QString &str) { - return QualifiedId(str.split(QLatin1Char('.'))); + return {str.split(QLatin1Char('.'))}; } QString QualifiedId::toString() const diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp index 0614f31f5..9131db7f5 100644 --- a/src/lib/corelib/language/scriptengine.cpp +++ b/src/lib/corelib/language/scriptengine.cpp @@ -46,16 +46,19 @@ #include "preparescriptobserver.h" #include <buildgraph/artifact.h> +#include <buildgraph/rulenode.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/error.h> #include <tools/fileinfo.h> #include <tools/profiling.h> #include <tools/qbsassert.h> +#include <tools/scripttools.h> #include <tools/qttools.h> #include <tools/stlutils.h> #include <tools/stringconstants.h> +#include <QtCore/qdatetime.h> #include <QtCore/qdebug.h> #include <QtCore/qdiriterator.h> #include <QtCore/qfile.h> @@ -63,18 +66,15 @@ #include <QtCore/qtextstream.h> #include <QtCore/qtimer.h> -#include <QtScript/qscriptclass.h> -#include <QtScript/qscriptvalueiterator.h> - +#include <cstring> #include <functional> #include <set> #include <utility> +#include <vector> namespace qbs { namespace Internal { -static QString getterFuncHelperProperty() { return QStringLiteral("qbsdata"); } - const bool debugJSImports = false; bool operator==(const ScriptEngine::PropertyCacheKey &lhs, @@ -85,9 +85,7 @@ bool operator==(const ScriptEngine::PropertyCacheKey &lhs, && lhs.m_propertyName == rhs.m_propertyName; } - - -static inline QHashValueType combineHash(QHashValueType h1, QHashValueType h2, QHashValueType seed) +static QHashValueType combineHash(QHashValueType h1, QHashValueType h2, QHashValueType seed) { // stolen from qHash(QPair) return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed; @@ -99,121 +97,210 @@ QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType see combineHash(qHash(k.m_propertyName), qHash(k.m_propertyMap), seed), seed); } -std::mutex ScriptEngine::m_creationDestructionMutex; - ScriptEngine::ScriptEngine(Logger &logger, EvalContext evalContext, PrivateTag) : m_scriptImporter(new ScriptImporter(this)), - m_modulePropertyScriptClass(nullptr), - m_propertyCacheEnabled(true), m_active(false), m_logger(logger), m_evalContext(evalContext), + m_logger(logger), m_evalContext(evalContext), m_observer(new PrepareScriptObserver(this, UnobserveMode::Disabled)) { - setProcessEventsInterval(1000); // For the cancelation mechanism to work. - m_cancelationError = currentContext()->throwValue(tr("Execution canceled")); - QScriptValue objectProto = globalObject().property(QStringLiteral("Object")); - m_definePropertyFunction = objectProto.property(QStringLiteral("defineProperty")); - QBS_ASSERT(m_definePropertyFunction.isFunction(), /* ignore */); - m_emptyFunction = evaluate(QStringLiteral("(function(){})")); - QBS_ASSERT(m_emptyFunction.isFunction(), /* ignore */); - // Initially push a new context to turn off scope chain insanity mode. - QScriptEngine::pushContext(); + setMaxStackSize(); + JS_SetRuntimeOpaque(m_jsRuntime, this); + JS_SetInterruptHandler(m_jsRuntime, interruptor, this); + setScopeLookup(m_context, &ScriptEngine::doExtraScopeLookup); + setFoundUndefinedHandler(m_context, &ScriptEngine::handleUndefinedFound); + setFunctionEnteredHandler(m_context, &ScriptEngine::handleFunctionEntered); + setFunctionExitedHandler(m_context, &ScriptEngine::handleFunctionExited); + m_dataWithPtrClass = registerClass("__data", nullptr, nullptr, JS_UNDEFINED); installQbsBuiltins(); extendJavaScriptBuiltins(); } std::unique_ptr<ScriptEngine> ScriptEngine::create(Logger &logger, EvalContext evalContext) { - std::lock_guard<std::mutex> lock(m_creationDestructionMutex); return std::make_unique<ScriptEngine>(logger, evalContext, PrivateTag()); } -ScriptEngine::~ScriptEngine() +ScriptEngine *ScriptEngine::engineForRuntime(const JSRuntime *runtime) { - m_creationDestructionMutex.lock(); - connect(this, &QObject::destroyed, std::bind(&std::mutex::unlock, &m_creationDestructionMutex)); + return static_cast<ScriptEngine *>(JS_GetRuntimeOpaque(const_cast<JSRuntime *>(runtime))); - releaseResourcesOfScriptObjects(); - delete (m_scriptImporter); +} + +ScriptEngine *ScriptEngine::engineForContext(const JSContext *ctx) +{ + return engineForRuntime(JS_GetRuntime(const_cast<JSContext *>(ctx))); +} + +LookupResult ScriptEngine::doExtraScopeLookup(JSContext *ctx, JSAtom prop) +{ + static const LookupResult fail{JS_UNDEFINED, JS_UNDEFINED, false}; + + ScriptEngine * const engine = engineForContext(ctx); + engine->m_lastLookupWasSuccess = false; + + JSValueList scopes; + if (!engine->m_scopeChains.isEmpty()) + scopes = engine->m_scopeChains.last(); + if (JS_IsObject(engine->m_globalObject)) + scopes.insert(scopes.begin(), engine->m_globalObject); + for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) { + const JSValue v = JS_GetProperty(ctx, *it, prop); + if (!JS_IsUndefined(v) || engine->m_lastLookupWasSuccess) { + engine->m_lastLookupWasSuccess = false; + return {v, *it, true}; + } + } + return fail; +} + +void ScriptEngine::handleUndefinedFound(JSContext *ctx) +{ + engineForContext(ctx)->setLastLookupStatus(true); +} + +void ScriptEngine::handleFunctionEntered(JSContext *ctx, JSValue this_obj) +{ + ScriptEngine::engineForContext(ctx)->m_contextStack.push_back(this_obj); +} + +void ScriptEngine::handleFunctionExited(JSContext *ctx) +{ + ScriptEngine::engineForContext(ctx)->m_contextStack.pop_back(); +} + +ScriptEngine::~ScriptEngine() +{ + reset(); + delete m_scriptImporter; if (m_elapsedTimeImporting != -1) { m_logger.qbsLog(LoggerInfo, true) << Tr::tr("Setting up imports took %1.") .arg(elapsedTimeString(m_elapsedTimeImporting)); } - delete m_modulePropertyScriptClass; - delete m_productPropertyScriptClass; + for (const auto &ext : std::as_const(m_internalExtensions)) + JS_FreeValue(m_context, ext); + for (const JSValue &s : std::as_const(m_stringCache)) + JS_FreeValue(m_context, s); + for (JSValue * const externalRef : std::as_const(m_externallyCachedValues)) { + JS_FreeValue(m_context, *externalRef); + *externalRef = JS_UNDEFINED; + } + setPropertyOnGlobalObject(QLatin1String("console"), JS_UNDEFINED); + JS_FreeContext(m_context); + JS_FreeRuntime(m_jsRuntime); } -void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, - ObserveMode observeMode) +void ScriptEngine::reset() { - installImportFunctions(); - m_currentDirPathStack.push(FileInfo::path(fileCtx->filePath())); - m_extensionSearchPathsStack.push(fileCtx->searchPaths()); - m_observeMode = observeMode; - - for (const JsImport &jsImport : fileCtx->jsImports()) - import(jsImport, targetObject); - if (m_observeMode == ObserveMode::Enabled) { - for (QScriptValue &sv : m_requireResults) - observeImport(sv); - m_requireResults.clear(); + // TODO: Check whether we can keep file and imports cache. + // We'd have to find a solution for the scope name problem then. + clearImportsCache(); + for (const auto &e : std::as_const(m_jsFileCache)) + JS_FreeValue(m_context, e.second); + m_jsFileCache.clear(); + + for (const JSValue &s : std::as_const(m_jsValueCache)) + JS_FreeValue(m_context, s); + m_jsValueCache.clear(); + + for (auto it = m_evalResults.cbegin(); it != m_evalResults.cend(); ++it) { + for (int i = 0; i < it.value(); ++i) + JS_FreeValue(m_context, it.key()); } + m_evalResults.clear(); + for (const auto &e : std::as_const(m_projectScriptValues)) + JS_FreeValue(m_context, e.second); + m_projectScriptValues.clear(); + for (const auto &e : std::as_const(m_baseProductScriptValues)) + JS_FreeValue(m_context, e.second); + m_baseProductScriptValues.clear(); + for (const auto &e : std::as_const(m_productArtifactsMapScriptValues)) + JS_FreeValue(m_context, e.second); + m_productArtifactsMapScriptValues.clear(); + for (const auto &e : std::as_const(m_moduleArtifactsMapScriptValues)) + JS_FreeValue(m_context, e.second); + m_moduleArtifactsMapScriptValues.clear(); + for (const auto &e : std::as_const(m_baseModuleScriptValues)) + JS_FreeValue(m_context, e.second); + m_baseModuleScriptValues.clear(); + for (auto it = m_artifactsScriptValues.cbegin(); it != m_artifactsScriptValues.cend(); ++it) { + it.key().first->setDeregister({}); + JS_FreeValue(m_context, it.value()); + } + m_artifactsScriptValues.clear(); +} - m_currentDirPathStack.pop(); - m_extensionSearchPathsStack.pop(); - uninstallImportFunctions(); +void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, + ObserveMode observeMode) +{ + Importer(*this, fileCtx, targetObject, observeMode).run(); } -void ScriptEngine::import(const JsImport &jsImport, QScriptValue &targetObject) +void ScriptEngine::import(const JsImport &jsImport, JSValue &targetObject) { - QBS_ASSERT(targetObject.isObject(), return); - QBS_ASSERT(targetObject.engine() == this, return); + QBS_ASSERT(JS_IsObject(targetObject), return); if (debugJSImports) qDebug() << "[ENGINE] import into " << jsImport.scopeName; - QScriptValue jsImportValue = m_jsImportCache.value(jsImport); - if (jsImportValue.isValid()) { + JSValue jsImportValue = m_jsImportCache.value(jsImport); + if (JS_IsObject(jsImportValue)) { if (debugJSImports) qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache hit)"; } else { if (debugJSImports) qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache miss)"; - jsImportValue = newObject(); + + ScopedJsValue scopedImportValue(m_context, JS_NewObject(m_context)); for (const QString &filePath : jsImport.filePaths) - importFile(filePath, jsImportValue); + importFile(filePath, scopedImportValue); + jsImportValue = scopedImportValue.release(); m_jsImportCache.insert(jsImport, jsImportValue); std::vector<QString> &filePathsForScriptValue - = m_filePathsPerImport[jsImportValue.objectId()]; + = m_filePathsPerImport[jsObjectId(jsImportValue)]; transform(jsImport.filePaths, filePathsForScriptValue, [](const auto &fp) { return fp; }); } - QScriptValue sv = newObject(); - sv.setPrototype(jsImportValue); - sv.setProperty(StringConstants::importScopeNamePropertyInternal(), jsImport.scopeName); - targetObject.setProperty(jsImport.scopeName, sv); + JSValue sv = JS_NewObjectProto(m_context, jsImportValue); + setJsProperty(m_context, sv, StringConstants::importScopeNamePropertyInternal(), + jsImport.scopeName); + setJsProperty(m_context, targetObject, jsImport.scopeName, sv); if (m_observeMode == ObserveMode::Enabled) observeImport(jsImportValue); } -void ScriptEngine::observeImport(QScriptValue &jsImport) +void ScriptEngine::observeImport(JSValue &jsImport) { - if (!m_observer->addImportId(jsImport.objectId())) + if (!m_observer->addImportId(quintptr((JS_VALUE_GET_OBJ(jsImport))))) return; - QScriptValueIterator it(jsImport); - while (it.hasNext()) { - it.next(); - if (it.flags() & QScriptValue::PropertyGetter) - continue; - QScriptValue property = it.value(); - if (!property.isFunction()) - continue; - setObservedProperty(jsImport, it.name(), property); - } + handleJsProperties(jsImport, [this, &jsImport](const JSAtom &name, + const JSPropertyDescriptor &desc) { + if (!JS_IsFunction(m_context, desc.value)) + return; + const char *const nameStr = JS_AtomToCString(m_context, name); + setObservedProperty(jsImport, QString::fromUtf8(nameStr, std::strlen(nameStr)), desc.value); + JS_FreeCString(m_context, nameStr); + }); } void ScriptEngine::clearImportsCache() { + for (const auto &jsImport : std::as_const(m_jsImportCache)) + JS_FreeValue(m_context, jsImport); m_jsImportCache.clear(); + m_filePathsPerImport.clear(); + m_observer->clearImportIds(); +} + +void ScriptEngine::registerEvaluator(Evaluator *evaluator) +{ + QBS_ASSERT(!m_evaluator, return); + m_evaluator = evaluator; +} + +void ScriptEngine::unregisterEvaluator(const Evaluator *evaluator) +{ + QBS_ASSERT(m_evaluator == evaluator, return); + m_evaluator = nullptr; } void ScriptEngine::checkContext(const QString &operation, @@ -242,7 +329,15 @@ void ScriptEngine::checkContext(const QString &operation, QBS_ASSERT(false, continue); break; } - m_logger.printWarning(ErrorInfo(warning, currentContext()->backtrace())); + if (!m_evalPositions.empty()) { + const JSValue exVal = JS_NewObject(m_context); + const auto &[file, line] = m_evalPositions.top(); + build_backtrace(m_context, exVal, file.toUtf8().constData(), line, 0); + const JsException ex(m_context, exVal, {}); + m_logger.printWarning(ErrorInfo(warning, ex.stackTrace())); + } else { + m_logger.printWarning(ErrorInfo(warning)); + } return; } } @@ -253,7 +348,7 @@ void ScriptEngine::addPropertyRequestedFromArtifact(const Artifact *artifact, m_propertiesRequestedFromArtifact[artifact->filePath()] << property; } -void ScriptEngine::addImportRequestedInScript(qint64 importValueId) +void ScriptEngine::addImportRequestedInScript(quintptr importValueId) { // Import list is assumed to be small, so let's not use a set. if (!contains(m_importsRequestedInScript, importValueId)) @@ -291,37 +386,21 @@ QVariant ScriptEngine::retrieveFromPropertyCache(const QString &moduleName, return m_propertyCache.value(PropertyCacheKey(moduleName, propertyName, propertyMap)); } -void ScriptEngine::defineProperty(QScriptValue &object, const QString &name, - const QScriptValue &descriptor) -{ - QScriptValue arguments = newArray(); - arguments.setProperty(0, object); - arguments.setProperty(1, name); - arguments.setProperty(2, descriptor); - QScriptValue result = m_definePropertyFunction.call(QScriptValue(), arguments); - QBS_ASSERT(!hasErrorOrException(result), qDebug() << name << result.toString()); -} - -static QScriptValue js_observedGet(QScriptContext *context, QScriptEngine *, - ScriptPropertyObserver * const observer) +static JSValue js_observedGet(JSContext *ctx, JSValueConst, int, JSValueConst *, int, JSValue *data) { - const QScriptValue data = context->callee().property(getterFuncHelperProperty()); - const QScriptValue value = data.property(2); - observer->onPropertyRead(data.property(0), data.property(1).toVariant().toString(), value); - return value; + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + engine->observer()->onPropertyRead(data[0], getJsString(ctx, data[1]), data[2]); + return JS_DupValue(engine->context(), data[2]); } -void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name, - const QScriptValue &value) +void ScriptEngine::setObservedProperty(JSValue &object, const QString &name, + const JSValue &value) { - QScriptValue data = newArray(); - data.setProperty(0, object); - data.setProperty(1, name); - data.setProperty(2, value); - QScriptValue getterFunc = newFunction(js_observedGet, - static_cast<ScriptPropertyObserver *>(m_observer.get())); - getterFunc.setProperty(getterFuncHelperProperty(), data); - object.setProperty(name, getterFunc, QScriptValue::PropertyGetter); + ScopedJsValue jsName(m_context, makeJsString(m_context, name)); + JSValueList funcData{object, jsName, value}; + JSValue getterFunc = JS_NewCFunctionData(m_context, &js_observedGet, 0, 0, 3, funcData.data()); + const ScopedJsAtom nameAtom(m_context, name); + JS_DefinePropertyGetSet(m_context, object, nameAtom, getterFunc, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); if (m_observer->unobserveMode() == UnobserveMode::Enabled) m_observedProperties.emplace_back(object, name, value); } @@ -329,38 +408,16 @@ void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name void ScriptEngine::unobserveProperties() { for (auto &elem : m_observedProperties) { - QScriptValue &object = std::get<0>(elem); + JSValue &object = std::get<0>(elem); const QString &name = std::get<1>(elem); - const QScriptValue &value = std::get<2>(elem); - object.setProperty(name, QScriptValue(), QScriptValue::PropertyGetter); - object.setProperty(name, value, QScriptValue::PropertyFlags()); + const JSValue &value = std::get<2>(elem); + const ScopedJsAtom jsName(m_context, name); + JS_DefineProperty(m_context, object, jsName, value, JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); } m_observedProperties.clear(); } -static QScriptValue js_deprecatedGet(QScriptContext *context, QScriptEngine *qtengine) -{ - const auto engine = static_cast<const ScriptEngine *>(qtengine); - const QScriptValue data = context->callee().property(getterFuncHelperProperty()); - engine->logger().qbsWarning() - << ScriptEngine::tr("Property %1 is deprecated. Please use %2 instead.").arg( - data.property(0).toString(), data.property(1).toString()); - return data.property(2); -} - -void ScriptEngine::setDeprecatedProperty(QScriptValue &object, const QString &oldName, - const QString &newName, const QScriptValue &value) -{ - QScriptValue data = newArray(); - data.setProperty(0, oldName); - data.setProperty(1, newName); - data.setProperty(2, value); - QScriptValue getterFunc = newFunction(js_deprecatedGet); - getterFunc.setProperty(getterFuncHelperProperty(), data); - object.setProperty(oldName, getterFunc, QScriptValue::PropertyGetter - | QScriptValue::SkipInEnumeration); -} - QProcessEnvironment ScriptEngine::environment() const { return m_environment; @@ -371,17 +428,17 @@ void ScriptEngine::setEnvironment(const QProcessEnvironment &env) m_environment = env; } -void ScriptEngine::importFile(const QString &filePath, QScriptValue &targetObject) +void ScriptEngine::importFile(const QString &filePath, JSValue targetObject) { AccumulatingTimer importTimer(m_elapsedTimeImporting != -1 ? &m_elapsedTimeImporting : nullptr); - QScriptValue &evaluationResult = m_jsFileCache[filePath]; - if (evaluationResult.isValid()) { - ScriptImporter::copyProperties(evaluationResult, targetObject); + JSValue &evaluationResult = m_jsFileCache[filePath]; + if (JS_IsObject(evaluationResult)) { + ScriptImporter::copyProperties(m_context, evaluationResult, targetObject); return; } QFile file(filePath); if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) - throw ErrorInfo(tr("Cannot open '%1'.").arg(filePath)); + throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); QTextStream stream(&file); setupDefaultCodec(stream); const QString sourceCode = stream.readAll(); @@ -394,7 +451,7 @@ void ScriptEngine::importFile(const QString &filePath, QScriptValue &targetObjec static QString findExtensionDir(const QStringList &searchPaths, const QString &extensionPath) { for (const QString &searchPath : searchPaths) { - const QString dirPath = searchPath + QStringLiteral("/imports/") + extensionPath; + QString dirPath = searchPath + QStringLiteral("/imports/") + extensionPath; QFileInfo fi(dirPath); if (fi.exists() && fi.isDir()) return dirPath; @@ -402,83 +459,161 @@ static QString findExtensionDir(const QStringList &searchPaths, const QString &e return {}; } -static QScriptValue mergeExtensionObjects(const QScriptValueList &lst) +JSValue ScriptEngine::mergeExtensionObjects(const JSValueList &lst) { - QScriptValue result; - for (const QScriptValue &v : lst) { - if (!result.isValid()) { + JSValue result = JS_UNDEFINED; + for (const JSValue &v : lst) { + if (!JS_IsObject(result)) { result = v; continue; } - QScriptValueIterator svit(v); - while (svit.hasNext()) { - svit.next(); - result.setProperty(svit.name(), svit.value()); - } + ScriptImporter::copyProperties(m_context, v, result); + JS_FreeValue(m_context, v); } return result; } -static QScriptValue loadInternalExtension(QScriptContext *context, ScriptEngine *engine, - const QString &uri) +JSValue ScriptEngine::getInternalExtension(const char *name) const { - const QString name = uri.mid(4); // remove the "qbs." part - QScriptValue extensionObj = JsExtensions::loadExtension(engine, name); - if (!extensionObj.isValid()) { - return context->throwError(ScriptEngine::tr("loadExtension: " - "cannot load extension '%1'.").arg(uri)); - } - return extensionObj; + const auto cached = m_internalExtensions.constFind(QLatin1String(name)); + if (cached != m_internalExtensions.constEnd()) + return JS_DupValue(m_context, cached.value()); + return JS_UNDEFINED; +} + +void ScriptEngine::addInternalExtension(const char *name, JSValue ext) +{ + m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext)); } -QScriptValue ScriptEngine::js_loadExtension(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QVariant &v, quintptr id, bool frozen) { - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The loadExtension function requires " - "an extension name.")); + if (v.isNull()) + return JS_UNDEFINED; + switch (static_cast<QMetaType::Type>(v.userType())) { + case QMetaType::QByteArray: + return asJsValue(v.toByteArray()); + case QMetaType::QString: + return asJsValue(v.toString()); + case QMetaType::QStringList: + return asJsValue(v.toStringList()); + case QMetaType::QVariantList: + return asJsValue(v.toList(), id, frozen); + case QMetaType::Int: + case QMetaType::UInt: + return JS_NewInt32(m_context, v.toInt()); + case QMetaType::Long: + case QMetaType::ULong: + case QMetaType::LongLong: + case QMetaType::ULongLong: + return JS_NewInt64(m_context, v.toInt()); + case QMetaType::Bool: + return JS_NewBool(m_context, v.toBool()); + case QMetaType::QDateTime: + return JS_NewDate( + m_context, v.toDateTime().toString(Qt::ISODateWithMs).toUtf8().constData()); + case QMetaType::QVariantMap: + return asJsValue(v.toMap(), id, frozen); + default: + return JS_UNDEFINED; } +} - const auto engine = static_cast<const ScriptEngine *>(qtengine); - ErrorInfo deprWarning(Tr::tr("The loadExtension() function is deprecated and will be " - "removed in a future version of Qbs. Use require() " - "instead."), context->backtrace()); - engine->logger().printWarning(deprWarning); +JSValue ScriptEngine::asJsValue(const QByteArray &s) +{ + return JS_NewArrayBufferCopy( + m_context, reinterpret_cast<const uint8_t *>(s.constData()), s.size()); +} - return js_require(context, qtengine); +JSValue ScriptEngine::asJsValue(const QString &s) +{ + const auto it = m_stringCache.constFind(s); + if (it != m_stringCache.constEnd()) + return JS_DupValue(m_context, it.value()); + const JSValue sv = JS_NewString(m_context, s.toUtf8().constData()); + m_stringCache.insert(s, sv); + return JS_DupValue(m_context, sv); } -QScriptValue ScriptEngine::js_loadFile(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QStringList &l) { - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The loadFile function requires a file path.")); - } + JSValue array = JS_NewArray(m_context); + setJsProperty(m_context, array, QLatin1String("length"), JS_NewInt32(m_context, l.size())); + for (int i = 0; i < l.size(); ++i) + JS_SetPropertyUint32(m_context, array, i, asJsValue(l.at(i))); + return array; +} - const auto engine = static_cast<const ScriptEngine *>(qtengine); - ErrorInfo deprWarning(Tr::tr("The loadFile() function is deprecated and will be " - "removed in a future version of Qbs. Use require() " - "instead."), context->backtrace()); - engine->logger().printWarning(deprWarning); +JSValue ScriptEngine::asJsValue(const QVariantMap &m, quintptr id, bool frozen) +{ + const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd(); + if (it != m_jsValueCache.constEnd()) + return JS_DupValue(m_context, it.value()); + frozen = id || frozen; + JSValue obj = JS_NewObject(m_context); + for (auto it = m.begin(); it != m.end(); ++it) + setJsProperty(m_context, obj, it.key(), asJsValue(it.value(), 0, frozen)); + if (frozen) + JS_ObjectSeal(m_context, obj, true); + if (!id) + return obj; + m_jsValueCache[id] = obj; + return JS_DupValue(m_context, obj); +} - return js_require(context, qtengine); +void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue value) +{ + const ScopedJsValue globalObject(m_context, JS_GetGlobalObject(m_context)); + setJsProperty(m_context, globalObject, property, value); } -QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QVariantList &l, quintptr id, bool frozen) { - const auto engine = static_cast<ScriptEngine *>(qtengine); - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The require function requires a module name or path.")); - } + const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd(); + if (it != m_jsValueCache.constEnd()) + return JS_DupValue(m_context, it.value()); + frozen = id || frozen; + JSValue array = JS_NewArray(m_context); + setJsProperty(m_context, array, QLatin1String("length"), JS_NewInt32(m_context, l.size())); + for (int i = 0; i < l.size(); ++i) + JS_SetPropertyUint32(m_context, array, i, asJsValue(l.at(i), 0, frozen)); + if (frozen) + JS_ObjectSeal(m_context, array, true); + if (!id) + return array; + m_jsValueCache[id] = array; + return JS_DupValue(m_context, array); +} + +JSValue ScriptEngine::loadInternalExtension(const QString &uri) +{ + const QString name = uri.mid(4); // remove the "qbs." part + const auto cached = m_internalExtensions.constFind(name); + if (cached != m_internalExtensions.constEnd()) + return cached.value(); + JSValue extensionObj = JsExtensions::loadExtension(this, name); + if (!JS_IsObject(extensionObj)) + return throwError(Tr::tr("loadExtension: cannot load extension '%1'.").arg(uri)); + m_internalExtensions.insert(name, extensionObj); + return extensionObj; +} - const QString moduleName = context->argument(0).toString(); +JSValue ScriptEngine::js_require(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int, JSValue *func_data) +{ + Q_UNUSED(this_val) + + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + QBS_ASSERT(engine, return JS_EXCEPTION); + if (argc < 1) + return engine->throwError(Tr::tr("The require function requires a module name or path.")); + + const QString moduleName = getJsString(ctx, argv[0]); // First try to load a named module if the argument doesn't look like a file path if (!moduleName.contains(QLatin1Char('/'))) { if (engine->m_extensionSearchPathsStack.empty()) - return context->throwError( - ScriptEngine::tr("require: internal error. No search paths.")); + return engine->throwError(Tr::tr("require: internal error. No search paths.")); if (engine->m_logger.debugEnabled()) { engine->m_logger.qbsDebug() @@ -491,11 +626,11 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt const QString dirPath = findExtensionDir(searchPaths, moduleNameAsPath); if (dirPath.isEmpty()) { if (moduleName.startsWith(QStringLiteral("qbs."))) - return loadInternalExtension(context, engine, moduleName); + return JS_DupValue(ctx, engine->loadInternalExtension(moduleName)); } else { QDirIterator dit(dirPath, StringConstants::jsFileWildcards(), QDir::Files | QDir::Readable); - QScriptValueList values; + JSValueList values; std::vector<QString> filePaths; try { while (dit.hasNext()) { @@ -504,19 +639,19 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt engine->m_logger.qbsDebug() << "[require] importing file " << filePath; } - QScriptValue obj = engine->newObject(); + ScopedJsValue obj(engine->context(), engine->newObject()); engine->importFile(filePath, obj); - values << obj; + values << obj.release(); filePaths.push_back(filePath); } } catch (const ErrorInfo &e) { - return context->throwError(e.toString()); + return engine->throwError(e.toString()); } if (!values.empty()) { - const QScriptValue mergedValue = mergeExtensionObjects(values); + const JSValue mergedValue = engine->mergeExtensionObjects(values); engine->m_requireResults.push_back(mergedValue); - engine->m_filePathsPerImport[mergedValue.objectId()] = filePaths; + engine->m_filePathsPerImport[jsObjectId(mergedValue)] = filePaths; return mergedValue; } } @@ -525,52 +660,88 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt // file located in the current directory search path; try that next } - if (engine->m_currentDirPathStack.empty()) { - return context->throwError( - ScriptEngine::tr("require: internal error. No current directory.")); - } + if (engine->m_currentDirPathStack.empty()) + return engine->throwError(Tr::tr("require: internal error. No current directory.")); - QScriptValue result; + JSValue result; try { const QString filePath = FileInfo::resolvePath(engine->m_currentDirPathStack.top(), moduleName); - result = engine->newObject(); - engine->importFile(filePath, result); static const QString scopeNamePrefix = QStringLiteral("_qbs_scope_"); const QString scopeName = scopeNamePrefix + QString::number(qHash(filePath), 16); - result.setProperty(StringConstants::importScopeNamePropertyInternal(), scopeName); - context->thisObject().setProperty(scopeName, result); + result = getJsProperty(ctx, func_data[0], scopeName); + if (JS_IsObject(result)) + return result; // Same JS file imported from same qbs file via different JS files (e.g. codesign.js from DarwinGCC.qbs via gcc.js and darwin.js). + ScopedJsValue scopedResult(engine->context(), engine->newObject()); + engine->importFile(filePath, scopedResult); + result = scopedResult.release(); + setJsProperty(ctx, result, StringConstants::importScopeNamePropertyInternal(), scopeName); + setJsProperty(ctx, func_data[0], scopeName, result); engine->m_requireResults.push_back(result); - engine->m_filePathsPerImport[result.objectId()] = { filePath }; + engine->m_filePathsPerImport[jsObjectId(JS_DupValue(ctx, result))] = { filePath }; } catch (const ErrorInfo &e) { - result = context->throwError(e.toString()); + result = engine->throwError(e.toString()); } return result; } -QScriptClass *ScriptEngine::modulePropertyScriptClass() const +JSClassID ScriptEngine::modulePropertyScriptClass() const { return m_modulePropertyScriptClass; } -void ScriptEngine::setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass) +void ScriptEngine::setModulePropertyScriptClass(JSClassID modulePropertyScriptClass) { m_modulePropertyScriptClass = modulePropertyScriptClass; } -void ScriptEngine::addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj) +template<typename T> JSValue getScriptValue(JSContext *ctx, const T *t, + const std::unordered_map<const T *, JSValue> &map) { - m_resourceAcquiringScriptObjects.push_back(obj); + const auto it = map.find(t); + return it == map.end() ? JS_UNDEFINED : JS_DupValue(ctx, it->second); } -void ScriptEngine::releaseResourcesOfScriptObjects() +template<typename T> void setScriptValue(JSContext *ctx, const T *t, JSValue value, + std::unordered_map<const T *, JSValue> &map) { - if (m_resourceAcquiringScriptObjects.empty()) + value = JS_DupValue(ctx, value); + const auto it = map.find(t); + if (it == map.end()) { + map.insert(std::make_pair(t, value)); return; - std::for_each(m_resourceAcquiringScriptObjects.begin(), m_resourceAcquiringScriptObjects.end(), - std::mem_fn(&ResourceAcquiringScriptObject::releaseResources)); - m_resourceAcquiringScriptObjects.clear(); + } + JS_FreeValue(ctx, it->second); + it->second = value; +} + +JSValue ScriptEngine::artifactsMapScriptValue(const ResolvedProduct *product) +{ + return getScriptValue(m_context, product, m_productArtifactsMapScriptValues); +} + +void ScriptEngine::setArtifactsMapScriptValue(const ResolvedProduct *product, JSValue value) +{ + setScriptValue(m_context, product, value, m_productArtifactsMapScriptValues); +} + +JSValue ScriptEngine::artifactsMapScriptValue(const ResolvedModule *module) +{ + return getScriptValue(m_context, module, m_moduleArtifactsMapScriptValues); +} + +void ScriptEngine::setArtifactsMapScriptValue(const ResolvedModule *module, JSValue value) +{ + setScriptValue(m_context, module, value, m_moduleArtifactsMapScriptValues); +} + +JSValue ScriptEngine::getArtifactProperty(JSValue obj, + const std::function<JSValue (const Artifact *)> &propGetter) +{ + std::lock_guard lock(m_artifactsMutex); + const Artifact * const a = attachedPointer<Artifact>(obj, dataWithPtrClass()); + return a ? propGetter(a) : JS_EXCEPTION; } void ScriptEngine::addCanonicalFilePathResult(const QString &filePath, @@ -617,49 +788,100 @@ Set<QString> ScriptEngine::imports() const return filePaths; } -QScriptValueList ScriptEngine::argumentList(const QStringList &argumentNames, - const QScriptValue &context) +JSValue ScriptEngine::newObject() const { - QScriptValueList result; - for (const auto &name : argumentNames) - result += context.property(name); - return result; + return JS_NewObject(m_context); } -CodeLocation ScriptEngine::lastErrorLocation(const QScriptValue &v, - const CodeLocation &fallbackLocation) const +JSValue ScriptEngine::newArray(int length, JsValueOwner owner) { - const QScriptValue &errorVal = lastErrorValue(v); - const CodeLocation errorLoc(errorVal.property(StringConstants::fileNameProperty()).toString(), - errorVal.property(QStringLiteral("lineNumber")).toInt32(), - errorVal.property(QStringLiteral("expressionCaretOffset")).toInt32(), - false); - return errorLoc.isValid() ? errorLoc : fallbackLocation; + JSValue arr = JS_NewArray(m_context); + JS_SetPropertyStr(m_context, arr, "length", JS_NewInt32(m_context, length)); + if (owner == JsValueOwner::ScriptEngine) + ++m_evalResults[arr]; + return arr; } -ErrorInfo ScriptEngine::lastError(const QScriptValue &v, const CodeLocation &fallbackLocation) const +JSValue ScriptEngine::evaluate(JsValueOwner resultOwner, const QString &code, + const QString &filePath, int line, const JSValueList &scopeChain) { - const QString msg = lastErrorString(v); - CodeLocation errorLocation = lastErrorLocation(v); - if (errorLocation.isValid()) - return ErrorInfo(msg, errorLocation); - const QStringList backtrace = uncaughtExceptionBacktraceOrEmpty(); - if (!backtrace.empty()) { - ErrorInfo e(msg, backtrace); - if (e.hasLocation()) - return e; + m_scopeChains << scopeChain; + const QByteArray &codeStr = code.toUtf8(); + + m_evalPositions.emplace(filePath, line); + const JSValue v = JS_EvalThis(m_context, globalObject(), codeStr.constData(), codeStr.length(), + filePath.toUtf8().constData(), line, JS_EVAL_TYPE_GLOBAL); + m_evalPositions.pop(); + m_scopeChains.removeLast(); + if (resultOwner == JsValueOwner::ScriptEngine && JS_VALUE_HAS_REF_COUNT(v)) + ++m_evalResults[v]; + return v; +} + +void ScriptEngine::handleJsProperties(JSValue obj, const PropertyHandler &handler) +{ + qbs::Internal::handleJsProperties(m_context, obj, handler); +} + +ScopedJsValueList ScriptEngine::argumentList(const QStringList &argumentNames, + const JSValue &context) const +{ + JSValueList result; + for (const auto &name : argumentNames) + result.push_back(getJsProperty(m_context, context, name)); + return {m_context, result}; +} + +JSClassID ScriptEngine::registerClass(const char *name, JSClassCall *constructor, + JSClassFinalizer *finalizer, JSValue scope, + GetPropertyNames getPropertyNames, GetProperty getProperty) +{ + JSClassID id = 0; + const auto classIt = m_classes.constFind(QLatin1String(name)); + if (classIt == m_classes.constEnd()) { + JS_NewClassID(&id); + const auto it = getProperty + ? m_exoticMethods.insert(id, JSClassExoticMethods{getProperty, getPropertyNames}) + : m_exoticMethods.end(); + JSClassDef jsClass{name, finalizer, nullptr, constructor, + it != m_exoticMethods.end() ? &it.value() : nullptr}; + const int status = JS_NewClass(m_jsRuntime, id, &jsClass); + QBS_ASSERT(status == 0, return 0); + m_classes.insert(QLatin1String(name), id); + } else { + id = classIt.value(); } - return ErrorInfo(msg, fallbackLocation); + if (!JS_IsUndefined(scope)) { + const JSValue classObj = JS_NewObjectClass(m_context, id); + JS_SetConstructorBit(m_context, classObj, constructor != nullptr); + JS_SetPropertyStr(m_context, scope, name, classObj); + } + return id; +} + +JSClassID ScriptEngine::getClassId(const char *name) const +{ + return m_classes.value(QLatin1String(name)); +} + +JSValue ScriptEngine::throwError(const QString &message) const +{ + return qbs::Internal::throwError(m_context, message); } void ScriptEngine::cancel() { - QTimer::singleShot(0, this, [this] { abort(); }); + m_canceling = true; } -void ScriptEngine::abort() +int ScriptEngine::interruptor(JSRuntime *, void *opaqueEngine) { - abortEvaluation(m_cancelationError); + const auto engine = reinterpret_cast<ScriptEngine *>(opaqueEngine); + if (engine->m_canceling) { + engine->m_canceling = false; + return 1; + } + return 0; } bool ScriptEngine::gatherFileResults() const @@ -668,83 +890,105 @@ bool ScriptEngine::gatherFileResults() const || evalContext() == EvalContext::ProbeExecution; } +void ScriptEngine::setMaxStackSize() +{ + size_t stackSize = 0; // Turn check off by default. + bool ok; + const int stackSizeFromEnv = qEnvironmentVariableIntValue("QBS_MAX_JS_STACK_SIZE", &ok); + if (ok && stackSizeFromEnv >= 0) + stackSize = stackSizeFromEnv; + JS_SetMaxStackSize(m_jsRuntime, stackSize); +} + +JSValue ScriptEngine::getArtifactScriptValue(Artifact *a, const QString &moduleName, + const std::function<void(JSValue obj)> &setup) +{ + std::lock_guard lock(m_artifactsMutex); + const auto it = m_artifactsScriptValues.constFind(qMakePair(a, moduleName)); + if (it != m_artifactsScriptValues.constEnd()) + return JS_DupValue(m_context, *it); + a->setDeregister([this](const Artifact *a) { + const std::lock_guard lock(m_artifactsMutex); + for (auto it = m_artifactsScriptValues.begin(); it != m_artifactsScriptValues.end(); ) { + if (it.key().first == a) { + JS_SetOpaque(it.value(), nullptr); + JS_FreeValue(m_context, it.value()); + it = m_artifactsScriptValues.erase(it); + } else { + ++it; + } + } + }); + JSValue obj = JS_NewObjectClass(context(), dataWithPtrClass()); + attachPointerTo(obj, a); + setup(obj); + m_artifactsScriptValues.insert(qMakePair(a, moduleName), JS_DupValue(m_context, obj)); + return obj; +} + +void ScriptEngine::releaseInputArtifactScriptValues(const RuleNode *ruleNode) +{ + for (auto it = m_artifactsScriptValues.begin(); it != m_artifactsScriptValues.end();) { + Artifact * const a = it.key().first; + if (ruleNode->children.contains(a)) { + a->setDeregister({}); + JS_FreeValue(m_context, it.value()); + it = m_artifactsScriptValues.erase(it); + } else { + ++it; + } + } +} + class JSTypeExtender { public: - JSTypeExtender(ScriptEngine *engine, const QString &typeName) - : m_engine(engine) + JSTypeExtender(ScriptEngine *engine, const QString &typeName) : m_engine(engine) { - m_proto = engine->globalObject().property(typeName) - .property(QStringLiteral("prototype")); - QBS_ASSERT(m_proto.isObject(), return); - m_descriptor = engine->newObject(); + const ScopedJsValue globalObject(engine->context(), JS_GetGlobalObject(engine->context())); + const ScopedJsValue type(engine->context(), getJsProperty(engine->context(), + globalObject, typeName)); + m_proto = getJsProperty(engine->context(), type, QStringLiteral("prototype")); + QBS_ASSERT(JS_IsObject(m_proto), return); + } + + ~JSTypeExtender() + { + JS_FreeValue(m_engine->context(), m_proto); } void addFunction(const QString &name, const QString &code) { - QScriptValue f = m_engine->evaluate(code); - QBS_ASSERT(f.isFunction(), return); - m_descriptor.setProperty(QStringLiteral("value"), f); - m_engine->defineProperty(m_proto, name, m_descriptor); + const JSValue f = m_engine->evaluate(JsValueOwner::Caller, code); + QBS_ASSERT(JS_IsFunction(m_engine->context(), f), return); + JS_DefinePropertyValueStr(m_engine->context(), m_proto, name.toUtf8().constData(), f, 0); } private: - ScriptEngine *const m_engine; - QScriptValue m_proto; - QScriptValue m_descriptor; + ScriptEngine * const m_engine; + JSValue m_proto = JS_UNDEFINED; }; -static QScriptValue js_consoleError(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.error() expects 1 argument")); - logger->qbsLog(LoggerError) << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleWarn(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.warn() expects 1 argument")); - logger->qbsWarning() << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleInfo(QScriptContext *context, QScriptEngine *engine, Logger *logger) +static JSValue js_consoleFunc(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv, + int level) { - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.info() expects 1 argument")); - logger->qbsInfo() << context->argument(0).toString(); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + QBS_ASSERT(engine, return JS_EXCEPTION); + if (Q_UNLIKELY(argc != 1)) + return engine->throwError(Tr::tr("The console functions expect 1 argument.")); + engine->logger().qbsLog(static_cast<LoggerLevel>(level)) << getJsString(ctx, argv[0]); return engine->undefinedValue(); } -static QScriptValue js_consoleDebug(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.debug() expects 1 argument")); - logger->qbsDebug() << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleLog(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - return js_consoleDebug(context, engine, logger); -} - void ScriptEngine::installQbsBuiltins() { - globalObject().setProperty(StringConstants::qbsModule(), m_qbsObject = newObject()); - - globalObject().setProperty(QStringLiteral("console"), m_consoleObject = newObject()); - installConsoleFunction(QStringLiteral("debug"), &js_consoleDebug); - installConsoleFunction(QStringLiteral("error"), &js_consoleError); - installConsoleFunction(QStringLiteral("info"), &js_consoleInfo); - installConsoleFunction(QStringLiteral("log"), &js_consoleLog); - installConsoleFunction(QStringLiteral("warn"), &js_consoleWarn); + const JSValue consoleObj = newObject(); + setPropertyOnGlobalObject(QLatin1String("console"), consoleObj); + installConsoleFunction(consoleObj, QStringLiteral("debug"), LoggerDebug); + installConsoleFunction(consoleObj, QStringLiteral("error"), LoggerError); + installConsoleFunction(consoleObj, QStringLiteral("info"), LoggerInfo); + installConsoleFunction(consoleObj, QStringLiteral("log"), LoggerDebug); + installConsoleFunction(consoleObj, QStringLiteral("warn"), LoggerWarning); } void ScriptEngine::extendJavaScriptBuiltins() @@ -774,48 +1018,27 @@ void ScriptEngine::extendJavaScriptBuiltins() JSTypeExtender stringExtender(this, QStringLiteral("String")); stringExtender.addFunction(QStringLiteral("contains"), QStringLiteral("(function(e){return this.indexOf(e) !== -1;})")); - stringExtender.addFunction(QStringLiteral("startsWith"), - QStringLiteral("(function(e){return this.slice(0, e.length) === e;})")); - stringExtender.addFunction(QStringLiteral("endsWith"), - QStringLiteral("(function(e){return this.slice(-e.length) === e;})")); -} - -void ScriptEngine::installFunction(const QString &name, int length, QScriptValue *functionValue, - FunctionSignature f, QScriptValue *targetObject = nullptr) -{ - if (!functionValue->isValid()) - *functionValue = newFunction(f, length); - (targetObject ? *targetObject : globalObject()).setProperty(name, *functionValue); } -void ScriptEngine::installQbsFunction(const QString &name, int length, FunctionSignature f) +void ScriptEngine::installConsoleFunction(JSValue consoleObj, const QString &name, + LoggerLevel level) { - QScriptValue functionValue; - installFunction(name, length, &functionValue, f, &m_qbsObject); + JS_SetPropertyStr(m_context, consoleObj, name.toUtf8().constData(), + JS_NewCFunctionMagic(m_context, js_consoleFunc, name.toUtf8().constData(), 1, + JS_CFUNC_generic_magic, level)); } -void ScriptEngine::installConsoleFunction(const QString &name, - QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)) -{ - m_consoleObject.setProperty(name, newFunction(f, &m_logger)); -} - -static QString loadFileString() { return QStringLiteral("loadFile"); } -static QString loadExtensionString() { return QStringLiteral("loadExtension"); } static QString requireString() { return QStringLiteral("require"); } -void ScriptEngine::installImportFunctions() +void ScriptEngine::installImportFunctions(JSValue importScope) { - installFunction(loadFileString(), 1, &m_loadFileFunction, js_loadFile); - installFunction(loadExtensionString(), 1, &m_loadExtensionFunction, js_loadExtension); - installFunction(requireString(), 1, &m_requireFunction, js_require); + const JSValue require = JS_NewCFunctionData(m_context, js_require, 1, 0, 1, &importScope); + setPropertyOnGlobalObject(requireString(), require); } void ScriptEngine::uninstallImportFunctions() { - globalObject().setProperty(loadFileString(), QScriptValue()); - globalObject().setProperty(loadExtensionString(), QScriptValue()); - globalObject().setProperty(requireString(), QScriptValue()); + setPropertyOnGlobalObject(requireString(), JS_UNDEFINED); } ScriptEngine::PropertyCacheKey::PropertyCacheKey(QString moduleName, @@ -826,5 +1049,54 @@ ScriptEngine::PropertyCacheKey::PropertyCacheKey(QString moduleName, { } +JsException ScriptEngine::checkAndClearException(const CodeLocation &fallbackLocation) const +{ + return {m_context, JS_GetException(m_context), fallbackLocation}; +} + +void ScriptEngine::clearRequestedProperties() +{ + m_propertiesRequestedInScript.clear(); + m_propertiesRequestedFromArtifact.clear(); + m_importsRequestedInScript.clear(); + m_productsWithRequestedDependencies.clear(); + m_requestedArtifacts.clear(); + m_requestedExports.clear(); +}; + +void ScriptEngine::takeOwnership(JSValue v) +{ + ++m_evalResults[v]; +} + +ScriptEngine::Importer::Importer( + ScriptEngine &engine, const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, + ObserveMode observeMode) + : m_engine(engine), m_fileCtx(fileCtx), m_targetObject(targetObject) +{ + m_engine.installImportFunctions(targetObject); + m_engine.m_currentDirPathStack.push(FileInfo::path(fileCtx->filePath())); + m_engine.m_extensionSearchPathsStack.push(fileCtx->searchPaths()); + m_engine.m_observeMode = observeMode; +} + +ScriptEngine::Importer::~Importer() +{ + m_engine.m_requireResults.clear(); + m_engine.m_currentDirPathStack.pop(); + m_engine.m_extensionSearchPathsStack.pop(); + m_engine.uninstallImportFunctions(); +} + +void ScriptEngine::Importer::run() +{ + for (const JsImport &jsImport : m_fileCtx->jsImports()) + m_engine.import(jsImport, m_targetObject); + if (m_engine.m_observeMode == ObserveMode::Enabled) { + for (JSValue &sv : m_engine.m_requireResults) + m_engine.observeImport(sv); + } +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h index 68e346b68..4a55392e3 100644 --- a/src/lib/corelib/language/scriptengine.h +++ b/src/lib/corelib/language/scriptengine.h @@ -45,9 +45,11 @@ #include <buildgraph/requestedartifacts.h> #include <buildgraph/requesteddependencies.h> #include <logging/logger.h> +#include <quickjs.h> #include <tools/codelocation.h> #include <tools/filetime.h> #include <tools/porting.h> +#include <tools/scripttools.h> #include <tools/set.h> #include <QtCore/qdir.h> @@ -56,8 +58,8 @@ #include <QtCore/qprocess.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptengine.h> - +#include <atomic> +#include <functional> #include <memory> #include <mutex> #include <stack> @@ -68,8 +70,10 @@ namespace qbs { namespace Internal { class Artifact; +class Evaluator; class JsImport; class PrepareScriptObserver; +class RuleNode; class ScriptImporter; class ScriptPropertyObserver; @@ -86,35 +90,33 @@ public: }; using DubiousContextList = std::vector<DubiousContext>; - -/* - * ScriptObject that acquires resources, for example a file handle. - * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources. - */ -class ResourceAcquiringScriptObject -{ -public: - virtual ~ResourceAcquiringScriptObject() = default; - virtual void releaseResources() = 0; -}; +enum class JsValueOwner { Caller, ScriptEngine }; // TODO: This smells like cheating. Should always be Caller. enum class ObserveMode { Enabled, Disabled }; -class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine +class QBS_AUTOTEST_EXPORT ScriptEngine { - Q_OBJECT struct PrivateTag {}; public: ScriptEngine(Logger &logger, EvalContext evalContext, PrivateTag); - ~ScriptEngine() override; + ~ScriptEngine(); static std::unique_ptr<ScriptEngine> create(Logger &logger, EvalContext evalContext); + static ScriptEngine *engineForRuntime(const JSRuntime *runtime); + static ScriptEngine *engineForContext(const JSContext *ctx); + static LookupResult doExtraScopeLookup(JSContext *ctx, JSAtom prop); + + void reset(); Logger &logger() const { return m_logger; } - void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, + void import(const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, ObserveMode observeMode); void clearImportsCache(); + void registerEvaluator(Evaluator *evaluator); + void unregisterEvaluator(const Evaluator *evaluator); + Evaluator *evaluator() const { return m_evaluator; } + void setEvalContext(EvalContext c) { m_evalContext = c; } EvalContext evalContext() const { return m_evalContext; } void checkContext(const QString &operation, const DubiousContextList &dubiousContexts); @@ -144,14 +146,7 @@ public: } void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); void addRequestedExport(const ResolvedProduct *product) { m_requestedExports.insert(product); } - void clearRequestedProperties() { - m_propertiesRequestedInScript.clear(); - m_propertiesRequestedFromArtifact.clear(); - m_importsRequestedInScript.clear(); - m_productsWithRequestedDependencies.clear(); - m_requestedArtifacts.clear(); - m_requestedExports.clear(); - } + void clearRequestedProperties(); PropertySet propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } QHash<QString, PropertySet> propertiesRequestedFromArtifact() const { return m_propertiesRequestedFromArtifact; @@ -167,9 +162,11 @@ public: RequestedArtifacts requestedArtifacts() const { return m_requestedArtifacts; } Set<const ResolvedProduct *> requestedExports() const { return m_requestedExports; } - void addImportRequestedInScript(qint64 importValueId); + void addImportRequestedInScript(quintptr importValueId); std::vector<QString> importedFilesUsedInScript() const; + void addExternallyCachedValue(JSValue *v) { m_externallyCachedValues.push_back(v); } + void setUsesIo() { m_usesIo = true; } void clearUsesIo() { m_usesIo = false; } bool usesIo() const { return m_usesIo; } @@ -183,11 +180,8 @@ public: QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName, const PropertyMapConstPtr &propertyMap); - void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor); - void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value); + void setObservedProperty(JSValue &object, const QString &name, const JSValue &value); void unobserveProperties(); - void setDeprecatedProperty(QScriptValue &object, const QString &name, const QString &newName, - const QScriptValue &value); PrepareScriptObserver *observer() const { return m_observer.get(); } QProcessEnvironment environment() const; @@ -206,23 +200,34 @@ public: QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; } Set<QString> imports() const; - static QScriptValueList argumentList(const QStringList &argumentNames, - const QScriptValue &context); - QStringList uncaughtExceptionBacktraceOrEmpty() const { - return hasUncaughtException() ? uncaughtExceptionBacktrace() : QStringList(); - } - bool hasErrorOrException(const QScriptValue &v) const { - return v.isError() || hasUncaughtException(); - } - QScriptValue lastErrorValue(const QScriptValue &v) const { - return v.isError() ? v : uncaughtException(); - } - QString lastErrorString(const QScriptValue &v) const { return lastErrorValue(v).toString(); } - CodeLocation lastErrorLocation(const QScriptValue &v, - const CodeLocation &fallbackLocation = CodeLocation()) const; - ErrorInfo lastError(const QScriptValue &v, - const CodeLocation &fallbackLocation = CodeLocation()) const; + JSValue newObject() const; + JSValue newArray(int length, JsValueOwner owner); + void takeOwnership(JSValue v); + JSValue undefinedValue() const { return JS_UNDEFINED; } + JSValue toScriptValue(const QVariant &v, quintptr id = 0) { return asJsValue(v, id); } + JSValue evaluate(JsValueOwner resultOwner, const QString &code, + const QString &filePath = QString(), int line = 1, + const JSValueList &scopeChain = {}); + void setLastLookupStatus(bool success) { m_lastLookupWasSuccess = success; } + JSContext *context() const { return m_context; } + JSValue globalObject() const { return m_globalObject; } + void setGlobalObject(JSValue obj) { m_globalObject = obj; } + void handleJsProperties(JSValueConst obj, const PropertyHandler &handler); + ScopedJsValueList argumentList(const QStringList &argumentNames, const JSValue &context) const; + + using GetProperty = int (*)(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); + using GetPropertyNames = int (*)(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj); + JSClassID registerClass(const char *name, JSClassCall *constructor, JSClassFinalizer *finalizer, + JSValue scope, + GetPropertyNames getPropertyNames = nullptr, + GetProperty getProperty = nullptr); + JSClassID getClassId(const char *name) const; + + JsException checkAndClearException(const CodeLocation &fallbackLocation) const; + JSValue throwError(const QString &message) const; void cancel(); @@ -231,77 +236,100 @@ public: bool isActive() const { return m_active; } void setActive(bool on) { m_active = on; } - using QScriptEngine::newFunction; - - template <typename T, typename E, - typename = std::enable_if_t<std::is_pointer_v<T>>, - typename = std::enable_if_t<std::is_pointer_v<E>>, - typename = std::enable_if_t<std::is_base_of_v< - QScriptEngine, std::remove_pointer_t<E>>> - > QScriptValue newFunction(QScriptValue (*signature)(QScriptContext *, E, T), T arg) { - return QScriptEngine::newFunction( - reinterpret_cast<FunctionWithArgSignature>(signature), - reinterpret_cast<void *>(const_cast< - std::add_pointer_t< - std::remove_const_t< - std::remove_pointer_t<T>>>>(arg))); - } - - QScriptClass *modulePropertyScriptClass() const; - void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass); + JSClassID modulePropertyScriptClass() const; + void setModulePropertyScriptClass(JSClassID modulePropertyScriptClass); - QScriptClass *productPropertyScriptClass() const { return m_productPropertyScriptClass; } - void setProductPropertyScriptClass(QScriptClass *productPropertyScriptClass) + JSClassID productPropertyScriptClass() const { return m_productPropertyScriptClass; } + void setProductPropertyScriptClass(JSClassID productPropertyScriptClass) { m_productPropertyScriptClass = productPropertyScriptClass; } - QScriptClass *artifactsScriptClass() const { return m_artifactsScriptClass; } - void setArtifactsScriptClass(QScriptClass *artifactsScriptClass) + JSClassID artifactsScriptClass(int index) const { return m_artifactsScriptClass[index]; } + void setArtifactsScriptClass(int index, JSClassID artifactsScriptClass) { - m_artifactsScriptClass = artifactsScriptClass; + m_artifactsScriptClass[index] = artifactsScriptClass; } - void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj); - void releaseResourcesOfScriptObjects(); + JSValue artifactsMapScriptValue(const ResolvedProduct *product); + void setArtifactsMapScriptValue(const ResolvedProduct *product, JSValue value); + JSValue artifactsMapScriptValue(const ResolvedModule *module); + void setArtifactsMapScriptValue(const ResolvedModule *module, JSValue value); + + JSValue getArtifactProperty(JSValue obj, + const std::function<JSValue(const Artifact *)> &propGetter); - QScriptValue &productScriptValuePrototype(const ResolvedProduct *product) + JSValue& baseProductScriptValue(const ResolvedProduct *product) { - return m_productScriptValues[product]; + return m_baseProductScriptValues[product]; } - QScriptValue &projectScriptValue(const ResolvedProject *project) + JSValue &projectScriptValue(const ResolvedProject *project) { return m_projectScriptValues[project]; } - QScriptValue &moduleScriptValuePrototype(const ResolvedModule *module) + JSValue &baseModuleScriptValue(const ResolvedModule *module) { - return m_moduleScriptValues[module]; + return m_baseModuleScriptValues[module]; } + JSValue getArtifactScriptValue(Artifact *a, const QString &moduleName, + const std::function<void(JSValue obj)> &setup); + void releaseInputArtifactScriptValues(const RuleNode *ruleNode); + + const JSValueList &contextStack() const { return m_contextStack; } + + JSClassID dataWithPtrClass() const { return m_dataWithPtrClass; } + + JSValue getInternalExtension(const char *name) const; + void addInternalExtension(const char *name, JSValue ext); + JSValue asJsValue(const QVariant &v, quintptr id = 0, bool frozen = false); + JSValue asJsValue(const QByteArray &s); + JSValue asJsValue(const QString &s); + JSValue asJsValue(const QStringList &l); + JSValue asJsValue(const QVariantList &l, quintptr id = 0, bool frozen = false); + JSValue asJsValue(const QVariantMap &m, quintptr id = 0, bool frozen = false); + + QVariant property(const char *name) const { return m_properties.value(QLatin1String(name)); } + void setProperty(const char *k, const QVariant &v) { m_properties.insert(QLatin1String(k), v); } + private: - QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE; + class Importer { + public: + Importer(ScriptEngine &engine, const FileContextBaseConstPtr &fileCtx, + JSValue &targetObject, ObserveMode observeMode); + ~Importer(); + void run(); + + private: + ScriptEngine &m_engine; + const FileContextBaseConstPtr &m_fileCtx; + JSValue &m_targetObject; + }; - void abort(); + static int interruptor(JSRuntime *rt, void *opaqueEngine); bool gatherFileResults() const; + void setMaxStackSize(); + void setPropertyOnGlobalObject(const QString &property, JSValue value); void installQbsBuiltins(); void extendJavaScriptBuiltins(); - void installFunction(const QString &name, int length, QScriptValue *functionValue, - FunctionSignature f, QScriptValue *targetObject); - void installQbsFunction(const QString &name, int length, FunctionSignature f); - void installConsoleFunction(const QString &name, - QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)); - void installImportFunctions(); + void installConsoleFunction(JSValue consoleObj, const QString &name, LoggerLevel level); + void installImportFunctions(JSValue importScope); void uninstallImportFunctions(); - void import(const JsImport &jsImport, QScriptValue &targetObject); - void observeImport(QScriptValue &jsImport); - void importFile(const QString &filePath, QScriptValue &targetObject); - static QScriptValue js_loadExtension(QScriptContext *context, QScriptEngine *qtengine); - static QScriptValue js_loadFile(QScriptContext *context, QScriptEngine *qtengine); - static QScriptValue js_require(QScriptContext *context, QScriptEngine *qtengine); + void import(const JsImport &jsImport, JSValue &targetObject); + void observeImport(JSValue &jsImport); + void importFile(const QString &filePath, JSValue targetObject); + static JSValue js_require(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data); + JSValue mergeExtensionObjects(const JSValueList &lst); + JSValue loadInternalExtension(const QString &uri); + + static void handleUndefinedFound(JSContext *ctx); + static void handleFunctionEntered(JSContext *ctx, JSValue this_obj); + static void handleFunctionExited(JSContext *ctx); class PropertyCacheKey { @@ -320,21 +348,24 @@ private: friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed); - static std::mutex m_creationDestructionMutex; + JSRuntime * const m_jsRuntime = JS_NewRuntime(); + JSContext * const m_context = JS_NewContext(m_jsRuntime); + JSValue m_globalObject = JS_NULL; ScriptImporter *m_scriptImporter; - QScriptClass *m_modulePropertyScriptClass; - QScriptClass *m_productPropertyScriptClass = nullptr; - QScriptClass *m_artifactsScriptClass = nullptr; - QHash<JsImport, QScriptValue> m_jsImportCache; - std::unordered_map<QString, QScriptValue> m_jsFileCache; - bool m_propertyCacheEnabled; - bool m_active; + JSClassID m_modulePropertyScriptClass = 0; + JSClassID m_productPropertyScriptClass = 0; + JSClassID m_artifactsScriptClass[2] = {0, 0}; + JSClassID m_dataWithPtrClass = 0; + Evaluator *m_evaluator = nullptr; + QHash<JsImport, JSValue> m_jsImportCache; + std::unordered_map<QString, JSValue> m_jsFileCache; + bool m_propertyCacheEnabled = true; + bool m_active = false; + std::atomic_bool m_canceling = false; QHash<PropertyCacheKey, QVariant> m_propertyCache; PropertySet m_propertiesRequestedInScript; QHash<QString, PropertySet> m_propertiesRequestedFromArtifact; Logger &m_logger; - QScriptValue m_definePropertyFunction; - QScriptValue m_emptyFunction; QProcessEnvironment m_environment; QHash<QString, QString> m_canonicalFilePathResult; QHash<QString, bool> m_fileExistsResult; @@ -342,28 +373,38 @@ private: QHash<QString, FileTime> m_fileLastModifiedResult; std::stack<QString> m_currentDirPathStack; std::stack<QStringList> m_extensionSearchPathsStack; - QScriptValue m_loadFileFunction; - QScriptValue m_loadExtensionFunction; - QScriptValue m_requireFunction; - QScriptValue m_qbsObject; - QScriptValue m_consoleObject; - QScriptValue m_cancelationError; + std::stack<std::pair<QString, int>> m_evalPositions; + JSValue m_qbsObject = JS_UNDEFINED; qint64 m_elapsedTimeImporting = -1; bool m_usesIo = false; EvalContext m_evalContext; - std::vector<ResourceAcquiringScriptObject *> m_resourceAcquiringScriptObjects; const std::unique_ptr<PrepareScriptObserver> m_observer; - std::vector<std::tuple<QScriptValue, QString, QScriptValue>> m_observedProperties; - std::vector<QScriptValue> m_requireResults; - std::unordered_map<qint64, std::vector<QString>> m_filePathsPerImport; + std::vector<std::tuple<JSValue, QString, JSValue>> m_observedProperties; + JSValueList m_requireResults; + std::unordered_map<quintptr, std::vector<QString>> m_filePathsPerImport; std::vector<qint64> m_importsRequestedInScript; Set<const ResolvedProduct *> m_productsWithRequestedDependencies; RequestedArtifacts m_requestedArtifacts; Set<const ResolvedProduct *> m_requestedExports; ObserveMode m_observeMode = ObserveMode::Disabled; - std::unordered_map<const ResolvedProduct *, QScriptValue> m_productScriptValues; - std::unordered_map<const ResolvedProject *, QScriptValue> m_projectScriptValues; - std::unordered_map<const ResolvedModule *, QScriptValue> m_moduleScriptValues; + std::unordered_map<const ResolvedProduct *, JSValue> m_baseProductScriptValues; + std::unordered_map<const ResolvedProduct *, JSValue> m_productArtifactsMapScriptValues; + std::unordered_map<const ResolvedModule *, JSValue> m_moduleArtifactsMapScriptValues; + std::unordered_map<const ResolvedProject *, JSValue> m_projectScriptValues; + std::unordered_map<const ResolvedModule *, JSValue> m_baseModuleScriptValues; + QList<JSValueList> m_scopeChains; + JSValueList m_contextStack; + QHash<JSClassID, JSClassExoticMethods> m_exoticMethods; + QHash<QString, JSClassID> m_classes; + QHash<QString, JSValue> m_internalExtensions; + QHash<QString, JSValue> m_stringCache; + QHash<quintptr, JSValue> m_jsValueCache; + QHash<JSValue, int> m_evalResults; + std::vector<JSValue *> m_externallyCachedValues; + QHash<QPair<Artifact *, QString>, JSValue> m_artifactsScriptValues; + QVariantMap m_properties; + std::recursive_mutex m_artifactsMutex; + bool m_lastLookupWasSuccess = false; }; class EvalContextSwitcher diff --git a/src/lib/corelib/language/scriptimporter.cpp b/src/lib/corelib/language/scriptimporter.cpp index 40162eb12..fdb0689ad 100644 --- a/src/lib/corelib/language/scriptimporter.cpp +++ b/src/lib/corelib/language/scriptimporter.cpp @@ -48,8 +48,6 @@ #include <parser/qmljsparser_p.h> #include <tools/error.h> -#include <QtScript/qscriptvalueiterator.h> - namespace qbs { namespace Internal { @@ -121,10 +119,10 @@ ScriptImporter::ScriptImporter(ScriptEngine *scriptEngine) { } -QScriptValue ScriptImporter::importSourceCode(const QString &sourceCode, const QString &filePath, - QScriptValue &targetObject) +JSValue ScriptImporter::importSourceCode(const QString &sourceCode, const QString &filePath, + JSValue &targetObject) { - Q_ASSERT(targetObject.isObject()); + Q_ASSERT(JS_IsObject(targetObject)); // The targetObject doesn't get overwritten but enhanced by the contents of the .js file. // This is necessary for library imports that consist of multiple js files. @@ -144,19 +142,18 @@ QScriptValue ScriptImporter::importSourceCode(const QString &sourceCode, const Q code = QLatin1String("(function(){\n") + sourceCode + extractor.suffix(); } - QScriptValue result = m_engine->evaluate(code, filePath, 0); - throwOnEvaluationError(m_engine, result, [&filePath] () { return CodeLocation(filePath, 0); }); - copyProperties(result, targetObject); - return result; + ScopedJsValue result(m_engine->context(), + m_engine->evaluate(JsValueOwner::Caller, code, filePath, 0)); + throwOnEvaluationError(m_engine, [&filePath] () { return CodeLocation(filePath, 0); }); + copyProperties(m_engine->context(), result, targetObject); + return result.release(); } -void ScriptImporter::copyProperties(const QScriptValue &src, QScriptValue &dst) +void ScriptImporter::copyProperties(JSContext *ctx, const JSValue &src, JSValue &dst) { - QScriptValueIterator it(src); - while (it.hasNext()) { - it.next(); - dst.setProperty(it.name(), it.value()); - } + handleJsProperties(ctx, src, [ctx, &dst](const JSAtom &name, const JSPropertyDescriptor &desc) { + JS_SetProperty(ctx, dst, name, JS_DupValue(ctx, desc.value)); + }); } } // namespace Internal diff --git a/src/lib/corelib/language/scriptimporter.h b/src/lib/corelib/language/scriptimporter.h index 8cff09382..6bec9b088 100644 --- a/src/lib/corelib/language/scriptimporter.h +++ b/src/lib/corelib/language/scriptimporter.h @@ -40,9 +40,9 @@ #ifndef SCRIPTIMPORTER_H #define SCRIPTIMPORTER_H -#include <QtCore/qhash.h> +#include <quickjs.h> -#include <QtScript/qscriptvalue.h> +#include <QtCore/qhash.h> namespace qbs { namespace Internal { @@ -53,9 +53,10 @@ class ScriptImporter { public: ScriptImporter(ScriptEngine *scriptEngine); - QScriptValue importSourceCode(const QString &sourceCode, const QString &filePath, QScriptValue &targetObject); + JSValue importSourceCode(const QString &sourceCode, const QString &filePath, + JSValue &targetObject); - static void copyProperties(const QScriptValue &src, QScriptValue &dst); + static void copyProperties(JSContext *ctx, const JSValue &src, JSValue &dst); private: ScriptEngine *m_engine; diff --git a/src/lib/corelib/language/scriptpropertyobserver.h b/src/lib/corelib/language/scriptpropertyobserver.h index 7fb362b95..80da705ee 100644 --- a/src/lib/corelib/language/scriptpropertyobserver.h +++ b/src/lib/corelib/language/scriptpropertyobserver.h @@ -40,10 +40,11 @@ #ifndef QBS_SCRIPTPROPERTYOBSERVER_H #define QBS_SCRIPTPROPERTYOBSERVER_H +#include <quickjs.h> + #include <QtCore/qglobal.h> QT_BEGIN_NAMESPACE -class QScriptValue; class QString; QT_END_NAMESPACE @@ -64,8 +65,8 @@ public: virtual ~ScriptPropertyObserver(); - virtual void onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) = 0; + virtual void onPropertyRead(const JSValue &object, const QString &name, + const JSValue &value) = 0; protected: ScriptEngine * engine() const { return m_engine; } diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp index 5a4da2c8f..634f54faf 100644 --- a/src/lib/corelib/language/value.cpp +++ b/src/lib/corelib/language/value.cpp @@ -48,29 +48,65 @@ namespace qbs { namespace Internal { -Value::Value(Type t, bool createdByPropertiesBlock) - : m_type(t), m_definingItem(nullptr), m_createdByPropertiesBlock(createdByPropertiesBlock) +Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t) { + if (createdByPropertiesBlock) + m_flags |= OriginPropertiesBlock; } -Value::Value(const Value &other) +Value::Value(const Value &other, ItemPool &pool) : m_type(other.m_type), - m_definingItem(other.m_definingItem), - m_next(other.m_next ? other.m_next->clone() : ValuePtr()), - m_createdByPropertiesBlock(other.m_createdByPropertiesBlock) + m_scope(other.m_scope), + m_scopeName(other.m_scopeName), + m_next(other.m_next ? other.m_next->clone(pool) : ValuePtr()), + m_candidates(other.m_candidates), + m_flags(other.m_flags) { } Value::~Value() = default; -Item *Value::definingItem() const +void Value::setScope(Item *scope, const QString &scopeName) { - return m_definingItem; + m_scope = scope; + m_scopeName = scopeName; } -void Value::setDefiningItem(Item *item) +int Value::priority(const Item *productItem) const { - m_definingItem = item; + if (m_priority == -1) + m_priority = calculatePriority(productItem); + return m_priority; +} + +int Value::calculatePriority(const Item *productItem) const +{ + if (setInternally()) + return INT_MAX; + if (setByCommandLine()) + return INT_MAX - 1; + if (setByProfile()) + return 2; + if (!scope()) + return 1; + if (scope()->type() == ItemType::Product) + return INT_MAX - 2; + if (!scope()->isPresentModule()) + return 0; + const auto it = std::find_if( + productItem->modules().begin(), productItem->modules().end(), + [this](const Item::Module &m) { return m.item == scope(); }); + QBS_CHECK(it != productItem->modules().end()); + return INT_MAX - 3 - it->maxDependsChainLength; +} + +void Value::resetPriority() +{ + m_priority = -1; + if (m_next) + m_next->resetPriority(); + for (const ValuePtr &v : m_candidates) + v->resetPriority(); } ValuePtr Value::next() const @@ -85,6 +121,11 @@ void Value::setNext(const ValuePtr &next) m_next = next; } +bool Value::setInternally() const +{ + return type() == VariantValueType && !setByProfile() && !setByCommandLine(); +} + JSSourceValue::JSSourceValue(bool createdByPropertiesBlock) : Value(JSSourceValueType, createdByPropertiesBlock) @@ -93,19 +134,18 @@ JSSourceValue::JSSourceValue(bool createdByPropertiesBlock) { } -JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other) +JSSourceValue::JSSourceValue(const JSSourceValue &other, ItemPool &pool) : Value(other, pool) { m_sourceCode = other.m_sourceCode; m_line = other.m_line; m_column = other.m_column; m_file = other.m_file; - m_flags = other.m_flags; m_baseValue = other.m_baseValue - ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone()) + ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone(pool)) : JSSourceValuePtr(); m_alternatives = transformed<std::vector<Alternative>>( - other.m_alternatives, [](const auto &alternative) { - return alternative.clone(); }); + other.m_alternatives, [&pool](const auto &alternative) { + return alternative.clone(pool); }); } JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock) @@ -115,9 +155,9 @@ JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock) JSSourceValue::~JSSourceValue() = default; -ValuePtr JSSourceValue::clone() const +ValuePtr JSSourceValue::clone(ItemPool &pool) const { - return std::make_shared<JSSourceValue>(*this); + return std::make_shared<JSSourceValue>(*this, pool); } QString JSSourceValue::sourceCodeForEvaluation() const @@ -142,24 +182,45 @@ CodeLocation JSSourceValue::location() const return CodeLocation(m_file->filePath(), m_line, m_column); } -void JSSourceValue::setHasFunctionForm(bool b) +void JSSourceValue::clearAlternatives() { - if (b) - m_flags |= HasFunctionForm; - else - m_flags &= ~HasFunctionForm; + m_alternatives.clear(); } -void JSSourceValue::clearAlternatives() +void JSSourceValue::setScope(Item *scope, const QString &scopeName) { - m_alternatives.clear(); + Value::setScope(scope, scopeName); + if (m_baseValue) + m_baseValue->setScope(scope, scopeName); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->setScope(scope, scopeName); +} + +void JSSourceValue::resetPriority() +{ + Value::resetPriority(); + if (m_baseValue) + m_baseValue->resetPriority(); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->resetPriority(); +} + +void JSSourceValue::addCandidate(const ValuePtr &v) +{ + Value::addCandidate(v); + if (m_baseValue) + m_baseValue->addCandidate(v); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->addCandidate(v); } -void JSSourceValue::setDefiningItem(Item *item) +void JSSourceValue::setCandidates(const std::vector<ValuePtr> &candidates) { - Value::setDefiningItem(item); + Value::setCandidates(candidates); + if (m_baseValue) + m_baseValue->setCandidates(candidates); for (const JSSourceValue::Alternative &a : m_alternatives) - a.value->setDefiningItem(item); + a.value->setCandidates(candidates); } ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock) @@ -174,29 +235,51 @@ ItemValuePtr ItemValue::create(Item *item, bool createdByPropertiesBlock) return std::make_shared<ItemValue>(item, createdByPropertiesBlock); } -ValuePtr ItemValue::clone() const +ValuePtr ItemValue::clone(ItemPool &pool) const { - return create(m_item->clone(), createdByPropertiesBlock()); + return create(m_item->clone(pool), createdByPropertiesBlock()); } +class StoredVariantValue : public VariantValue +{ +public: + explicit StoredVariantValue(QVariant v) : VariantValue(std::move(v)) {} + + quintptr id() const override { return quintptr(this); } +}; + VariantValue::VariantValue(QVariant v) : Value(VariantValueType, false) , m_value(std::move(v)) { } -VariantValuePtr VariantValue::create(const QVariant &v) +VariantValue::VariantValue(const VariantValue &other, ItemPool &pool) + : Value(other, pool), m_value(other.m_value) {} + +template<typename T> +VariantValuePtr createImpl(const QVariant &v) { if (!v.isValid()) - return invalidValue(); + return VariantValue::invalidValue(); if (static_cast<QMetaType::Type>(v.userType()) == QMetaType::Bool) return v.toBool() ? VariantValue::trueValue() : VariantValue::falseValue(); - return std::make_shared<VariantValue>(v); + return std::make_shared<T>(v); +} + +VariantValuePtr VariantValue::create(const QVariant &v) +{ + return createImpl<VariantValue>(v); +} + +VariantValuePtr VariantValue::createStored(const QVariant &v) +{ + return createImpl<StoredVariantValue>(v); } -ValuePtr VariantValue::clone() const +ValuePtr VariantValue::clone(ItemPool &pool) const { - return std::make_shared<VariantValue>(*this); + return std::make_shared<VariantValue>(*this, pool); } const VariantValuePtr &VariantValue::falseValue() diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h index 5f6b5ca16..1a6746e24 100644 --- a/src/lib/corelib/language/value.h +++ b/src/lib/corelib/language/value.h @@ -42,6 +42,7 @@ #include "forward_decls.h" #include <tools/codelocation.h> +#include <QtCore/qstring.h> #include <QtCore/qvariant.h> #include <vector> @@ -49,42 +50,86 @@ namespace qbs { namespace Internal { class Item; +class ItemPool; class ValueHandler; class Value { public: - enum Type - { + enum Type { JSSourceValueType, ItemValueType, VariantValueType }; + enum Flag { + NoFlags = 0x00, + SourceUsesBase = 0x01, + SourceUsesOuter = 0x02, + SourceUsesOriginal = 0x04, + HasFunctionForm = 0x08, + ExclusiveListValue = 0x10, + BuiltinDefaultValue = 0x20, + OriginPropertiesBlock = 0x40, + OriginProfile = 0x80, + OriginCommandLine = 0x100, + }; + Q_DECLARE_FLAGS(Flags, Flag) + Value(Type t, bool createdByPropertiesBlock); - Value(const Value &other); + Value(const Value &other) = delete; + Value(const Value &other, ItemPool &pool); virtual ~Value(); Type type() const { return m_type; } virtual void apply(ValueHandler *) = 0; - virtual ValuePtr clone() const = 0; + virtual ValuePtr clone(ItemPool &) const = 0; virtual CodeLocation location() const { return {}; } - Item *definingItem() const; - virtual void setDefiningItem(Item *item); + Item *scope() const { return m_scope; } + virtual void setScope(Item *scope, const QString &scopeName); + QString scopeName() const { return m_scopeName; } + int priority(const Item *productItem) const; + virtual void resetPriority(); ValuePtr next() const; void setNext(const ValuePtr &next); - bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; } - void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; } - void clearCreatedByPropertiesBlock() { m_createdByPropertiesBlock = false; } + virtual void addCandidate(const ValuePtr &v) { m_candidates.push_back(v); } + const std::vector<ValuePtr> &candidates() const { return m_candidates; } + virtual void setCandidates(const std::vector<ValuePtr> &candidates) { m_candidates = candidates; } + + bool createdByPropertiesBlock() const { return m_flags & OriginPropertiesBlock; } + void markAsSetByProfile() { m_flags |= OriginProfile; } + bool setByProfile() const { return m_flags & OriginProfile; } + void markAsSetByCommandLine() { m_flags |= OriginCommandLine; } + bool setByCommandLine() const { return m_flags & OriginCommandLine; } + bool setInternally() const; + bool expired(const Item *productItem) const { return priority(productItem) == 0; } + + void setSourceUsesBase() { m_flags |= SourceUsesBase; } + bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); } + void setSourceUsesOuter() { m_flags |= SourceUsesOuter; } + bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); } + void setSourceUsesOriginal() { m_flags |= SourceUsesOriginal; } + bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); } + void setHasFunctionForm() { m_flags |= HasFunctionForm; } + bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); } + void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; } + bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); } + void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; } + bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); } private: + int calculatePriority(const Item *productItem) const; + Type m_type; - Item *m_definingItem; + Item *m_scope = nullptr; + QString m_scopeName; ValuePtr m_next; - bool m_createdByPropertiesBlock; + std::vector<ValuePtr> m_candidates; + Flags m_flags; + mutable int m_priority = -1; }; class ValueHandler @@ -99,27 +144,15 @@ class JSSourceValue : public Value { friend class ItemReaderASTVisitor; - enum Flag - { - NoFlags = 0x00, - SourceUsesBase = 0x01, - SourceUsesOuter = 0x02, - SourceUsesOriginal = 0x04, - HasFunctionForm = 0x08, - ExclusiveListValue = 0x10, - BuiltinDefaultValue = 0x20, - }; - Q_DECLARE_FLAGS(Flags, Flag) - public: explicit JSSourceValue(bool createdByPropertiesBlock); - JSSourceValue(const JSSourceValue &other); + JSSourceValue(const JSSourceValue &other, ItemPool &pool); static JSSourceValuePtr QBS_AUTOTEST_EXPORT create(bool createdByPropertiesBlock = false); ~JSSourceValue() override; void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; void setSourceCode(QStringView sourceCode) { m_sourceCode = sourceCode; } QStringView sourceCode() const { return m_sourceCode; } @@ -133,17 +166,6 @@ public: void setFile(const FileContextPtr &file) { m_file = file; } const FileContextPtr &file() const { return m_file; } - void setSourceUsesBaseFlag() { m_flags |= SourceUsesBase; } - bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); } - bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); } - bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); } - bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); } - void setHasFunctionForm(bool b); - void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; } - bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); } - void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; } - bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); } - const JSSourceValuePtr &baseValue() const { return m_baseValue; } void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; } @@ -160,10 +182,10 @@ public: Alternative() = default; Alternative(PropertyData c, PropertyData o, JSSourceValuePtr v) : condition(std::move(c)), overrideListProperties(std::move(o)), value(std::move(v)) {} - Alternative clone() const + Alternative clone(ItemPool &pool) const { return Alternative(condition, overrideListProperties, - std::static_pointer_cast<JSSourceValue>(value->clone())); + std::static_pointer_cast<JSSourceValue>(value->clone(pool))); } PropertyData condition; @@ -176,14 +198,16 @@ public: void addAlternative(const Alternative &alternative) { m_alternatives.push_back(alternative); } void clearAlternatives(); - void setDefiningItem(Item *item) override; + void setScope(Item *scope, const QString &scopeName) override; + void resetPriority() override; + void addCandidate(const ValuePtr &v) override; + void setCandidates(const std::vector<ValuePtr> &candidates) override; private: QStringView m_sourceCode; int m_line; int m_column; FileContextPtr m_file; - Flags m_flags; JSSourceValuePtr m_baseValue; std::vector<Alternative> m_alternatives; }; @@ -200,7 +224,7 @@ public: private: void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; Item *m_item; }; @@ -210,12 +234,15 @@ class VariantValue : public Value { public: explicit VariantValue(QVariant v); + VariantValue(const VariantValue &v, ItemPool &pool); static VariantValuePtr create(const QVariant &v = QVariant()); + static VariantValuePtr createStored(const QVariant &v = QVariant()); void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; const QVariant &value() const { return m_value; } + virtual quintptr id() const { return 0; } static const VariantValuePtr &falseValue(); static const VariantValuePtr &trueValue(); |