diff options
Diffstat (limited to 'src/lib/corelib/loader/loaderutils.cpp')
-rw-r--r-- | src/lib/corelib/loader/loaderutils.cpp | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp new file mode 100644 index 000000000..05a077dbe --- /dev/null +++ b/src/lib/corelib/loader/loaderutils.cpp @@ -0,0 +1,961 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "loaderutils.h" + +#include "itemreader.h" + +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/itempool.h> +#include <language/language.h> +#include <language/resolvedfilecontext.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +namespace qbs::Internal { + +QString fullProductDisplayName(const QString &name, const QString &multiplexId) +{ + static const auto multiplexIdToString =[](const QString &id) { + return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8())); + }; + QString result = name; + if (!multiplexId.isEmpty()) + result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexId)); + return result; +} + +void adjustParametersScopes(Item *item, Item *scope) +{ + if (item->type() == ItemType::ModuleParameters) { + item->setScope(scope); + return; + } + + for (const auto &value : item->properties()) { + if (value->type() == Value::ItemValueType) + adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope); + } +} + +QString ProductContext::uniqueName() const +{ + return ResolvedProduct::uniqueName(name, multiplexConfigurationId); +} + +QString ProductContext::displayName() const +{ + return fullProductDisplayName(name, multiplexConfigurationId); +} + +void ProductContext::handleError(const ErrorInfo &error) +{ + const bool alreadyHadError = delayedError.hasError(); + if (!alreadyHadError) { + delayedError.append(Tr::tr("Error while handling product '%1':") + .arg(name), item->location()); + } + if (error.isInternalError()) { + if (alreadyHadError) { + qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString() + << "in product" << name; + return; + } + } + const auto errorItems = error.items(); + for (const ErrorItem &ei : errorItems) + delayedError.append(ei.description(), ei.codeLocation()); + project->topLevelProject->addDisabledItem(item); +} + +TopLevelProjectContext::~TopLevelProjectContext() { qDeleteAll(m_projects); } + +bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator) +{ + if (evaluator.boolValue(item, StringConstants::conditionProperty())) + return true; + addDisabledItem(item); + return false; +} + +void TopLevelProjectContext::checkCancelation() +{ + if (m_progressObserver && m_progressObserver->canceled()) + m_canceled = true; + if (m_canceled) + throw CancelException(); +} + +QString TopLevelProjectContext::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) +{ + const auto sourceCodeGuard = m_sourceCode.lock(); + QString &code = sourceCodeGuard.get()[value->sourceCode()]; + if (!code.isNull()) + return code; + code = value->sourceCodeForEvaluation(); + return code; +} + +ScriptFunctionPtr TopLevelProjectContext::scriptFunctionValue(Item *item, const QString &name) +{ + const JSSourceValuePtr value = item->sourceProperty(name); + QBS_CHECK(value); + const auto scriptFunctionMapGuard = m_scriptFunctionMap.lock(); + ScriptFunctionPtr &script = scriptFunctionMapGuard.get()[value->location()]; + 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; +} + +QString TopLevelProjectContext::sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) +{ + const auto scriptFunctionMapGuard = m_scriptFunctions.lock(); + QString &scriptFunction = scriptFunctionMapGuard.get()[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; +} + +const ResolvedFileContextPtr &TopLevelProjectContext::resolvedFileContext( + const FileContextConstPtr &ctx) +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) + result = ResolvedFileContext::create(*ctx); + return result; +} + +void TopLevelProjectContext::removeProductToHandle(const ProductContext &product) +{ + std::unique_lock lock(m_productsToHandle.mutex); + m_productsToHandle.data.remove(&product); +} + +bool TopLevelProjectContext::isProductQueuedForHandling(const ProductContext &product) const +{ + std::shared_lock lock(m_productsToHandle.mutex); + return m_productsToHandle.data.contains(&product); +} + +void TopLevelProjectContext::addDisabledItem(Item *item) +{ + m_disabledItems.lock().get() << item; +} + +bool TopLevelProjectContext::isDisabledItem(const Item *item) const +{ + return m_disabledItems.lock_shared().get().contains(item); +} + +void TopLevelProjectContext::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +ProgressObserver *TopLevelProjectContext::progressObserver() const { return m_progressObserver; } + +void TopLevelProjectContext::addQueuedError(const ErrorInfo &error) +{ + m_queuedErrors.lock().get() << error; +} + +void TopLevelProjectContext::addProfileConfig(const QString &profileName, + const QVariantMap &profileConfig) +{ + m_profileConfigs.insert(profileName, profileConfig); +} + +std::optional<QVariantMap> TopLevelProjectContext::profileConfig(const QString &profileName) const +{ + const auto it = m_profileConfigs.constFind(profileName); + if (it == m_profileConfigs.constEnd()) + return {}; + return it.value().toMap(); +} + +void TopLevelProjectContext::addProjectLevelProbe(const ProbeConstPtr &probe) +{ + m_probesInfo.projectLevelProbes << probe; +} + +const std::vector<ProbeConstPtr> TopLevelProjectContext::projectLevelProbes() const +{ + return m_probesInfo.projectLevelProbes; +} + +void TopLevelProjectContext::addProduct(ProductContext &product) +{ + m_productsByName.insert({product.name, &product}); +} + +void TopLevelProjectContext::addProductByType(ProductContext &product, const FileTags &tags) +{ + const auto productsByTypeGuard = m_productsByType.lock(); + for (const FileTag &tag : tags) + productsByTypeGuard.get().insert({tag, &product}); +} + +ProductContext *TopLevelProjectContext::productWithNameAndConstraint( + const QString &name, const std::function<bool (ProductContext &)> &constraint) +{ + const auto candidates = m_productsByName.equal_range(name); + for (auto it = candidates.first; it != candidates.second; ++it) { + ProductContext * const candidate = it->second; + if (constraint(*candidate)) + return candidate; + } + return nullptr; +} + +std::vector<ProductContext *> TopLevelProjectContext::productsWithNameAndConstraint( + const QString &name, const std::function<bool (ProductContext &)> &constraint) +{ + std::vector<ProductContext *> result; + const auto candidates = m_productsByName.equal_range(name); + for (auto it = candidates.first; it != candidates.second; ++it) { + ProductContext * const candidate = it->second; + if (constraint(*candidate)) + result << candidate; + } + return result; +} + +std::vector<ProductContext *> TopLevelProjectContext::productsWithTypeAndConstraint( + const FileTags &tags, const std::function<bool (ProductContext &)> &constraint) +{ + const auto productsByTypeGuard = m_productsByType.lock_shared(); + std::vector<ProductContext *> matchingProducts; + for (const FileTag &typeTag : tags) { + const auto range = productsByTypeGuard.get().equal_range(typeTag); + for (auto it = range.first; it != range.second; ++it) { + if (constraint(*it->second)) + matchingProducts.push_back(it->second); + } + } + return matchingProducts; +} + +std::vector<std::pair<ProductContext *, CodeLocation>> +TopLevelProjectContext::finishedProductsWithBulkDependency(const FileTag &tag) const +{ + return m_reverseBulkDependencies.value(tag); +} + +void TopLevelProjectContext::registerBulkDependencies(ProductContext &product) +{ + for (const auto &tagAndLoc : product.bulkDependencies) { + for (const FileTag &tag : tagAndLoc.first) + m_reverseBulkDependencies[tag].emplace_back(&product, tagAndLoc.second); + } +} + +void TopLevelProjectContext::addProjectNameUsedInOverrides(const QString &name) +{ + m_projectNamesUsedInOverrides << name; +} + +const Set<QString> &TopLevelProjectContext::projectNamesUsedInOverrides() const +{ + return m_projectNamesUsedInOverrides; +} + +void TopLevelProjectContext::addProductNameUsedInOverrides(const QString &name) +{ + m_productNamesUsedInOverrides << name; +} + +const Set<QString> &TopLevelProjectContext::productNamesUsedInOverrides() const +{ + return m_productNamesUsedInOverrides; +} + +void TopLevelProjectContext::addMultiplexConfiguration(const QString &id, const QVariantMap &config) +{ + m_multiplexConfigsById.insert(std::make_pair(id, config)); +} + +QVariantMap TopLevelProjectContext::multiplexConfiguration(const QString &id) const +{ + if (id.isEmpty()) + return {}; + const auto it = m_multiplexConfigsById.find(id); + QBS_CHECK(it != m_multiplexConfigsById.end() && !it->second.isEmpty()); + return it->second; +} + +std::lock_guard<std::mutex> TopLevelProjectContext::moduleProvidersCacheLock() +{ + return std::lock_guard<std::mutex>(m_moduleProvidersCacheMutex); +} + +void TopLevelProjectContext::setModuleProvidersCache(const ModuleProvidersCache &cache) +{ + m_moduleProvidersCache = cache; +} + +ModuleProviderInfo *TopLevelProjectContext::moduleProvider(const ModuleProvidersCacheKey &key) +{ + if (const auto it = m_moduleProvidersCache.find(key); it != m_moduleProvidersCache.end()) + return &(*it); + return nullptr; +} + +ModuleProviderInfo &TopLevelProjectContext::addModuleProvider(const ModuleProvidersCacheKey &key, + const ModuleProviderInfo &provider) +{ + return m_moduleProvidersCache[key] = provider; +} + +void TopLevelProjectContext::addParameterDeclarations(const Item *moduleProto, + const Item::PropertyDeclarationMap &decls) +{ + m_parameterDeclarations.lock().get().insert({moduleProto, decls}); +} + +Item::PropertyDeclarationMap TopLevelProjectContext::parameterDeclarations(Item *moduleProto) const +{ + const auto parameterDeclarationsGuard = m_parameterDeclarations.lock_shared(); + const auto &declarations = parameterDeclarationsGuard.get(); + if (const auto it = declarations.find(moduleProto); it != declarations.end()) { + return it->second; + } + return {}; +} + +void TopLevelProjectContext::setParameters(const Item *moduleProto, const QVariantMap ¶meters) +{ + m_parameters.lock().get().insert({moduleProto, parameters}); +} + +QVariantMap TopLevelProjectContext::parameters(Item *moduleProto) const +{ + const auto parametersGuard = m_parameters.lock_shared(); + const auto ¶meters = parametersGuard.get(); + if (const auto it = parameters.find(moduleProto); it != parameters.end()) { + return it->second; + } + return {}; +} + +void TopLevelProjectContext::addCodeLink(const QString &sourceFile, const CodeRange &sourceRange, + const CodeLocation &target) +{ + const auto codeLinksGuard = m_codeLinks.lock(); + QList<CodeLocation> &links = codeLinksGuard.get()[sourceFile][sourceRange]; + if (!links.contains(target)) + links << target; +} + +QString TopLevelProjectContext::findModuleDirectory( + const QualifiedId &module, const QString &searchPath, + const std::function<QString()> &findOnDisk) +{ + const auto modulePathCacheGuard = m_modulePathCache.lock(); + auto &path = modulePathCacheGuard.get()[{searchPath, module}]; + if (!path) + path = findOnDisk(); + return *path; +} + +QStringList TopLevelProjectContext::getModuleFilesForDirectory( + const QString &dir, const std::function<QStringList ()> &findOnDisk) +{ + const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock(); + auto &list = moduleFilesGuard.get()[dir]; + if (!list) + list = findOnDisk(); + return *list; +} + +void TopLevelProjectContext::removeModuleFileFromDirectoryCache(const QString &filePath) +{ + const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock(); + auto &moduleFiles = moduleFilesGuard.get(); + const auto it = moduleFiles.find(FileInfo::path(filePath)); + QBS_CHECK(it != moduleFiles.end()); + auto &files = it->second; + QBS_CHECK(files); + files->removeOne(filePath); +} + +void TopLevelProjectContext::addUnknownProfilePropertyError(const Item *moduleProto, + const ErrorInfo &error) +{ + m_unknownProfilePropertyErrors.lock().get()[moduleProto].push_back(error); +} + +const std::vector<ErrorInfo> &TopLevelProjectContext::unknownProfilePropertyErrors( + const Item *moduleProto) const +{ + const auto errorsGuard = m_unknownProfilePropertyErrors.lock_shared(); + const auto &errors = errorsGuard.get(); + if (const auto it = errors.find(moduleProto); it != errors.end()) { + return it->second; + } + static const std::vector<ErrorInfo> empty; + return empty; +} + +Item *TopLevelProjectContext::getModulePrototype(const QString &filePath, const QString &profile, + const std::function<Item *()> &produce) +{ + const auto modulePrototypesGuard = m_modulePrototypes.lock(); + auto &prototypeList = modulePrototypesGuard.get()[filePath]; + for (const auto &prototype : prototypeList) { + if (prototype.second == profile) + return prototype.first; + } + Item * const module = produce(); + if (module) + prototypeList.emplace_back(module, profile); + return module; +} + +void TopLevelProjectContext::addLocalProfile(const QString &name, const QVariantMap &values, + const CodeLocation &location) +{ + if (m_localProfiles.contains(name)) + throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(name), location); + m_localProfiles.insert(name, values); +} + +void TopLevelProjectContext::checkForLocalProfileAsTopLevelProfile(const QString &topLevelProfile) +{ + for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) { + if (it.key() != topLevelProfile) + continue; + + // This covers the edge case that a locally defined profile was specified as the + // top-level profile, in which case we must invalidate the qbs module prototype that was + // created in early setup before local profiles were handled. + const auto prototypesGuard = m_modulePrototypes.lock(); + auto &modulePrototypes = prototypesGuard.get(); + QBS_CHECK(modulePrototypes.size() == 1); + modulePrototypes.clear(); + break; + } +} + +std::lock_guard<std::mutex> TopLevelProjectContext::probesCacheLock() +{ + return std::lock_guard<std::mutex>(m_probesMutex); +} + +void TopLevelProjectContext::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) +{ + for (const ProbeConstPtr& probe : oldProbes) + m_probesInfo.oldProjectProbes[probe->globalId()] << probe; +} + +ProbeConstPtr TopLevelProjectContext::findOldProjectProbe(const QString &id, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProjectProbes.value(id)) { + if (filter(oldProbe)) + return oldProbe; + } + return {}; +} + +void TopLevelProjectContext::setOldProductProbes( + const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) +{ + m_probesInfo.oldProductProbes = oldProbes; +} + +ProbeConstPtr TopLevelProjectContext::findOldProductProbe(const QString &productName, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProductProbes.value(productName)) { + if (filter(oldProbe)) + return oldProbe; + } + return {}; +} + +void TopLevelProjectContext::addNewlyResolvedProbe(const ProbeConstPtr &probe) +{ + m_probesInfo.currentProbes[probe->location()] << probe; +} + +ProbeConstPtr TopLevelProjectContext::findCurrentProbe(const CodeLocation &location, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &probe : m_probesInfo.currentProbes.value(location)) { + if (filter(probe)) + return probe; + } + return {}; +} + +void TopLevelProjectContext::collectDataFromEngine(const ScriptEngine &engine) +{ + const auto project = dynamic_cast<TopLevelProject *>(m_projects.front()->project.get()); + QBS_CHECK(project); + project->canonicalFilePathResults.insert(engine.canonicalFilePathResults()); + project->fileExistsResults.insert(engine.fileExistsResults()); + project->directoryEntriesResults.insert(engine.directoryEntriesResults()); + project->fileLastModifiedResults.insert(engine.fileLastModifiedResults()); + project->environment.insert(engine.environment()); + project->buildSystemFiles.unite(engine.imports()); +} + +ItemPool &TopLevelProjectContext::createItemPool() +{ + m_itemPools.push_back(std::make_unique<ItemPool>()); + return *m_itemPools.back(); +} + +class LoaderState::Private +{ +public: + Private(LoaderState &q, const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, ItemPool &itemPool, ScriptEngine &engine, + Logger &&logger) + : parameters(parameters), topLevelProject(topLevelProject), itemPool(itemPool), + logger(std::move(logger)), itemReader(q), evaluator(&engine) + { + this->logger.clearWarnings(); + this->logger.storeWarnings(); + } + + const SetupProjectParameters ¶meters; + TopLevelProjectContext &topLevelProject; + ItemPool &itemPool; + + Logger logger; + ItemReader itemReader; + Evaluator evaluator; +}; + +LoaderState::LoaderState(const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, ItemPool &itemPool, + ScriptEngine &engine, Logger logger) + : d(makePimpl<Private>(*this, parameters, topLevelProject, itemPool, engine, std::move(logger))) +{ + d->itemReader.init(); +} + +LoaderState::~LoaderState() = default; +const SetupProjectParameters &LoaderState::parameters() const { return d->parameters; } +ItemPool &LoaderState::itemPool() { return d->itemPool; } +Evaluator &LoaderState::evaluator() { return d->evaluator; } +Logger &LoaderState::logger() { return d->logger; } +ItemReader &LoaderState::itemReader() { return d->itemReader; } +TopLevelProjectContext &LoaderState::topLevelProject() { return d->topLevelProject; } + +static QString verbatimValue(LoaderState &state, const ValueConstPtr &value) +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = std::static_pointer_cast<const JSSourceValue>( + value); + result = state.topLevelProject().sourceCodeForEvaluation(sourceValue); + } + return result; +} + +static void resolveRuleArtifactBinding( + LoaderState &state, const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, QualifiedIdSet *seenBindings) +{ + for (auto it = item->properties().constBegin(); it != item->properties().constEnd(); ++it) { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(state, 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 = state.topLevelProject().sourceCodeForEvaluation(sourceValue); + rab.location = sourceValue->location(); + ruleArtifact->bindings.push_back(rab); + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +static void resolveRuleArtifact(LoaderState &state, 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(state, item->property(StringConstants::filePathProperty())); + artifact->fileTags = state.evaluator().fileTagsValue(item, StringConstants::fileTagsProperty()); + artifact->alwaysUpdated = state.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( + state, artifact, std::static_pointer_cast<ItemValue>(it.value())->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!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(state, rule, child); + } + + rule->name = evaluator.stringValue(item, StringConstants::nameProperty()); + rule->prepareScript.initialize(state.topLevelProject().scriptFunctionValue( + item, StringConstants::prepareProperty())); + rule->outputArtifactsScript.initialize(state.topLevelProject().scriptFunctionValue( + item, StringConstants::outputArtifactsProperty())); + rule->outputFileTags = 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 = evaluator.boolValue(item, StringConstants::multiplexProperty()); + rule->alwaysRun = evaluator.boolValue(item, StringConstants::alwaysRunProperty()); + rule->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty()); + rule->inputsFromDependencies + = evaluator.fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); + bool requiresInputsSet = false; + rule->requiresInputs = evaluator.boolValue(item, StringConstants::requiresInputsProperty(), + &requiresInputsSet); + if (!requiresInputsSet) + rule->requiresInputs = rule->declaresInputs(); + rule->auxiliaryInputs + = evaluator.fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); + rule->excludedInputs + = evaluator.fileTagsValue(item, StringConstants::excludedInputsProperty()); + if (rule->excludedInputs.empty()) { + rule->excludedInputs = evaluator.fileTagsValue( + item, StringConstants::excludedAuxiliaryInputsProperty()); + } + rule->explicitlyDependsOn + = evaluator.fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); + rule->explicitlyDependsOnFromDependencies = evaluator.fileTagsValue( + item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); + rule->module = moduleContext ? 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 (productContext) { + rule->product = productContext->product.get(); + productContext->product->rules.push_back(rule); + } else { + projectContext->rules.push_back(rule); + } +} + +void resolveFileTagger(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) + return; + std::vector<FileTaggerConstPtr> &fileTaggers = productContext + ? productContext->product->fileTaggers + : projectContext->fileTaggers; + const QStringList patterns = 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 = 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 = evaluator.intValue(item, StringConstants::priorityProperty()); + fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); +} + +void resolveJobLimit(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) + return; + const QString jobPool = 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 = 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 = moduleContext + ? moduleContext->jobLimits + : productContext ? productContext->product->jobLimits + : projectContext->jobLimits; + JobLimit jobLimit(jobPool, jobCount); + const int oldLimit = jobLimits.getLimit(jobPool); + if (oldLimit == -1 || oldLimit > jobCount) + jobLimits.setJobLimit(jobLimit); +} + +const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +bool ProductContext::dependenciesResolvingPending() const +{ + return (!dependenciesContext || !dependenciesContext->dependenciesResolved) + && !product && !delayedError.hasError(); +} + +std::pair<ProductDependency, ProductContext *> ProductContext::pendingDependency() const +{ + return dependenciesContext ? dependenciesContext->pendingDependency() + : std::make_pair(ProductDependency::None, nullptr); +} + +TimingData &TimingData::operator+=(const TimingData &other) +{ + dependenciesResolving += other.dependenciesResolving; + moduleProviders += other.moduleProviders; + moduleInstantiation += other.moduleInstantiation; + propertyMerging += other.propertyMerging; + groupsSetup += other.groupsSetup; + groupsResolving += other.groupsResolving; + preparingProducts += other.preparingProducts; + resolvingProducts += other.resolvingProducts; + probes += other.probes; + propertyEvaluation += other.propertyEvaluation; + propertyChecking += other.propertyChecking; + return *this; +} + +DependenciesContext::~DependenciesContext() = default; + +ItemReaderCache::AstCacheEntry &ItemReaderCache::retrieveOrSetupCacheEntry( + const QString &filePath, const std::function<void (AstCacheEntry &)> &setup) +{ + const auto astCacheGuard = m_astCache.lock(); + AstCacheEntry &entry = astCacheGuard.get()[filePath]; + if (!entry.ast) { + setup(entry); + m_filesRead << filePath; + } + return entry; +} + +const QStringList &ItemReaderCache::retrieveOrSetDirectoryEntries( + const QString &dir, const std::function<QStringList ()> &findOnDisk) +{ + const auto directoryEntriesGuard = m_directoryEntries.lock(); + auto &entries = directoryEntriesGuard.get()[dir]; + if (!entries) + entries = findOnDisk(); + return *entries; +} + +bool ItemReaderCache::AstCacheEntry::addProcessingThread() +{ + return m_processingThreads.lock().get().insert(std::this_thread::get_id()).second; +} + +void ItemReaderCache::AstCacheEntry::removeProcessingThread() +{ + m_processingThreads.lock().get().remove(std::this_thread::get_id()); +} + +class DependencyParametersMerger +{ +public: + DependencyParametersMerger(std::vector<Item::Module::ParametersWithPriority> &&candidates) + : m_candidates(std::move(candidates)) { } + QVariantMap merge(); + +private: + void merge(QVariantMap ¤t, const QVariantMap &next, int nextPrio); + + const std::vector<Item::Module::ParametersWithPriority> m_candidates; + + struct Conflict { + Conflict(QStringList path, QVariant val1, QVariant val2, int prio) + : path(std::move(path)), val1(std::move(val1)), val2(std::move(val2)), priority(prio) {} + QStringList path; + QVariant val1; + QVariant val2; + int priority; + }; + std::vector<Conflict> m_conflicts; + QVariantMap m_currentValue; + int m_currentPrio = INT_MIN; + QStringList m_path; +}; + +QVariantMap mergeDependencyParameters(std::vector<Item::Module::ParametersWithPriority> &&candidates) +{ + return DependencyParametersMerger(std::move(candidates)).merge(); +} + +QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2) +{ + return mergeDependencyParameters({std::make_pair(m1, 0), std::make_pair(m2, 0)}); +} + +QVariantMap DependencyParametersMerger::merge() +{ + for (const auto &next : m_candidates) { + merge(m_currentValue, next.first, next.second); + m_currentPrio = next.second; + } + + if (!m_conflicts.empty()) { + ErrorInfo error(Tr::tr("Conflicting parameter values encountered:")); + for (const Conflict &conflict : m_conflicts) { + // TODO: Location would be nice ... + error.append(Tr::tr(" Parameter '%1' cannot be both '%2' and '%3'.") + .arg(conflict.path.join(QLatin1Char('.')), + conflict.val1.toString(), conflict.val2.toString())); + } + throw error; + } + + return m_currentValue; +} + +void DependencyParametersMerger::merge(QVariantMap ¤t, const QVariantMap &next, int nextPrio) +{ + for (auto it = next.begin(); it != next.end(); ++it) { + m_path << it.key(); + const QVariant &newValue = it.value(); + QVariant ¤tValue = current[it.key()]; + if (newValue.userType() == QMetaType::QVariantMap) { + QVariantMap mdst = currentValue.toMap(); + merge(mdst, it.value().toMap(), nextPrio); + currentValue = mdst; + } else { + if (m_currentPrio == nextPrio) { + if (currentValue.isValid() && !qVariantsEqual(currentValue, newValue)) + m_conflicts.emplace_back(m_path, currentValue, newValue, m_currentPrio); + } else { + removeIf(m_conflicts, [this](const Conflict &conflict) { + return m_path == conflict.path; + }); + } + currentValue = newValue; + } + m_path.removeLast(); + } +} + +} // namespace qbs::Internal |