diff options
author | Christian Kandeler <christian.kandeler@digia.com> | 2014-01-09 17:50:40 +0100 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@digia.com> | 2014-01-10 18:11:22 +0100 |
commit | 81af9acaa295a574c1cb5e6714725197dac7f530 (patch) | |
tree | cc8c94467f49a7d267e5249f624874feecc7eed4 /src/lib/corelib/language/projectresolver.cpp | |
parent | 2fe25eb3f20ffb4e58cb559f2bcb9950c963290a (diff) |
Move Qt profile setup into a dedicated library.
Otherwise all changes to the implementation will have to be duplicated
in IDEs.
Change-Id: I61e6d4fa1ee9b724eb5d9de9f233dc915a6c8bc3
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
Diffstat (limited to 'src/lib/corelib/language/projectresolver.cpp')
-rw-r--r-- | src/lib/corelib/language/projectresolver.cpp | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp new file mode 100644 index 000000000..06205c7fe --- /dev/null +++ b/src/lib/corelib/language/projectresolver.cpp @@ -0,0 +1,999 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "projectresolver.h" + +#include "artifactproperties.h" +#include "builtindeclarations.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "moduleloader.h" +#include "propertymapinternal.h" +#include "scriptengine.h" +#include <jsextensions/moduleproperties.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/scripttools.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> + +#include <QFileInfo> +#include <QDir> +#include <QSet> +#include <set> + +namespace qbs { +namespace Internal { + +extern bool debugProperties; + +static const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +ProjectResolver::ProjectResolver(ModuleLoader *ldr, const BuiltinDeclarations *builtins, + const Logger &logger) + : m_evaluator(ldr->evaluator()) + , m_builtins(builtins) + , m_logger(logger) + , m_engine(m_evaluator->engine()) + , m_progressObserver(0) +{ +} + +ProjectResolver::~ProjectResolver() +{ +} + +void ProjectResolver::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) +{ + const QList<ResolvedProductPtr> allProducts = project->allProducts(); + for (int i = 0; i < allProducts.count(); ++i) { + const ResolvedProductConstPtr product1 = allProducts.at(i); + const QString productName = product1->name; + for (int j = i + 1; j < allProducts.count(); ++j) { + const ResolvedProductConstPtr product2 = allProducts.at(j); + if (product2->name == productName) { + ErrorInfo error; + error.append(Tr::tr("Duplicate product name '%1'.").arg(productName)); + error.append(Tr::tr("First product defined here."), product1->location); + error.append(Tr::tr("Second product defined here."), product2->location); + throw error; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolve(ModuleLoaderResult &loadResult, + const SetupProjectParameters &setupParameters) +{ + QBS_CHECK(FileInfo::isAbsolute(setupParameters.buildRoot())); + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[PR] resolving " << loadResult.root->file()->filePath(); + + ProjectContext projectContext; + projectContext.loadResult = &loadResult; + m_setupParams = setupParameters; + m_productContext = 0; + m_moduleContext = 0; + resolveTopLevelProject(loadResult.root, &projectContext); + TopLevelProjectPtr top = projectContext.project.staticCast<TopLevelProject>(); + checkForDuplicateProductNames(top); + top->buildSystemFiles.unite(loadResult.qbsFiles); + return top; +} + +void ProjectResolver::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); + } +} + +QString ProjectResolver::verbatimValue(const ValueConstPtr &value) const +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = value.staticCast<const JSSourceValue>(); + result = sourceValue->sourceCode(); + } + return result; +} + +QString ProjectResolver::verbatimValue(Item *item, const QString &name) const +{ + return verbatimValue(item->property(name)); +} + +void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(item); + Q_UNUSED(projectContext); +} + +static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) +{ + QSet<QString> subProjectNames; + QSet<ResolvedProjectPtr> projectsInNeedOfNameChange; + foreach (const ResolvedProjectPtr &p, parentProject->subProjects) { + if (subProjectNames.contains(p->name)) + projectsInNeedOfNameChange << p; + else + subProjectNames << p->name; + makeSubProjectNamesUniqe(p); + } + while (!projectsInNeedOfNameChange.isEmpty()) { + QSet<ResolvedProjectPtr>::Iterator it = projectsInNeedOfNameChange.begin(); + while (it != projectsInNeedOfNameChange.end()) { + const ResolvedProjectPtr p = *it; + p->name += QLatin1Char('_'); + if (!subProjectNames.contains(p->name)) { + subProjectNames << p->name; + it = projectsInNeedOfNameChange.erase(it); + } else { + ++it; + } + } + } +} + +void ProjectResolver::resolveTopLevelProject(Item *item, ProjectContext *projectContext) +{ + if (m_progressObserver) + m_progressObserver->setMaximum(projectContext->loadResult->productInfos.count()); + const TopLevelProjectPtr project = TopLevelProject::create(); + project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); + project->buildDirectory + = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), project->id()); + projectContext->project = project; + resolveProject(item, projectContext); + project->usedEnvironment = m_engine->usedEnvironment(); + project->fileExistsResults = m_engine->fileExistsResults(); + project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); + project->environment = m_engine->environment(); + project->buildSystemFiles = m_engine->imports(); + makeSubProjectNamesUniqe(project); + resolveProductDependencies(projectContext); +} + +void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + projectContext->project->name = m_evaluator->stringValue(item, QLatin1String("name")); + projectContext->project->location = item->location(); + if (projectContext->project->name.isEmpty()) + projectContext->project->name = FileInfo::baseName(item->location().fileName()); // FIXME: Must also be changed in item? + projectContext->project->enabled + = m_evaluator->boolValue(item, QLatin1String("condition")); + if (!projectContext->project->enabled) + return; + + projectContext->dummyModule = ResolvedModule::create(); + + QVariantMap projectProperties; + for (QMap<QString, PropertyDeclaration>::const_iterator it + = item->propertyDeclarations().constBegin(); + it != item->propertyDeclarations().constEnd(); ++it) { + if (it.value().flags.testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + continue; + const ValueConstPtr v = item->property(it.key()); + QBS_ASSERT(v && v->type() != Value::ItemValueType, continue); + projectProperties.insert(it.key(), m_evaluator->property(item, it.key()).toVariant()); + } + projectContext->project->setProjectProperties(projectProperties); + + ItemFuncMap mapping; + mapping["Project"] = &ProjectResolver::resolveProject; + mapping["SubProject"] = &ProjectResolver::resolveSubProject; + mapping["Product"] = &ProjectResolver::resolveProduct; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Rule"] = &ProjectResolver::resolveRule; + + foreach (Item *child, item->children()) + callItemFunction(mapping, child, projectContext); + + foreach (const ResolvedProductPtr &product, projectContext->project->products) + postProcess(product, projectContext); +} + +void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + ProjectContext subProjectContext = createProjectContext(projectContext); + + Item * const projectItem = item->child(QLatin1String("Project")); + if (projectItem) { + resolveProject(projectItem, &subProjectContext); + return; + } + + // No project item was found, which means the project was disabled. + subProjectContext.project->enabled = false; + Item * const propertiesItem = item->child(QLatin1String("Properties")); + if (propertiesItem) { + subProjectContext.project->name + = m_evaluator->stringValue(propertiesItem, QLatin1String("name")); + } +} + +class ModuleNameEquals +{ + QString m_str; +public: + ModuleNameEquals(const QString &str) + : m_str(str) + {} + + bool operator()(const Item::Module &module) + { + return module.name.count() == 1 && module.name.first() == m_str; + } +}; + +void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + ProductContext productContext; + m_productContext = &productContext; + productContext.item = item; + const QString productSourceDirectory = QFileInfo(item->file()->filePath()).absolutePath(); + item->setProperty(QLatin1String("sourceDirectory"), + VariantValue::create(productSourceDirectory)); + item->setProperty(QLatin1String("buildDirectory"), VariantValue::create(projectContext + ->project->topLevelProject()->buildDirectory)); + ResolvedProductPtr product = ResolvedProduct::create(); + product->project = projectContext->project; + m_productItemMap.insert(product, item); + projectContext->project->products += product; + productContext.product = product; + product->name = m_evaluator->stringValue(item, QLatin1String("name")); + if (product->name.isEmpty()) { + product->name = FileInfo::completeBaseName(item->file()->filePath()); + item->setProperty("name", VariantValue::create(product->name)); + } + m_logger.qbsTrace() << "[PR] resolveProduct " << product->name; + + if (std::find_if(item->modules().begin(), item->modules().end(), + ModuleNameEquals(product->name)) != item->modules().end()) { + throw ErrorInfo( + Tr::tr("The product name '%1' collides with a module name.").arg(product->name), + item->location()); + } + + ModuleLoader::overrideItemProperties(item, product->name, m_setupParams.overriddenValuesTree()); + m_productsByName.insert(product->name, product); + product->enabled = m_evaluator->boolValue(item, QLatin1String("condition")); + product->additionalFileTags + = m_evaluator->fileTagsValue(item, QLatin1String("additionalFileTags")); + product->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("type")); + product->targetName = m_evaluator->stringValue(item, QLatin1String("targetName")); + product->sourceDirectory = productSourceDirectory; + product->destinationDirectory + = m_evaluator->stringValue(item, QLatin1String("destinationDirectory")); + product->location = item->location(); + product->properties = PropertyMapInternal::create(); + product->properties->setValue(createProductConfig()); + ModuleProperties::init(m_evaluator->scriptValue(item), product); + + QList<Item *> subItems = item->children(); + const ValuePtr filesProperty = item->property(QLatin1String("files")); + if (filesProperty) { + Item *fakeGroup = Item::create(item->pool()); + fakeGroup->setFile(item->file()); + fakeGroup->setLocation(item->location()); + fakeGroup->setScope(item); + fakeGroup->setTypeName(QLatin1String("Group")); + fakeGroup->setProperty(QLatin1String("name"), VariantValue::create(product->name)); + fakeGroup->setProperty(QLatin1String("files"), filesProperty); + fakeGroup->setProperty(QLatin1String("excludeFiles"), + item->property(QLatin1String("excludeFiles"))); + fakeGroup->setProperty(QLatin1String("overrideTags"), VariantValue::create(false)); + m_builtins->setupItemForBuiltinType(fakeGroup); + subItems.prepend(fakeGroup); + } + + ItemFuncMap mapping; + mapping["Depends"] = &ProjectResolver::ignoreItem; + mapping["Rule"] = &ProjectResolver::resolveRule; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Transformer"] = &ProjectResolver::resolveTransformer; + mapping["Group"] = &ProjectResolver::resolveGroup; + mapping["Export"] = &ProjectResolver::resolveExport; + mapping["Probe"] = &ProjectResolver::ignoreItem; + + foreach (Item *child, subItems) + callItemFunction(mapping, child, projectContext); + + foreach (const Item::Module &module, item->modules()) + resolveModule(module.name, module.item, projectContext); + + m_productContext = 0; + if (m_progressObserver) + m_progressObserver->incrementProgressValue(); +} + +void ProjectResolver::resolveModule(const QStringList &moduleName, Item *item, + ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, QLatin1String("present"))) + return; + ModuleContext moduleContext; + moduleContext.module = ResolvedModule::create(); + m_moduleContext = &moduleContext; + + const ResolvedModulePtr &module = moduleContext.module; + module->name = ModuleLoader::fullModuleName(moduleName); + module->setupBuildEnvironmentScript = scriptFunctionValue(item, "setupBuildEnvironment"); + module->setupRunEnvironmentScript = scriptFunctionValue(item, "setupRunEnvironment"); + + m_productContext->product->additionalFileTags + += m_evaluator->fileTagsValue(item, "additionalProductFileTags"); + + foreach (const Item::Module &m, item->modules()) + module->moduleDependencies += ModuleLoader::fullModuleName(m.name); + + m_productContext->product->modules += module; + + ItemFuncMap mapping; + mapping["Rule"] = &ProjectResolver::resolveRule; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Transformer"] = &ProjectResolver::resolveTransformer; + mapping["PropertyOptions"] = &ProjectResolver::ignoreItem; + mapping["Depends"] = &ProjectResolver::ignoreItem; + mapping["Probe"] = &ProjectResolver::ignoreItem; + foreach (Item *child, item->children()) + callItemFunction(mapping, child, projectContext); + + m_moduleContext = 0; +} + +static void createSourceArtifact(const ResolvedProductConstPtr &rproduct, + const PropertyMapPtr &properties, + const QString &fileName, + const FileTags &fileTags, + bool overrideTags, + QList<SourceArtifactPtr> &artifactList) +{ + SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->absoluteFilePath = FileInfo::resolvePath(rproduct->sourceDirectory, fileName); + artifact->fileTags = fileTags; + artifact->overrideFileTags = overrideTags; + artifact->properties = properties; + artifactList += artifact; +} + +static bool isSomeModulePropertySet(Item *group) +{ + for (QMap<QString, ValuePtr>::const_iterator it = group->properties().constBegin(); + it != group->properties().constEnd(); ++it) + { + if (it.value()->type() == Value::ItemValueType) { + // An item value is a module value in this case. + ItemValuePtr iv = it.value().staticCast<ItemValue>(); + foreach (ValuePtr ivv, iv->item()->properties()) { + if (ivv->type() == Value::JSSourceValueType) + return true; + } + } + } + return false; +} + +void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(projectContext); + checkCancelation(); + PropertyMapPtr properties = m_productContext->product->properties; + if (isSomeModulePropertySet(item)) { + properties = PropertyMapInternal::create(); + properties->setValue(evaluateModuleValues(item)); + } + + const bool isEnabled = m_evaluator->boolValue(item, QLatin1String("condition")); + QStringList files = m_evaluator->stringListValue(item, QLatin1String("files")); + const QStringList fileTagsFilter + = m_evaluator->stringListValue(item, QLatin1String("fileTagsFilter")); + if (!fileTagsFilter.isEmpty()) { + if (Q_UNLIKELY(!files.isEmpty())) + throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), + item->location()); + if (!isEnabled) + return; + ArtifactPropertiesPtr aprops = ArtifactProperties::create(); + aprops->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); + PropertyMapPtr cfg = PropertyMapInternal::create(); + cfg->setValue(evaluateModuleValues(item)); + aprops->setPropertyMapInternal(cfg); + m_productContext->product->artifactProperties += aprops; + return; + } + if (Q_UNLIKELY(files.isEmpty() && !item->hasProperty(QLatin1String("files")))) { + // Yield an error if Group without files binding is encountered. + // An empty files value is OK but a binding must exist. + throw ErrorInfo(Tr::tr("Group without files is not allowed."), + item->location()); + } + QStringList patterns; + for (int i = files.count(); --i >= 0;) { + if (FileInfo::isPattern(files[i])) + patterns.append(files.takeAt(i)); + } + GroupPtr group = ResolvedGroup::create(); + group->prefix = m_evaluator->stringValue(item, QLatin1String("prefix")); + if (!group->prefix.isEmpty()) { + for (int i = files.count(); --i >= 0;) + files[i].prepend(group->prefix); + } + group->location = item->location(); + group->enabled = isEnabled; + group->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("fileTags")); + group->overrideTags = m_evaluator->boolValue(item, QLatin1String("overrideTags")); + + if (!patterns.isEmpty()) { + SourceWildCards::Ptr wildcards = SourceWildCards::create(); + wildcards->excludePatterns = m_evaluator->stringListValue(item, "excludeFiles"); + wildcards->prefix = group->prefix; + wildcards->patterns = patterns; + QSet<QString> files = wildcards->expandPatterns(group, m_productContext->product->sourceDirectory); + foreach (const QString &fileName, files) + createSourceArtifact(m_productContext->product, properties, fileName, + group->fileTags, group->overrideTags, wildcards->files); + group->wildcards = wildcards; + } + + foreach (const QString &fileName, files) + createSourceArtifact(m_productContext->product, properties, fileName, + group->fileTags, group->overrideTags, group->files); + ErrorInfo fileError; + foreach (const SourceArtifactConstPtr &a, group->files) { + if (!FileInfo(a->absoluteFilePath).exists()) { + fileError.append(Tr::tr("File '%1' does not exist.") + .arg(a->absoluteFilePath), item->property("files")->location()); + } + } + if (fileError.hasError()) + throw ErrorInfo(fileError); + + group->name = m_evaluator->stringValue(item, "name"); + if (group->name.isEmpty()) + group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.count()); + group->properties = properties; + m_productContext->product->groups += group; +} + +static QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) +{ + const QString args = decl.functionArgumentNames.join(QLatin1String(",")); + if (value->hasFunctionForm()) { + // Insert the argument list. + QString code = value->sourceCode(); + code.insert(10, args); + // Remove the function application "()" that has been + // added in ItemReaderASTVisitor::visitStatement. + return code.left(code.length() - 2); + } else { + return QLatin1String("(function(") + args + QLatin1String("){return ") + + value->sourceCode() + QLatin1String(";})"); + } +} + +ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const +{ + ScriptFunctionPtr script = ScriptFunction::create(); + JSSourceValuePtr value = item->sourceProperty(name); + if (value) { + const PropertyDeclaration decl = item->propertyDeclaration(name); + script->sourceCode = sourceCodeAsFunction(value, decl); + script->argumentNames = decl.functionArgumentNames; + script->location = value->location(); + script->fileContext = resolvedFileContext(value->file()); + } + return script; +} + +ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) { + result = ResolvedFileContext::create(); + result->filePath = ctx->filePath(); + result->jsExtensions = ctx->jsExtensions(); + result->jsImports = ctx->jsImports(); + } + return result; +} + +void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (!m_evaluator->boolValue(item, QLatin1String("condition"))) + return; + + RulePtr rule = Rule::create(); + + // read artifacts + bool hasAlwaysUpdatedArtifact = false; + foreach (Item *child, item->children()) { + if (Q_UNLIKELY(child->typeName() != QLatin1String("Artifact"))) + throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), + child->location()); + + resolveRuleArtifact(rule, child, &hasAlwaysUpdatedArtifact); + } + + if (Q_UNLIKELY(!hasAlwaysUpdatedArtifact)) + throw ErrorInfo(Tr::tr("At least one output artifact of a rule " + "must have alwaysUpdated set to true."), + item->location()); + + rule->script = scriptFunctionValue(item, QLatin1String("prepare")); + rule->multiplex = m_evaluator->boolValue(item, QLatin1String("multiplex")); + rule->inputs = m_evaluator->fileTagsValue(item, "inputs"); + rule->usings = m_evaluator->fileTagsValue(item, "usings"); + rule->auxiliaryInputs + = m_evaluator->fileTagsValue(item, QLatin1String("auxiliaryInputs")); + rule->explicitlyDependsOn = m_evaluator->fileTagsValue(item, "explicitlyDependsOn"); + rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + if (m_productContext) + m_productContext->product->rules += rule; + else + projectContext->rules += rule; +} + +class StringListLess +{ +public: + bool operator()(const QStringList &lhs, const QStringList &rhs) + { + const int c = qMin(lhs.count(), rhs.count()); + for (int i = 0; i < c; ++i) { + int n = lhs.at(i).compare(rhs.at(i)); + if (n < 0) + return true; + if (n > 0) + return false; + } + return lhs.count() < rhs.count(); + } +}; + +class StringListSet : public std::set<QStringList, StringListLess> +{ +public: + typedef std::pair<iterator, bool> InsertResult; +}; + +void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item, + bool *hasAlwaysUpdatedArtifact) +{ + if (!m_evaluator->boolValue(item, QLatin1String("condition"))) + return; + RuleArtifactPtr artifact = RuleArtifact::create(); + rule->artifacts += artifact; + artifact->fileName = verbatimValue(item, "fileName"); + artifact->fileTags = m_evaluator->fileTagsValue(item, "fileTags"); + artifact->alwaysUpdated = m_evaluator->boolValue(item, "alwaysUpdated"); + if (artifact->alwaysUpdated) + *hasAlwaysUpdatedArtifact = true; + + StringListSet seenBindings; + for (Item *obj = item; obj; obj = obj->prototype()) { + for (QMap<QString, ValuePtr>::const_iterator it = obj->properties().constBegin(); + it != obj->properties().constEnd(); ++it) + { + if (it.value()->type() != Value::ItemValueType) + continue; + resolveRuleArtifactBinding(artifact, it.value().staticCast<ItemValue>()->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, + Item *item, + const QStringList &namePrefix, + StringListSet *seenBindings) +{ + for (QMap<QString, ValuePtr>::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) + { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(ruleArtifact, + it.value().staticCast<ItemValue>()->item(), name, + seenBindings); + } else if (it.value()->type() == Value::JSSourceValueType) { + const StringListSet::InsertResult insertResult = seenBindings->insert(name); + if (!insertResult.second) + continue; + JSSourceValuePtr sourceValue = it.value().staticCast<JSSourceValue>(); + RuleArtifact::Binding rab; + rab.name = name; + rab.code = sourceValue->sourceCode(); + rab.location = sourceValue->location(); + ruleArtifact->bindings += rab; + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + QList<FileTaggerConstPtr> &fileTaggers = m_productContext + ? m_productContext->product->fileTaggers : projectContext->fileTaggers; + QStringList patterns = m_evaluator->stringListValue(item, QLatin1String("patterns")); + const FileTags fileTags = m_evaluator->fileTagsValue(item, "fileTags"); + if (fileTags.isEmpty()) + throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); + + // TODO: Remove in 1.3. + bool patternWasSet; + const QStringList oldPatterns = m_evaluator->stringListValue(item, QLatin1String("pattern"), + &patternWasSet); + if (patternWasSet) { + m_logger.printWarning(ErrorInfo(Tr::tr("The 'pattern' property is deprecated. Please " + "use 'patterns' instead."), item->location())); + patterns << oldPatterns; + } + + if (patterns.isEmpty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + + foreach (const QString &pattern, patterns) { + if (pattern.isEmpty()) + throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); + } + fileTaggers += FileTagger::create(patterns, fileTags); +} + +void ProjectResolver::resolveTransformer(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, "condition")) { + m_logger.qbsTrace() << "[PR] transformer condition is false"; + return; + } + + ResolvedTransformerPtr rtrafo = ResolvedTransformer::create(); + rtrafo->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + rtrafo->inputs = m_evaluator->stringListValue(item, "inputs"); + for (int i = 0; i < rtrafo->inputs.count(); ++i) + rtrafo->inputs[i] = FileInfo::resolvePath(m_productContext->product->sourceDirectory, rtrafo->inputs.at(i)); + rtrafo->transform = scriptFunctionValue(item, QLatin1String("prepare")); + rtrafo->explicitlyDependsOn = m_evaluator->fileTagsValue(item, "explicitlyDependsOn"); + + foreach (const Item *child, item->children()) { + if (Q_UNLIKELY(child->typeName() != QLatin1String("Artifact"))) + throw ErrorInfo(Tr::tr("Transformer: wrong child type '%0'.").arg(child->typeName())); + SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->properties = m_productContext->product->properties; + QString fileName = m_evaluator->stringValue(child, "fileName"); + if (Q_UNLIKELY(fileName.isEmpty())) + throw ErrorInfo(Tr::tr("Artifact fileName must not be empty.")); + artifact->absoluteFilePath = FileInfo::resolvePath(m_productContext->product->topLevelProject()->buildDirectory, + fileName); + artifact->fileTags = m_evaluator->fileTagsValue(child, "fileTags"); + if (artifact->fileTags.isEmpty()) + artifact->fileTags.insert(unknownFileTag()); + rtrafo->outputs += artifact; + } + + m_productContext->product->transformers += rtrafo; +} + +void ProjectResolver::resolveExport(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(projectContext); + checkCancelation(); + const QString &productName = m_productContext->product->name; + m_exports[productName] = evaluateModuleValues(item); +} + +static void insertExportedConfig(const QString &usedProductName, + const QVariantMap &exportedConfig, + const PropertyMapPtr &propertyMap) +{ + QVariantMap properties = propertyMap->value(); + QVariant &modulesEntry = properties[QLatin1String("modules")]; + QVariantMap modules = modulesEntry.toMap(); + modules.insert(usedProductName, exportedConfig); + modulesEntry = modules; + propertyMap->setValue(properties); +} + +static void addUsedProducts(ModuleLoaderResult::ProductInfo *productInfo, + const ModuleLoaderResult::ProductInfo &usedProductInfo, + bool *productsAdded) +{ + int oldCount = productInfo->usedProducts.count(); + QSet<QString> usedProductNames; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, + productInfo->usedProducts) + usedProductNames += usedProduct.name; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, + usedProductInfo.usedProductsFromExportItem) { + if (!usedProductNames.contains(usedProduct.name)) + productInfo->usedProducts += usedProduct; + } + *productsAdded = (oldCount != productInfo->usedProducts.count()); +} + +void ProjectResolver::resolveProductDependencies(ProjectContext *projectContext) +{ + // Collect product dependencies from Export items. + bool productDependenciesAdded; + QList<ResolvedProductPtr> allProducts = projectContext->project->allProducts(); + do { + productDependenciesAdded = false; + foreach (const ResolvedProductPtr &rproduct, allProducts) { + if (!rproduct->enabled) + continue; + Item *productItem = m_productItemMap.value(rproduct); + ModuleLoaderResult::ProductInfo &productInfo + = projectContext->loadResult->productInfos[productItem]; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, + productInfo.usedProducts) { + ResolvedProductPtr usedProduct + = m_productsByName.value(dependency.name); + if (Q_UNLIKELY(!usedProduct)) + throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(dependency.name), + productItem->location()); + Item *usedProductItem = m_productItemMap.value(usedProduct); + const ModuleLoaderResult::ProductInfo usedProductInfo + = projectContext->loadResult->productInfos.value(usedProductItem); + bool added; + addUsedProducts(&productInfo, usedProductInfo, &added); + if (added) + productDependenciesAdded = true; + } + } + } while (productDependenciesAdded); + + // Resolve all inter-product dependencies. + foreach (const ResolvedProductPtr &rproduct, allProducts) { + if (!rproduct->enabled) + continue; + Item *productItem = m_productItemMap.value(rproduct); + foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, + projectContext->loadResult->productInfos.value(productItem).usedProducts) { + const QString &usedProductName = dependency.name; + ResolvedProductPtr usedProduct = m_productsByName.value(usedProductName); + if (Q_UNLIKELY(!usedProduct)) + throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(usedProductName), + productItem->location()); + rproduct->dependencies.insert(usedProduct); + + // insert the configuration of the Export item into the product's configuration + const QVariantMap exportedConfig = m_exports.value(usedProductName); + if (exportedConfig.isEmpty()) + continue; + + insertExportedConfig(usedProductName, exportedConfig, rproduct->properties); + + // insert the configuration of the Export item into the artifact configurations + foreach (SourceArtifactPtr artifact, rproduct->allEnabledFiles()) { + if (artifact->properties != rproduct->properties) + insertExportedConfig(usedProductName, exportedConfig, + artifact->properties); + } + } + } +} + +void ProjectResolver::postProcess(const ResolvedProductPtr &product, + ProjectContext *projectContext) const +{ + product->fileTaggers += projectContext->fileTaggers; + foreach (const RulePtr &rule, projectContext->rules) + product->rules += rule; + applyFileTaggers(product); +} + +void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const +{ + foreach (const SourceArtifactPtr &artifact, product->allEnabledFiles()) + applyFileTaggers(artifact, product, m_logger); +} + +void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product, const Logger &logger) +{ + if (!artifact->overrideFileTags || artifact->fileTags.isEmpty()) { + const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); + const FileTags fileTags = product->fileTagsForFileName(fileName); + artifact->fileTags.unite(fileTags); + if (artifact->fileTags.isEmpty()) + artifact->fileTags.insert(unknownFileTag()); + if (logger.traceEnabled()) + logger.qbsTrace() << "[PR] adding file tags " << artifact->fileTags + << " to " << fileName; + } +} + +QVariantMap ProjectResolver::evaluateModuleValues(Item *item) const +{ + QVariantMap modules; + evaluateModuleValues(item, &modules); + QVariantMap result; + result[QLatin1String("modules")] = modules; + return result; +} + +void ProjectResolver::evaluateModuleValues(Item *item, QVariantMap *modulesMap) const +{ + checkCancelation(); + for (Item::Modules::const_iterator it = item->modules().constBegin(); + it != item->modules().constEnd(); ++it) + { + QVariantMap depmods; + const Item::Module &module = *it; + evaluateModuleValues(module.item, &depmods); + QVariantMap dep = evaluateProperties(module.item); + dep.insert("modules", depmods); + modulesMap->insert(ModuleLoader::fullModuleName(module.name), dep); + } +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item) const +{ + const QVariantMap tmplt; + return evaluateProperties(item, item, tmplt); +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item, + Item *propertiesContainer, + const QVariantMap &tmplt) const +{ + QVariantMap result = tmplt; + for (QMap<QString, ValuePtr>::const_iterator it = propertiesContainer->properties().begin(); + it != propertiesContainer->properties().end(); ++it) + { + checkCancelation(); + switch (it.value()->type()) { + case Value::ItemValueType: + { + // Ignore items. Those point to module instances + // and are handled in evaluateModuleValues(). + break; + } + case Value::JSSourceValueType: + { + if (result.contains(it.key())) + break; + PropertyDeclaration pd; + for (Item *obj = item; obj; obj = obj->prototype()) { + pd = obj->propertyDeclarations().value(it.key()); + if (pd.isValid()) + break; + } + if (pd.type == PropertyDeclaration::Verbatim + || pd.flags.testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + { + break; + } + const QScriptValue scriptValue = m_evaluator->property(item, it.key()); + if (Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(scriptValue))) + throw ErrorInfo(scriptValue.toString(), it.value()->location()); + + // NOTE: Loses type information if scriptValue.isUndefined == true, + // as such QScriptValues become invalid QVariants. + QVariant v = scriptValue.toVariant(); + + if (pd.type == PropertyDeclaration::Path) + v = convertPathProperty(v.toString(), + m_productContext->product->sourceDirectory); + else if (pd.type == PropertyDeclaration::PathList) + v = convertPathListProperty(v.toStringList(), + m_productContext->product->sourceDirectory); + else if (pd.type == PropertyDeclaration::StringList) + v = v.toStringList(); + result[it.key()] = v; + break; + } + case Value::VariantValueType: + { + if (result.contains(it.key())) + break; + VariantValuePtr vvp = it.value().staticCast<VariantValue>(); + result[it.key()] = vvp->value(); + break; + } + case Value::BuiltinValueType: + // ignore + break; + } + } + return propertiesContainer->prototype() + ? evaluateProperties(item, propertiesContainer->prototype(), result) + : result; +} + +QVariantMap ProjectResolver::createProductConfig() const +{ + QVariantMap cfg = evaluateModuleValues(m_productContext->item); + cfg = evaluateProperties(m_productContext->item, m_productContext->item, cfg); + return cfg; +} + +QString ProjectResolver::convertPathProperty(const QString &path, const QString &dirPath) const +{ + return path.isEmpty() ? path : QDir::cleanPath(FileInfo::resolvePath(dirPath, path)); +} + +QStringList ProjectResolver::convertPathListProperty(const QStringList &paths, + const QString &dirPath) const +{ + QStringList result; + foreach (const QString &path, paths) + result += convertPathProperty(path, dirPath); + return result; +} + +void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, + ProjectContext *projectContext) +{ + const QByteArray typeName = item->typeName().toLocal8Bit(); + ItemFuncPtr f = mappings.value(typeName); + QBS_CHECK(f); + if (typeName == "Project") { + ProjectContext subProjectContext = createProjectContext(projectContext); + (this->*f)(item, &subProjectContext); + } else { + (this->*f)(item, projectContext); + } +} + +ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const +{ + ProjectContext subProjectContext; + subProjectContext.project = ResolvedProject::create(); + parentProjectContext->project->subProjects += subProjectContext.project; + subProjectContext.project->parentProject = parentProjectContext->project; + subProjectContext.loadResult = parentProjectContext->loadResult; + return subProjectContext; +} + +} // namespace Internal +} // namespace qbs |