diff options
Diffstat (limited to 'src/lib/corelib/language')
-rw-r--r-- | src/lib/corelib/language/builtindeclarations.cpp | 48 | ||||
-rw-r--r-- | src/lib/corelib/language/evaluator.cpp | 82 | ||||
-rw-r--r-- | src/lib/corelib/language/evaluator.h | 15 | ||||
-rw-r--r-- | src/lib/corelib/language/item.cpp | 133 | ||||
-rw-r--r-- | src/lib/corelib/language/item.h | 97 | ||||
-rw-r--r-- | src/lib/corelib/language/itempool.cpp | 2 | ||||
-rw-r--r-- | src/lib/corelib/language/language.cpp | 132 | ||||
-rw-r--r-- | src/lib/corelib/language/language.h | 52 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleproviderinfo.h | 19 | ||||
-rw-r--r-- | src/lib/corelib/language/propertydeclaration.cpp | 109 | ||||
-rw-r--r-- | src/lib/corelib/language/propertydeclaration.h | 14 | ||||
-rw-r--r-- | src/lib/corelib/language/propertymapinternal.h | 2 | ||||
-rw-r--r-- | src/lib/corelib/language/qualifiedid.cpp | 2 | ||||
-rw-r--r-- | src/lib/corelib/language/scriptengine.cpp | 74 | ||||
-rw-r--r-- | src/lib/corelib/language/scriptengine.h | 11 | ||||
-rw-r--r-- | src/lib/corelib/language/value.cpp | 52 | ||||
-rw-r--r-- | src/lib/corelib/language/value.h | 21 |
17 files changed, 600 insertions, 265 deletions
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index acf50b4f3..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,25 +328,23 @@ 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.setAllowedChildTypes({ItemType::Depends, ItemType::FileTagger, ItemType::Group, + ItemType::JobLimit, ItemType::Parameter, ItemType::Parameters, + ItemType::Probe, ItemType::PropertyOptions, + ItemType::Rule, ItemType::Scanner}); PropertyDeclaration nameDecl = nameProperty(); PropertyDeclaration::Flags nameFlags = nameDecl.flags(); nameFlags |= PropertyDeclaration::ReadOnlyFlag; diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp index 9a19828bb..084f2f4a9 100644 --- a/src/lib/corelib/language/evaluator.cpp +++ b/src/lib/corelib/language/evaluator.cpp @@ -77,13 +77,15 @@ static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t 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(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED, - getEvalPropertyNames, getEvalProperty)) + getEvalPropertyNames, getEvalPropertySafe)) { scriptEngine->registerEvaluator(this); } @@ -96,6 +98,7 @@ Evaluator::~Evaluator() 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)) { @@ -191,6 +194,15 @@ std::optional<QStringList> Evaluator::optionalStringListValue( 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 { const ValueConstPtr v = item->property(name); @@ -215,24 +227,13 @@ JSValue 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 = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass); attachPointerTo(scriptValue, edata); return scriptValue; } -void Evaluator::clearCache(const Item *item) -{ - const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item), - m_scriptEngine->dataWithPtrClass()); - if (data) { - for (const auto value : std::as_const(data->valueCache)) - JS_FreeValue(m_scriptEngine->context(), value); - data->valueCache.clear(); - } -} - void Evaluator::handleEvaluationError(const Item *item, const QString &name) { throwOnEvaluationError(m_scriptEngine, [&item, &name] () { @@ -278,6 +279,42 @@ Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConst return result; } +// 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) +{ + std::lock_guard lock(m_cacheInvalidationMutex); + m_invalidatedCaches << item; +} + +void Evaluator::clearCache(const Item *item) +{ + 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::clearCache(EvaluationData &edata) +{ + for (const auto value : std::as_const(edata.valueCache)) + JS_FreeValue(m_scriptEngine->context(), value); + edata.valueCache.clear(); +} + void throwOnEvaluationError(ScriptEngine *engine, const std::function<CodeLocation()> &provideFallbackCodeLocation) { @@ -773,7 +810,7 @@ private: void handle(VariantValue *variantValue) override { - *result = engine->toScriptValue(variantValue->value()); + *result = engine->toScriptValue(variantValue->value(), variantValue->id()); engine->takeOwnership(*result); } }; @@ -853,10 +890,11 @@ static void collectValuesFromNextChain( struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; }; static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item, - const QString &name, const EvaluationData *data) + const QString &name, EvaluationData *data) { Evaluator * const evaluator = data->evaluator; - const bool isModuleInstance = item->type() == ItemType::ModuleInstance; + 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)) { @@ -871,6 +909,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item 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) @@ -893,6 +932,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item 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()); @@ -957,5 +997,15 @@ static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue o 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 } // namespace qbs diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h index d791a4c5d..d86e08eb1 100644 --- a/src/lib/corelib/language/evaluator.h +++ b/src/lib/corelib/language/evaluator.h @@ -49,11 +49,13 @@ #include <QtCore/qhash.h> #include <functional> +#include <mutex> #include <optional> #include <stack> namespace qbs { namespace Internal { +class EvaluationData; class FileTags; class Logger; class PropertyDeclaration; @@ -83,6 +85,8 @@ 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, JSValue &v); @@ -99,6 +103,8 @@ public: 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(); } @@ -113,9 +119,10 @@ public: bool isNonDefaultValue(const Item *item, const QString &name) const; private: - void onItemPropertyChanged(Item *item) override { clearCache(item); } + void onItemPropertyChanged(Item *item) override { invalidateCache(item); } bool evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet); + void clearCache(EvaluationData &edata); ScriptEngine * const m_scriptEngine; const JSClassID m_scriptClass; @@ -124,6 +131,8 @@ private: QString m_pathPropertiesBaseDir; PropertyDependencies m_propertyDependencies; std::stack<QualifiedId> m_requestedProperties; + std::mutex m_cacheInvalidationMutex; + Set<const Item *> m_invalidatedCaches; bool m_valueCacheEnabled = false; }; @@ -139,7 +148,9 @@ public: m_evaluator->setPathPropertiesBaseDir(baseDir); } - ~EvalCacheEnabler() + ~EvalCacheEnabler() { reset(); } + + void reset() { m_evaluator->setCachingEnabled(false); m_evaluator->clearPathPropertiesBaseDir(); diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp index 647d05aa2..e5de8f195 100644 --- a/src/lib/corelib/language/item.cpp +++ b/src/lib/corelib/language/item.cpp @@ -40,13 +40,13 @@ #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> @@ -60,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; @@ -91,14 +83,14 @@ Item *Item::clone() const dup->m_children.reserve(m_children.size()); for (const Item * const child : std::as_const(m_children)) { - Item *clonedChild = child->clone(); + 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; @@ -128,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)) @@ -139,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); @@ -156,29 +152,30 @@ 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(); - ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()), + ItemValuePtr result = ItemValue::create(Item::create(&pool, itemTemplate->type()), createdByPropertiesBlock); setProperty(name, result); return result; @@ -188,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); } @@ -196,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); } @@ -211,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); @@ -230,12 +249,12 @@ void Item::addModule(const Item::Module &module) QBS_CHECK(none_of(m_modules, [&](const Module &m) { if (m.name != module.name) return false; - if (!!module.productInfo != !!m.productInfo) + if (!!module.product != !!m.product) return true; - if (!module.productInfo) + if (!module.product) return true; - if (module.productInfo->multiplexId == m.productInfo->multiplexId - && module.productInfo->profile == m.productInfo->profile) { + if (module.product->multiplexConfigurationId == m.product->multiplexConfigurationId + && module.product->profileName == m.product->profileName) { return true; } return false; @@ -245,17 +264,13 @@ void Item::addModule(const Item::Module &module) m_modules.push_back(module); } -void Item::setObserver(ItemObserver *observer) const -{ - QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten - m_observer = observer; -} - 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 @@ -270,8 +285,14 @@ bool Item::isPresentModule() const return v && v->type() == Value::JSSourceValueType; } +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) { @@ -353,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); } @@ -457,5 +509,10 @@ void setScopeForDescendants(Item *item, Item *scope) } } +CodeRange Item::codeRange() const +{ + return {{m_location.line(), m_location.column()}, m_endPosition}; +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h index 337cad78a..d0dde98c4 100644 --- a/src/lib/corelib/language/item.h +++ b/src/lib/corelib/language/item.h @@ -53,7 +53,9 @@ #include <QtCore/qlist.h> #include <QtCore/qmap.h> -#include <optional> +#include <atomic> +#include <mutex> +#include <utility> #include <vector> namespace qbs { @@ -64,32 +66,45 @@ 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 { QualifiedId name; Item *item = nullptr; - struct ProductInfo { - ProductInfo(Item *i, const QString &m, const QString &p) - : item(i), multiplexId(m), profile(p) {} - Item *item = nullptr; - QString multiplexId; - QString profile; - }; - std::optional<ProductInfo> productInfo; // Set if and only if the dep is a product. + ProductContext *product = nullptr; // Set if and only if the dep is a product. - // All items that declared an explicit dependency on this module. Can contain any + // All the sites that declared an explicit dependency on this module. Can contain any // number of module instances and at most one product. - std::vector<Item *> loadingItems; + using ParametersWithPriority = std::pair<QVariantMap, int>; + 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; @@ -105,11 +120,11 @@ public: 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; } @@ -119,8 +134,8 @@ public: const QList<Item *> &children() const { return m_children; } QList<Item *> &children() { return m_children; } Item *child(ItemType type, bool checkForMultiple = true) const; - const PropertyMap &properties() const { return m_properties; } - PropertyMap &properties() { return m_properties; } + const 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; @@ -141,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; } @@ -165,6 +182,7 @@ public: static void removeChild(Item *parent, Item *child); void dump() const; bool isPresentModule() const; + bool isFallbackModule() const; void setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger); void copyProperty(const QString &propertyName, Item *target) const; void overrideProperties( @@ -180,18 +198,23 @@ public: 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; @@ -199,6 +222,10 @@ 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; } @@ -207,6 +234,22 @@ Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString 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/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/language.cpp b/src/lib/corelib/language/language.cpp index 33fd3c6a4..b3f4b2a64 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -51,7 +51,8 @@ #include <buildgraph/rulegraph.h> // TODO: Move to language? #include <buildgraph/transformer.h> #include <jsextensions/jsextensions.h> -#include <loader/productitemmultiplexer.h> +#include <language/value.h> +#include <loader/loaderutils.h> #include <logging/categories.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> @@ -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. @@ -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; } @@ -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) @@ -427,7 +411,7 @@ QString ResolvedProduct::uniqueName() const QString ResolvedProduct::fullDisplayName() const { - return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId); + return fullProductDisplayName(name, multiplexConfigurationId); } QString ResolvedProduct::profile() const @@ -503,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) { @@ -710,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('/')) { @@ -738,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 @@ -757,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(); @@ -785,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; @@ -798,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) { @@ -931,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) @@ -958,20 +965,19 @@ 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 diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index 1dae572a1..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> @@ -116,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; @@ -134,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: @@ -144,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; }; @@ -230,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: @@ -248,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; - const ResolvedGroup *group = nullptr; // The owning group. + // to be restored by the owning class + QString prefix; + QString baseDir; + QString buildDir; + Set<QString> expandedFiles; + + // 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 @@ -287,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); @@ -692,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. @@ -723,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; diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h index 500d370cc..c35ed220a 100644 --- a/src/lib/corelib/language/moduleproviderinfo.h +++ b/src/lib/corelib/language/moduleproviderinfo.h @@ -83,24 +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 class StoredModuleProviderInfo { public: - using CacheKey = std::tuple< - QString /*name*/, - QVariantMap /*config*/, - QVariantMap /*qbsModule*/, - int /*lookup*/ - >; - using ModuleProvidersCache = QHash<CacheKey, ModuleProviderInfo>; - ModuleProvidersCache providers; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp index 215918462..d56ab3bb0 100644 --- a/src/lib/corelib/language/propertydeclaration.cpp +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -46,6 +46,7 @@ #include "value.h" #include <api/languageinfo.h> +#include <loader/loaderutils.h> #include <logging/translator.h> #include <tools/error.h> #include <tools/setupprojectparameters.h> @@ -105,7 +106,6 @@ public: DeprecationInfo deprecationInfo; }; - PropertyDeclaration::PropertyDeclaration() : d(new PropertyDeclarationData) { @@ -308,17 +308,78 @@ 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(const Set<Item *> &disabledItems, - const SetupProjectParameters ¶ms, Logger &logger) - : m_disabledItems(disabledItems) - , m_params(params) - , m_logger(logger) - { } - void operator()(Item *item) { handleItem(item); } + PropertyDeclarationCheck(LoaderState &loaderState) : m_loaderState(loaderState) {} + void operator()(Item *item) + { + m_checkingProject = item->type() == ItemType::Project; + handleItem(item); + } private: void handle(JSSourceValue *value) override @@ -326,7 +387,7 @@ private: if (!value->createdByPropertiesBlock()) { const ErrorInfo error(Tr::tr("Property '%1' is not declared.") .arg(m_currentName), value->location()); - handlePropertyError(error, m_params, m_logger); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); } } void handle(ItemValue *value) override @@ -365,7 +426,7 @@ private: 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); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); return false; } @@ -373,16 +434,19 @@ private: } void handleItem(Item *item) { + if (m_checkingProject && item->type() == ItemType::Product) + return; if (!m_handledItems.insert(item).second) return; - if (m_disabledItems.contains(item) - || item->type() == ItemType::Module + 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) { + || item->type() == ItemType::PropertiesInSubProject + + || m_loaderState.topLevelProject().isDisabledItem(item)) { return; } @@ -395,9 +459,12 @@ private: const PropertyDeclaration decl = item->propertyDeclaration(it.key()); if (decl.isValid()) { const ErrorInfo deprecationError = decl.checkForDeprecation( - m_params.deprecationWarningMode(), it.value()->location(), m_logger); - if (deprecationError.hasError()) - handlePropertyError(deprecationError, m_params, m_logger); + 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(); @@ -429,20 +496,18 @@ private: Item *parentItem() const { return m_parentItems.back(); } - const Set<Item *> &m_disabledItems; + LoaderState &m_loaderState; Set<Item *> m_handledItems; std::vector<Item *> m_parentItems; QualifiedId m_currentModuleName; QString m_currentName; - const SetupProjectParameters &m_params; - Logger &m_logger; + bool m_checkingProject = false; }; } // namespace -void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems, - const SetupProjectParameters ¶ms, Logger &logger) +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState) { - PropertyDeclarationCheck(disabledItems, params, logger)(topLevelItem); + (PropertyDeclarationCheck(loaderState))(topLevelItem); } } // namespace Internal diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h index 8c87faedb..79a39ecbd 100644 --- a/src/lib/corelib/language/propertydeclaration.h +++ b/src/lib/corelib/language/propertydeclaration.h @@ -41,7 +41,6 @@ #define QBS_PROPERTYDECLARATION_H #include <tools/deprecationwarningmode.h> -#include <tools/set.h> #include <QtCore/qshareddata.h> #include <QtCore/qstring.h> @@ -53,11 +52,11 @@ QT_END_NAMESPACE namespace qbs { class CodeLocation; class ErrorInfo; -class SetupProjectParameters; namespace Internal { class DeprecationInfo; class PropertyDeclarationData; class Item; +class LoaderState; class Logger; class PropertyDeclaration @@ -129,13 +128,20 @@ public: 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, const Set<Item *> &disabledItems, - const SetupProjectParameters ¶ms, Logger &logger); +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState); } // namespace Internal 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 11d41b5c2..998384547 100644 --- a/src/lib/corelib/language/scriptengine.cpp +++ b/src/lib/corelib/language/scriptengine.cpp @@ -58,6 +58,7 @@ #include <tools/stlutils.h> #include <tools/stringconstants.h> +#include <QtCore/qdatetime.h> #include <QtCore/qdebug.h> #include <QtCore/qdiriterator.h> #include <QtCore/qfile.h> @@ -196,6 +197,10 @@ void ScriptEngine::reset() 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()); @@ -481,6 +486,38 @@ void ScriptEngine::addInternalExtension(const char *name, JSValue ext) m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext)); } +JSValue ScriptEngine::asJsValue(const QVariant &v, quintptr id, bool frozen) +{ + 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().toMSecsSinceEpoch()); + case QMetaType::QVariantMap: + return asJsValue(v.toMap(), id, frozen); + default: + return JS_UNDEFINED; + } +} + JSValue ScriptEngine::asJsValue(const QByteArray &s) { return JS_NewArrayBufferCopy( @@ -506,12 +543,21 @@ JSValue ScriptEngine::asJsValue(const QStringList &l) return array; } -JSValue ScriptEngine::asJsValue(const QVariantMap &m) +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(), makeJsVariant(m_context, it.value())); - return obj; + 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); } void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue value) @@ -520,13 +566,22 @@ void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue va setJsProperty(m_context, globalObject, property, value); } -JSValue ScriptEngine::asJsValue(const QVariantList &l) +JSValue ScriptEngine::asJsValue(const QVariantList &l, 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 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, makeJsVariant(m_context, l.at(i))); - return array; + 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) @@ -752,7 +807,7 @@ JSValue ScriptEngine::evaluate(JsValueOwner resultOwner, const QString &code, m_scopeChains << scopeChain; const QByteArray &codeStr = code.toUtf8(); - m_evalPositions.emplace(std::make_pair(filePath, line)); + 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(); @@ -773,7 +828,7 @@ ScopedJsValueList ScriptEngine::argumentList(const QStringList &argumentNames, JSValueList result; for (const auto &name : argumentNames) result.push_back(getJsProperty(m_context, context, name)); - return ScopedJsValueList(m_context, result); + return {m_context, result}; } JSClassID ScriptEngine::registerClass(const char *name, JSClassCall *constructor, @@ -1026,8 +1081,7 @@ ScriptEngine::Importer::Importer( ScriptEngine::Importer::~Importer() { - if (m_engine.m_observeMode == ObserveMode::Enabled) - m_engine.m_requireResults.clear(); + m_engine.m_requireResults.clear(); m_engine.m_currentDirPathStack.pop(); m_engine.m_extensionSearchPathsStack.pop(); m_engine.uninstallImportFunctions(); diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h index 4d797dd43..4a55392e3 100644 --- a/src/lib/corelib/language/scriptengine.h +++ b/src/lib/corelib/language/scriptengine.h @@ -58,6 +58,7 @@ #include <QtCore/qprocess.h> #include <QtCore/qstring.h> +#include <atomic> #include <functional> #include <memory> #include <mutex> @@ -204,7 +205,7 @@ public: JSValue newArray(int length, JsValueOwner owner); void takeOwnership(JSValue v); JSValue undefinedValue() const { return JS_UNDEFINED; } - JSValue toScriptValue(const QVariant &v) const { return makeJsVariant(m_context, v); } + 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 = {}); @@ -283,11 +284,12 @@ public: 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); - JSValue asJsValue(const QVariantMap &m); + 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); } @@ -359,7 +361,7 @@ private: std::unordered_map<QString, JSValue> m_jsFileCache; bool m_propertyCacheEnabled = true; bool m_active = false; - bool m_canceling = false; + std::atomic_bool m_canceling = false; QHash<PropertyCacheKey, QVariant> m_propertyCache; PropertySet m_propertiesRequestedInScript; QHash<QString, PropertySet> m_propertiesRequestedFromArtifact; @@ -396,6 +398,7 @@ private: 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; diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp index fabc64ccd..634f54faf 100644 --- a/src/lib/corelib/language/value.cpp +++ b/src/lib/corelib/language/value.cpp @@ -54,11 +54,11 @@ Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t) m_flags |= OriginPropertiesBlock; } -Value::Value(const Value &other) +Value::Value(const Value &other, ItemPool &pool) : m_type(other.m_type), m_scope(other.m_scope), m_scopeName(other.m_scopeName), - m_next(other.m_next ? other.m_next->clone() : ValuePtr()), + m_next(other.m_next ? other.m_next->clone(pool) : ValuePtr()), m_candidates(other.m_candidates), m_flags(other.m_flags) { @@ -134,18 +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_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) @@ -155,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 @@ -235,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 262813841..1a6746e24 100644 --- a/src/lib/corelib/language/value.h +++ b/src/lib/corelib/language/value.h @@ -50,6 +50,7 @@ namespace qbs { namespace Internal { class Item; +class ItemPool; class ValueHandler; class Value @@ -76,12 +77,13 @@ public: 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 *scope() const { return m_scope; } @@ -144,13 +146,13 @@ class JSSourceValue : public Value 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; } @@ -180,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; @@ -222,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; }; @@ -232,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(); |