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 | |
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')
130 files changed, 12656 insertions, 0 deletions
diff --git a/src/lib/corelib/language/artifactproperties.cpp b/src/lib/corelib/language/artifactproperties.cpp new file mode 100644 index 000000000..470163004 --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 "artifactproperties.h" +#include <language/propertymapinternal.h> +#include <tools/persistence.h> + +namespace qbs { +namespace Internal { + +ArtifactPropertiesPtr ArtifactProperties::create() +{ + return ArtifactPropertiesPtr(new ArtifactProperties); +} + +ArtifactProperties::ArtifactProperties() +{ +} + +void ArtifactProperties::load(PersistentPool &pool) +{ + pool.stream() >> m_fileTagsFilter; + m_propertyMap = pool.idLoadS<PropertyMapInternal>(); +} + +void ArtifactProperties::store(PersistentPool &pool) const +{ + pool.stream() << m_fileTagsFilter; + pool.store(m_propertyMap); +} + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2) +{ + return ap1.fileTagsFilter() == ap2.fileTagsFilter() + && ap1.propertyMap()->value() == ap2.propertyMap()->value(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/artifactproperties.h b/src/lib/corelib/language/artifactproperties.h new file mode 100644 index 000000000..8c688da20 --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ARTIFACTPROPERTIES_H +#define QBS_ARTIFACTPROPERTIES_H + +#include "language.h" +#include "forward_decls.h" +#include <tools/persistentobject.h> + +namespace qbs { +namespace Internal { + +class ArtifactProperties : public PersistentObject +{ +public: + static ArtifactPropertiesPtr create(); + + void setFileTagsFilter(const FileTags &filter) { m_fileTagsFilter = filter; } + FileTags fileTagsFilter() const { return m_fileTagsFilter; } + + PropertyMapPtr propertyMap() const { return m_propertyMap; } + void setPropertyMapInternal(const PropertyMapPtr &pmap) { m_propertyMap = pmap; } + +private: + ArtifactProperties(); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + FileTags m_fileTagsFilter; + PropertyMapPtr m_propertyMap; +}; + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2); +inline bool operator!=(const ArtifactProperties &ap1, const ArtifactProperties &ap2) { + return !(ap1 == ap2); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTPROPERTIES_H diff --git a/src/lib/corelib/language/asttools.cpp b/src/lib/corelib/language/asttools.cpp new file mode 100644 index 000000000..c394fc187 --- /dev/null +++ b/src/lib/corelib/language/asttools.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 "asttools.h" +#include <parser/qmljsast_p.h> + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid) +{ + QStringList result; + for (; qid; qid = qid->next) + result.append(qid->name.toString()); + return result; +} + +CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location) +{ + return CodeLocation(filePath, location.startLine, location.startColumn); +} + +QString textOf(const QString &source, QbsQmlJS::AST::Node *node) +{ + if (!node) + return QString(); + return source.mid(node->firstSourceLocation().begin(), + node->lastSourceLocation().end() - node->firstSourceLocation().begin()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/asttools.h b/src/lib/corelib/language/asttools.h new file mode 100644 index 000000000..cb0f0b852 --- /dev/null +++ b/src/lib/corelib/language/asttools.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ASTTOOLS_H +#define QBS_ASTTOOLS_H + +#include <parser/qmljsastfwd_p.h> +#include <tools/codelocation.h> +#include <QStringList> + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid); +CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location); +QString textOf(const QString &source, QbsQmlJS::AST::Node *node); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ASTTOOLS_H diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp new file mode 100644 index 000000000..4bbc02579 --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** 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 "builtindeclarations.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +const char QBS_LANGUAGE_VERSION[] = "1.0"; + +BuiltinDeclarations::BuiltinDeclarations() + : m_languageVersion(QLatin1String(QBS_LANGUAGE_VERSION)) +{ + addArtifactItem(); + addDependsItem(); + addExportItem(); + addFileTaggerItem(); + addGroupItem(); + addModuleItem(); + addProbeItem(); + addProductItem(); + addProjectItem(); + addPropertiesItem(); + addPropertyOptionsItem(); + addRuleItem(); + addSubprojectItem(); + addTransformerItem(); +} + +QString BuiltinDeclarations::languageVersion() const +{ + return m_languageVersion; +} + +QByteArray BuiltinDeclarations::qmlTypeInfo() const +{ + // Header: + QByteArray result; + result.append("import QtQuick.tooling 1.0\n\n"); + result.append("// This file describes the plugin-supplied types contained in the library.\n"); + result.append("// It is used for QML tooling purposes only.\n\n"); + result.append("Module {\n"); + + // Individual Components: + foreach (const QString &component, m_builtins.keys()) { + QByteArray componentName = component.toUtf8(); + result.append(" Component {\n"); + result.append(QByteArray(" name: \"") + componentName + QByteArray("\"\n")); + result.append(" exports: [ \"qbs/"); + result.append(componentName); + result.append(" "); + result.append(QBS_LANGUAGE_VERSION); + result.append("\" ]\n"); + result.append(" prototype: \"QQuickItem\"\n"); + + ItemDeclaration itemDecl = m_builtins.value(component); + foreach (const PropertyDeclaration &property, itemDecl.properties()) { + result.append(" Property { name=\""); + result.append(property.name.toUtf8()); + result.append("\"; "); + switch (property.type) { + case qbs::Internal::PropertyDeclaration::UnknownType: + result.append("type=\"unknown\""); + break; + case qbs::Internal::PropertyDeclaration::Boolean: + result.append("type=\"bool\""); + break; + case qbs::Internal::PropertyDeclaration::Integer: + result.append("type=\"int\""); + break; + case qbs::Internal::PropertyDeclaration::Path: + result.append("type=\"string\""); + break; + case qbs::Internal::PropertyDeclaration::PathList: + result.append("type=\"string\"; isList=true"); + break; + case qbs::Internal::PropertyDeclaration::String: + result.append("type=\"string\""); + break; + case qbs::Internal::PropertyDeclaration::StringList: + result.append("type=\"string\"; isList=true"); + break; + case qbs::Internal::PropertyDeclaration::Variant: + result.append("type=\"QVariant\""); + break; + case qbs::Internal::PropertyDeclaration::Verbatim: + result.append("type=\"string\""); + break; + } + result.append(" }\n"); // Property + } + + result.append(" }\n"); // Component + } + + // Footer: + result.append("}\n"); // Module + return result; +} + +bool BuiltinDeclarations::containsType(const QString &typeName) const +{ + return m_builtins.contains(typeName); +} + +ItemDeclaration BuiltinDeclarations::declarationsForType(const QString &typeName) const +{ + return m_builtins.value(typeName); +} + +void BuiltinDeclarations::setupItemForBuiltinType(Item *item) const +{ + foreach (const PropertyDeclaration &pd, declarationsForType(item->typeName()).properties()) { + item->m_propertyDeclarations.insert(pd.name, pd); + ValuePtr &value = item->m_properties[pd.name]; + if (!value) { + JSSourceValuePtr sourceValue = JSSourceValue::create(); + sourceValue->setFile(item->file()); + sourceValue->setSourceCode(pd.initialValueSource.isEmpty() ? + "undefined" : pd.initialValueSource); + value = sourceValue; + } + } +} + +void BuiltinDeclarations::insert(const ItemDeclaration &decl) +{ + m_builtins.insert(decl.typeName(), decl); +} + +static PropertyDeclaration conditionProperty() +{ + PropertyDeclaration decl(QLatin1String("condition"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("true"); + return decl; +} + +static PropertyDeclaration nameProperty() +{ + return PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String); +} + +static PropertyDeclaration prepareScriptProperty() +{ + PropertyDeclaration decl(QLatin1String("prepare"), PropertyDeclaration::Verbatim); + decl.functionArgumentNames + << QLatin1String("project") << QLatin1String("product") + << QLatin1String("inputs") << QLatin1String("outputs") + << QLatin1String("input") << QLatin1String("output"); + return decl; +} + +void BuiltinDeclarations::addArtifactItem() +{ + ItemDeclaration item(QLatin1String("Artifact")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("fileName"), PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant); + PropertyDeclaration decl(QLatin1String("alwaysUpdated"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("true"); + item << decl; + insert(item); +} + +void BuiltinDeclarations::addDependsItem() +{ + ItemDeclaration item(QLatin1String("Depends")); + item << conditionProperty(); + item << nameProperty(); + item << PropertyDeclaration(QLatin1String("submodules"), PropertyDeclaration::Variant); + PropertyDeclaration requiredDecl(QLatin1String("required"), PropertyDeclaration::Boolean); + requiredDecl.initialValueSource = QLatin1String("true"); + item << requiredDecl; + insert(item); +} + +void BuiltinDeclarations::addExportItem() +{ + ItemDeclaration item(QLatin1String("Export")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Depends") + << QLatin1String("Module") // needed, because we're adding module instances internally + ); + insert(item); +} + +void BuiltinDeclarations::addFileTaggerItem() +{ + ItemDeclaration item(QLatin1String("FileTagger")); + + // TODO: Remove in 1.3 + item << PropertyDeclaration(QLatin1String("pattern"), PropertyDeclaration::StringList); + + item << PropertyDeclaration(QLatin1String("patterns"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant); + insert(item); +} + +void BuiltinDeclarations::addGroupItem() +{ + ItemDeclaration item(QLatin1String("Group")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("files"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("fileTagsFilter"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("excludeFiles"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("prefix"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + PropertyDeclaration declaration; + declaration.name = QLatin1String("overrideTags"); + declaration.type = PropertyDeclaration::Boolean; + declaration.flags = PropertyDeclaration::PropertyNotAvailableInConfig; + declaration.initialValueSource = QLatin1String("true"); + item << declaration; + insert(item); +} + +void BuiltinDeclarations::addModuleItem() +{ + ItemDeclaration item(QLatin1String("Module")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") // needed, because we're adding module instances internally + ); + item << nameProperty(); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("setupBuildEnvironment"), + PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("setupRunEnvironment"), + PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("validate"), + PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("additionalProductFileTags"), + PropertyDeclaration::Variant); + PropertyDeclaration presentDecl(QLatin1String("present"), PropertyDeclaration::Boolean); + presentDecl.initialValueSource = QLatin1String("true"); + item << presentDecl; + insert(item); +} + +void BuiltinDeclarations::addProbeItem() +{ + ItemDeclaration item(QLatin1String("Probe")); + item << conditionProperty(); + PropertyDeclaration foundProperty(QLatin1String("found"), PropertyDeclaration::Boolean); + foundProperty.initialValueSource = QLatin1String("false"); + item << foundProperty; + item << PropertyDeclaration(QLatin1String("configure"), PropertyDeclaration::Verbatim); + insert(item); +} + +void BuiltinDeclarations::addProductItem() +{ + ItemDeclaration item(QLatin1String("Product")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") + << QLatin1String("Depends") + << QLatin1String("Transformer") + << QLatin1String("Group") + << QLatin1String("FileTagger") + << QLatin1String("Export") + << QLatin1String("Probe") + << QLatin1String("Rule")); + item << conditionProperty(); + PropertyDeclaration decl(QLatin1String("type"), PropertyDeclaration::StringList); + decl.initialValueSource = QLatin1String("[]"); + item << decl; + item << nameProperty(); + decl = PropertyDeclaration("targetName", PropertyDeclaration::String); + decl.initialValueSource = QLatin1String("name"); + item << decl; + decl = PropertyDeclaration(QLatin1String("destinationDirectory"), PropertyDeclaration::String); + decl.initialValueSource = QLatin1String("'.'"); + item << decl; + item << PropertyDeclaration(QLatin1String("consoleApplication"), + PropertyDeclaration::Boolean); + item << PropertyDeclaration(QLatin1String("files"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("excludeFiles"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("qbsSearchPaths"), + PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("version"), PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addProjectItem() +{ + ItemDeclaration item(QLatin1String("Project")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") + << QLatin1String("Project") + << QLatin1String("SubProject") + << QLatin1String("Product") + << QLatin1String("FileTagger") + << QLatin1String("Rule")); + item << nameProperty(); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("references"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("qbsSearchPaths"), + PropertyDeclaration::StringList, PropertyDeclaration::PropertyNotAvailableInConfig); + insert(item); +} + +void BuiltinDeclarations::addPropertiesItem() +{ + insert(ItemDeclaration(QLatin1String("Properties"))); +} + +void BuiltinDeclarations::addPropertyOptionsItem() +{ + ItemDeclaration item(QLatin1String("PropertyOptions")); + item << nameProperty(); + item << PropertyDeclaration(QLatin1String("allowedValues"), PropertyDeclaration::Variant); + item << PropertyDeclaration(QLatin1String("description"), PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addRuleItem() +{ + ItemDeclaration item(QLatin1String("Rule")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Artifact")); + item << conditionProperty(); + PropertyDeclaration decl(QLatin1String("multiplex"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("false"); + item << decl; + item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("usings"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("auxiliaryInputs"), + PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("explicitlyDependsOn"), + PropertyDeclaration::StringList); + item << prepareScriptProperty(); + insert(item); +} + +void BuiltinDeclarations::addSubprojectItem() +{ + ItemDeclaration item(QLatin1String("SubProject")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Project") // needed, because we're adding this internally + << QLatin1String("Properties")); + item << PropertyDeclaration(QLatin1String("filePath"), PropertyDeclaration::Path); + PropertyDeclaration inheritProperty; + inheritProperty.name = QLatin1String("inheritProperties"); + inheritProperty.type = PropertyDeclaration::Boolean; + inheritProperty.initialValueSource = QLatin1String("true"); + item << inheritProperty; + insert(item); +} + +void BuiltinDeclarations::addTransformerItem() +{ + ItemDeclaration item(QLatin1String("Transformer")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Artifact")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::Variant); + item << prepareScriptProperty(); + item << PropertyDeclaration(QLatin1String("explicitlyDependsOn"), + PropertyDeclaration::StringList); + insert(item); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h new file mode 100644 index 000000000..b793c5aae --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_BUILTINDECLARATIONS_H +#define QBS_BUILTINDECLARATIONS_H + +#include "itemdeclaration.h" + +#include <QByteArray> +#include <QMap> + +namespace qbs { +namespace Internal { + +class Item; + +class BuiltinDeclarations +{ +public: + BuiltinDeclarations(); + + QString languageVersion() const; + QByteArray qmlTypeInfo() const; + bool containsType(const QString &typeName) const; + ItemDeclaration declarationsForType(const QString &typeName) const; + void setupItemForBuiltinType(qbs::Internal::Item *item) const; + +private: + void insert(const ItemDeclaration &decl); + void addArtifactItem(); + void addDependsItem(); + void addExportItem(); + void addFileTaggerItem(); + void addGroupItem(); + void addModuleItem(); + void addProbeItem(); + void addProductItem(); + void addProjectItem(); + void addPropertiesItem(); + void addPropertyOptionsItem(); + void addRuleItem(); + void addSubprojectItem(); + void addTransformerItem(); + + QString m_languageVersion; + QMap<QString, ItemDeclaration> m_builtins; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILTINDECLARATIONS_H diff --git a/src/lib/corelib/language/builtinvalue.cpp b/src/lib/corelib/language/builtinvalue.cpp new file mode 100644 index 000000000..d7f98083d --- /dev/null +++ b/src/lib/corelib/language/builtinvalue.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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 "builtinvalue.h" + +namespace qbs { +namespace Internal { + +BuiltinValue::BuiltinValue(Builtin builtin) + : Value(Value::BuiltinValueType) + , m_builtin(builtin) +{ +} + +BuiltinValuePtr BuiltinValue::create(Builtin builtin) +{ + return BuiltinValuePtr(new BuiltinValue(builtin)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/builtinvalue.h b/src/lib/corelib/language/builtinvalue.h new file mode 100644 index 000000000..77c6fd14a --- /dev/null +++ b/src/lib/corelib/language/builtinvalue.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_BUILTINVALUE_H +#define QBS_BUILTINVALUE_H + +#include "value.h" + +namespace qbs { +namespace Internal { + +class BuiltinValue : public Value +{ +public: + enum Builtin + { + GetNativeSettingFunction, + GetEnvFunction, + GetHostOSFunction, + CanonicalArchitectureFunction + }; + + static BuiltinValuePtr create(Builtin builtin); + + void apply(ValueHandler *handler) { handler->handle(this); } + + Builtin builtin() const { return m_builtin; } + void setBuiltin(const Builtin &builtin) { m_builtin = builtin; } + +private: + BuiltinValue(Builtin builtin); + + Builtin m_builtin; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILTINVALUE_H diff --git a/src/lib/corelib/language/evaluationdata.h b/src/lib/corelib/language/evaluationdata.h new file mode 100644 index 000000000..930ac1f0c --- /dev/null +++ b/src/lib/corelib/language/evaluationdata.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_EVALUATIONDATA_H +#define QBS_EVALUATIONDATA_H + +#include <QHash> +#include <QScriptEngine> +#include <QScriptValue> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class Evaluator; +class Item; + +class EvaluationData +{ +public: + Evaluator *evaluator; + const Item *item; + mutable QHash<QScriptString, QScriptValue> valueCache; + + void attachTo(QScriptValue &); + static EvaluationData *get(const QScriptValue &); +}; + +inline void EvaluationData::attachTo(QScriptValue &scriptValue) +{ + QVariant v; + v.setValue<quintptr>(reinterpret_cast<quintptr>(this)); + scriptValue.setData(scriptValue.engine()->newVariant(v)); +} + +inline EvaluationData *EvaluationData::get(const QScriptValue &scriptValue) +{ + const quintptr ptr = scriptValue.data().toVariant().value<quintptr>(); + return reinterpret_cast<EvaluationData *>(ptr); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATIONDATA_H diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp new file mode 100644 index 000000000..29b3837fd --- /dev/null +++ b/src/lib/corelib/language/evaluator.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** 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 "evaluator.h" +#include "evaluationdata.h" +#include "evaluatorscriptclass.h" +#include "filecontext.h" +#include "filetags.h" +#include "item.h" +#include <jsextensions/jsextensions.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> +#include <QDebug> +#include <QScriptEngine> + +namespace qbs { +namespace Internal { + + +Evaluator::Evaluator(ScriptEngine *scriptEngine, const Logger &logger) + : m_scriptEngine(scriptEngine) + , m_scriptClass(new EvaluatorScriptClass(scriptEngine, logger)) +{ +} + +Evaluator::~Evaluator() +{ + QHash<const Item *, QScriptValue>::iterator it = m_scriptValueMap.begin(); + for (; it != m_scriptValueMap.end(); ++it) { + EvaluationData *data = EvaluationData::get(*it); + if (data) { + if (data->item) + data->item->setPropertyObserver(0); + delete data; + } + } + delete m_scriptClass; +} + +QScriptValue Evaluator::property(const Item *item, const QString &name) +{ + return scriptValue(item).property(name); +} + +QScriptValue Evaluator::property(const Item *item, const QStringList &nameParts) +{ + const Item *targetItem = item; + const int c = nameParts.count() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->properties().value(nameParts.at(i)); + if (!v) + return QScriptValue(); + QBS_ASSERT(v->type() == Value::ItemValueType, return QScriptValue()); + targetItem = v.staticCast<ItemValue>()->item(); + QBS_ASSERT(targetItem, return QScriptValue()); + } + return property(targetItem, nameParts.last()); +} + +bool Evaluator::boolValue(const Item *item, const QString &name, bool defaultValue, + bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + handleEvaluationError(item, name, v); + if (!v.isValid() || v.isUndefined()) { + if (propertyWasSet) + *propertyWasSet = false; + return defaultValue; + } + if (propertyWasSet) + *propertyWasSet = true; + return v.toBool(); +} + +FileTags Evaluator::fileTagsValue(const Item *item, const QString &name) +{ + return FileTags::fromStringList(stringListValue(item, name)); +} + +QString Evaluator::stringValue(const Item *item, const QString &name, + const QString &defaultValue, bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + handleEvaluationError(item, name, v); + if (!v.isValid() || v.isUndefined()) { + if (propertyWasSet) + *propertyWasSet = false; + return defaultValue; + } + if (propertyWasSet) + *propertyWasSet = true; + return v.toString(); +} + +static QStringList toStringList(const QScriptValue &scriptValue, + const Item *item, const QString &propertyName) +{ + if (scriptValue.isString()) { + return QStringList(scriptValue.toString()); + } else if (scriptValue.isArray()) { + QStringList lst; + int i = 0; + forever { + QScriptValue elem = scriptValue.property(i++); + if (!elem.isValid()) + break; + if (elem.isArray() || elem.isObject()) { + // Let's assume all other JS types are convertible to string. + throw ErrorInfo(Tr::tr("Expected array element of type String at index %1.") + .arg(i - 1), + item->property(propertyName)->location()); + } + lst.append(elem.toString()); + } + return lst; + } + return QStringList(); +} + +QStringList Evaluator::stringListValue(const Item *item, const QString &name, bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + if (propertyWasSet) + *propertyWasSet = v.isValid() && !v.isUndefined(); + handleEvaluationError(item, name, v); + return toStringList(v, item, name); +} + +QScriptValue Evaluator::scriptValue(const Item *item) +{ + QScriptValue &scriptValue = m_scriptValueMap[item]; + if (scriptValue.isObject()) { + // already initialized + return scriptValue; + } + + EvaluationData *edata = new EvaluationData; + edata->evaluator = this; + edata->item = item; + edata->item->setPropertyObserver(this); + + scriptValue = m_scriptEngine->newObject(m_scriptClass); + edata->attachTo(scriptValue); + return scriptValue; +} + +void Evaluator::onItemPropertyChanged(Item *item) +{ + EvaluationData *data = EvaluationData::get(m_scriptValueMap.value(item)); + if (data) + data->valueCache.clear(); +} + +void Evaluator::onItemDestroyed(Item *item) +{ + delete EvaluationData::get(m_scriptValueMap.value(item)); + m_scriptValueMap.remove(item); +} + +void Evaluator::handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue) +{ + if (Q_LIKELY(!m_scriptEngine->hasErrorOrException(scriptValue))) + return; + const ValueConstPtr value = item->property(name); + CodeLocation location = value ? value->location() : CodeLocation(); + if (m_scriptEngine->hasUncaughtException()) { + throw ErrorInfo(m_scriptEngine->uncaughtException().toString(), + CodeLocation(location.fileName(), m_scriptEngine->uncaughtExceptionLineNumber())); + } + throw ErrorInfo(scriptValue.toString(), location); +} + +QScriptValue Evaluator::fileScope(const FileContextConstPtr &file) +{ + QScriptValue &result = m_fileScopeMap[file]; + if (result.isObject()) { + // already initialized + return result; + } + + if (file->idScope()) + result = scriptValue(file->idScope()); + else + result = m_scriptEngine->newObject(); + result.setProperty(QLatin1String("filePath"), file->filePath()); + result.setProperty(QLatin1String("path"), file->dirPath()); + m_scriptEngine->import(file->jsImports(), result, result); + JsExtensions::setupExtensions(file->jsExtensions(), result); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h new file mode 100644 index 000000000..a17605af1 --- /dev/null +++ b/src/lib/corelib/language/evaluator.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_EVALUATOR_H +#define QBS_EVALUATOR_H + +#include "forward_decls.h" +#include "itemobserver.h" +#include <language/scriptengine.h> + +#include <QHash> +#include <QScriptValue> + +namespace qbs { +namespace Internal { +class FileTags; + +class EvaluatorScriptClass; + +class Evaluator : private ItemObserver +{ + friend class SVConverter; + +public: + Evaluator(ScriptEngine *scriptEngine, const Logger &logger); + virtual ~Evaluator(); + + ScriptEngine *engine() const; + QScriptValue property(const Item *item, const QString &name); + QScriptValue property(const Item *item, const QStringList &nameParts); + + bool boolValue(const Item *item, const QString &name, bool defaultValue = false, + bool *propertyWasSet = 0); + FileTags fileTagsValue(const Item *item, const QString &name); + QString stringValue(const Item *item, const QString &name, + const QString &defaultValue = QString(), bool *propertyWasSet = 0); + QStringList stringListValue(const Item *item, const QString &name, bool *propertyWasSet = 0); + + QScriptValue scriptValue(const Item *item); + QScriptValue fileScope(const FileContextConstPtr &file); + +private: + void onItemPropertyChanged(Item *item); + void onItemDestroyed(Item *item); + void handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue); + + ScriptEngine *m_scriptEngine; + EvaluatorScriptClass *m_scriptClass; + mutable QHash<const Item *, QScriptValue> m_scriptValueMap; + mutable QHash<FileContextConstPtr, QScriptValue> m_fileScopeMap; +}; + +inline ScriptEngine *Evaluator::engine() const +{ + return m_scriptEngine; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATOR_H diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp new file mode 100644 index 000000000..fb89f53e6 --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.cpp @@ -0,0 +1,577 @@ +/**************************************************************************** +** +** 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 "evaluatorscriptclass.h" + +#include "builtinvalue.h" +#include "evaluationdata.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "scriptengine.h" +#include "propertydeclaration.h" +#include <tools/hostosinfo.h> +#include <tools/qbsassert.h> +#include <tools/scripttools.h> + +#include <QScriptString> +#include <QScriptValue> +#include <QDebug> +#include <QSettings> + +namespace qbs { +namespace Internal { + +class SVConverter : ValueHandler +{ + EvaluatorScriptClass *const scriptClass; + ScriptEngine *const engine; + QScriptContext *const scriptContext; + const QScriptValue *object; + const ValuePtr &valuePtr; + const bool inPrototype; + char pushedScopesCount; + +public: + const QScriptString *propertyName; + const EvaluationData *data; + QScriptValue *result; + + SVConverter(EvaluatorScriptClass *esc, const QScriptValue *obj, const ValuePtr &v, + bool _inPrototype) + : scriptClass(esc) + , engine(static_cast<ScriptEngine *>(esc->engine())) + , scriptContext(esc->engine()->currentContext()) + , object(obj) + , valuePtr(v) + , inPrototype(_inPrototype) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + void setupConvenienceProperty(const QString &conveniencePropertyName, QScriptValue *extraScope, + const QScriptValue &scriptValue) + { + if (!extraScope->isObject()) + *extraScope = scriptClass->engine()->newObject(); + if (!scriptValue.isValid() || scriptValue.isUndefined()) { + // If there's no such value, use an empty array to have the convenience property + // still available. + extraScope->setProperty(conveniencePropertyName, engine->newArray()); + } else { + extraScope->setProperty(conveniencePropertyName, scriptValue); + } + } + + void pushScope(const QScriptValue &scope) + { + if (scope.isObject()) { + scriptContext->pushScope(scope); + ++pushedScopesCount; + } + } + + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scriptContext->popScope(); + } + + void handle(JSSourceValue *value) + { + const Item *conditionScopeItem = 0; + QScriptValue conditionScope; + QScriptValue conditionFileScope; + Item *outerItem = data->item->outerItem(); + for (int i = 0; i < value->alternatives().count(); ++i) { + const JSSourceValue::Alternative *alternative = 0; + alternative = &value->alternatives().at(i); + if (conditionScopeItem != alternative->conditionScopeItem) { + conditionScopeItem = alternative->conditionScopeItem; + conditionScope = data->evaluator->scriptValue(alternative->conditionScopeItem); + QBS_ASSERT(conditionScope.isObject(), return); + conditionFileScope = data->evaluator->fileScope(conditionScopeItem->file()); + } + engine->currentContext()->pushScope(conditionFileScope); + pushItemScopes(conditionScopeItem); + engine->currentContext()->pushScope(conditionScope); + const QScriptValue cr = engine->evaluate(alternative->condition); + engine->currentContext()->popScope(); + engine->currentContext()->popScope(); + popScopes(); + if (engine->hasErrorOrException(cr)) { + *result = cr; + return; + } + if (cr.toBool()) { + // condition is true, let's use the value of this alternative + if (alternative->value->sourceUsesOuter()) { + // Clone value but without alternatives. + JSSourceValuePtr outerValue = JSSourceValue::create(); + outerValue->setFile(value->file()); + outerValue->setSourceCode(value->sourceCode()); + outerValue->setBaseValue(value->baseValue()); + outerValue->setLocation(value->location()); + outerItem = Item::create(data->item->pool()); + outerItem->setProperty(propertyName->toString(), outerValue); + } + value = alternative->value.data(); + break; + } + } + + QScriptValue extraScope; + if (value->sourceUsesBase()) { + QScriptValue baseValue; + if (value->baseValue()) { + SVConverter converter(scriptClass, object, value->baseValue(), inPrototype); + converter.propertyName = propertyName; + converter.data = data; + converter.result = &baseValue; + converter.start(); + } + setupConvenienceProperty(QLatin1String("base"), &extraScope, baseValue); + } + if (value->sourceUsesOuter() && outerItem) + setupConvenienceProperty(QLatin1String("outer"), &extraScope, + data->evaluator->property(outerItem, *propertyName)); + + pushScope(data->evaluator->fileScope(value->file())); + pushItemScopes(data->item); + if (inPrototype || !data->item->isModuleInstance()) { + // Own properties of module instances must not have the instance itself in the scope. + pushScope(*object); + } + pushScope(extraScope); + const CodeLocation valueLocation = value->location(); + *result = engine->evaluate(value->sourceCode(), valueLocation.fileName(), + valueLocation.line()); + popScopes(); + } + + void handle(ItemValue *value) + { + Item *item = value->item(); + if (!item) + qDebug() << "SVConverter got null item" << propertyName->toString(); + *result = data->evaluator->scriptValue(item); + if (!result->isValid()) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) + { + *result = scriptClass->engine()->toScriptValue(variantValue->value()); + } + + void handle(BuiltinValue *builtinValue) + { + *result = scriptClass->scriptValueForBuiltin(builtinValue->builtin()); + } +}; + +bool debugProperties = false; + +enum QueryPropertyType +{ + QPTDefault, + QPTParentProperty +}; + +EvaluatorScriptClass::EvaluatorScriptClass(ScriptEngine *scriptEngine, const Logger &logger) + : QScriptClass(scriptEngine) + , m_logger(logger) +{ + m_getNativeSettingBuiltin = scriptEngine->newFunction(js_getNativeSetting, 3); + m_getEnvBuiltin = scriptEngine->newFunction(js_getEnv, 1); + m_getHostOSBuiltin = scriptEngine->newFunction(js_getHostOS, 1); + m_canonicalArchitectureBuiltin = scriptEngine->newFunction(js_canonicalArchitecture, 1); +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryProperty(const QScriptValue &object, + const QScriptString &name, + QScriptClass::QueryFlags flags, + uint *id) +{ + Q_UNUSED(flags); + + // We assume that it's safe to save the result of the query in a member of the scriptclass. + // It must be cleared in the property method before doing any further lookup. + QBS_ASSERT(m_queryResult.isNull(), return QueryFlags()); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty " << object.objectId() << " " << name; + + EvaluationData *const data = EvaluationData::get(object); + const QString nameString = name.toString(); + if (nameString == QLatin1String("parent")) { + *id = QPTParentProperty; + m_queryResult.data = data; + return QScriptClass::HandlesReadAccess; + } + + *id = QPTDefault; + if (!data) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: no data attached"; + return QScriptClass::QueryFlags(); + } + + return queryItemProperty(data, nameString); +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryItemProperty(const EvaluationData *data, + const QString &name, + bool ignoreParent) +{ + for (const Item *item = data->item; item; item = item->prototype()) { + m_queryResult.value = item->properties().value(name); + if (!m_queryResult.value.isNull()) { + m_queryResult.data = data; + if (data->item != item) + m_queryResult.inPrototype = true; + return HandlesReadAccess; + } + } + + if (!ignoreParent && data->item->parent()) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: query parent"; + EvaluationData parentdata = *data; + parentdata.item = data->item->parent(); + const QueryFlags qf = queryItemProperty(&parentdata, name, true); + if (qf.testFlag(HandlesReadAccess)) { + m_queryResult.data = data; + return qf; + } + } + + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: no such property"; + return QScriptClass::QueryFlags(); +} + +QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue) +{ + return (scriptValue.isObject() + ? QLatin1String("[Object: ") + + QString::number(scriptValue.objectId(), 16) + QLatin1Char(']') + : scriptValue.toVariant().toString()); +} + +Item *EvaluatorScriptClass::findParentOfType(const Item *item, const QString &typeName) +{ + for (Item *it = item->parent(); it; it = it->parent()) { + if (it->typeName() == typeName) + return it; + } + return 0; +} + +inline void convertToPropertyType(const PropertyDeclaration::Type t, QScriptValue &v) +{ + if (v.isUndefined()) + return; + switch (t) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + case PropertyDeclaration::Verbatim: + break; + case PropertyDeclaration::Boolean: + if (!v.isBool()) + v = v.toBool(); + break; + case PropertyDeclaration::Integer: + if (!v.isNumber()) + v = v.toNumber(); + break; + case PropertyDeclaration::Path: + case PropertyDeclaration::String: + if (!v.isString()) + v = v.toString(); + break; + case PropertyDeclaration::PathList: + case PropertyDeclaration::StringList: + if (!v.isArray()) { + QScriptValue x = v.engine()->newArray(1); + x.setProperty(0, v.isString() ? v : v.toString()); + v = x; + } + break; + } +} + +QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QScriptString &name, + uint id) +{ + const EvaluationData *data = m_queryResult.data; + const bool inPrototype = m_queryResult.inPrototype; + m_queryResult.data = 0; + m_queryResult.inPrototype = false; + QBS_ASSERT(data, return QScriptValue()); + + const QueryPropertyType qpt = static_cast<QueryPropertyType>(id); + if (qpt == QPTParentProperty) { + return data->item->parent() + ? data->evaluator->scriptValue(data->item->parent()) + : engine()->undefinedValue(); + } + + ValuePtr value; + m_queryResult.value.swap(value); + QBS_ASSERT(value, return QScriptValue()); + QBS_ASSERT(m_queryResult.isNull(), return QScriptValue()); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] property " << name; + + QScriptValue result = data->valueCache.value(name); + if (result.isValid()) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] cache hit " << name << ": " << resultToString(result); + return result; + } + + SVConverter converter(this, &object, value, inPrototype); + converter.propertyName = &name; + converter.data = data; + converter.result = &result; + converter.start(); + + const PropertyDeclaration decl = data->item->propertyDeclarations().value(name.toString()); + convertToPropertyType(decl.type, result); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] cache miss " << name << ": " << resultToString(result); + data->valueCache.insert(name, result); + return result; +} + +QScriptValue EvaluatorScriptClass::scriptValueForBuiltin(BuiltinValue::Builtin builtin) const +{ + switch (builtin) { + case BuiltinValue::GetNativeSettingFunction: + return m_getNativeSettingBuiltin; + case BuiltinValue::GetEnvFunction: + return m_getEnvBuiltin; + case BuiltinValue::GetHostOSFunction: + return m_getHostOSBuiltin; + case BuiltinValue::CanonicalArchitectureFunction: + return m_canonicalArchitectureBuiltin; + } + QBS_ASSERT(!"unhandled builtin", ;); + return QScriptValue(); +} + +QScriptValue EvaluatorScriptClass::js_getNativeSetting(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1 || context->argumentCount() > 3)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getNativeSetting expects between 1 and 3 arguments")); + } + + QString key = context->argumentCount() > 1 ? context->argument(1).toString() : QString(); + + // We'll let empty string represent the default registry value + if (HostOsInfo::isWindowsHost() && key.isEmpty()) + key = QLatin1String("."); + + QVariant defaultValue = context->argumentCount() > 2 ? context->argument(2).toVariant() : QVariant(); + + QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); + QVariant value = settings.value(key, defaultValue); + return value.isNull() ? engine->undefinedValue() : engine->toScriptValue(value); +} + +QScriptValue EvaluatorScriptClass::js_getEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getEnv expects 1 argument")); + } + const QString name = context->argument(0).toString(); + ScriptEngine * const e = static_cast<ScriptEngine *>(engine); + const QString value = e->environment().value(name); + e->addEnvironmentVariable(name, value); + return value.isNull() ? engine->undefinedValue() : value; +} + +QScriptValue EvaluatorScriptClass::js_getHostOS(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context); + QStringList hostSystem; + +#if defined(Q_OS_AIX) + hostSystem << "aix"; +#endif +#if defined(Q_OS_ANDROID) + hostSystem << "android"; +#endif +#if defined(Q_OS_BLACKBERRY) + hostSystem << "blackberry"; +#endif +#if defined(Q_OS_BSD4) + hostSystem << "bsd" << "bsd4"; +#endif +#if defined(Q_OS_BSDI) + hostSystem << "bsdi"; +#endif +#if defined(Q_OS_CYGWIN) + hostSystem << "cygwin"; +#endif +#if defined(Q_OS_DARWIN) + hostSystem << "darwin"; +#endif +#if defined(Q_OS_DGUX) + hostSystem << "dgux"; +#endif +#if defined(Q_OS_DYNIX) + hostSystem << "dynix"; +#endif +#if defined(Q_OS_FREEBSD) + hostSystem << "freebsd"; +#endif +#if defined(Q_OS_HPUX) + hostSystem << "hpux"; +#endif +#if defined(Q_OS_HURD) + hostSystem << "hurd"; +#endif +#if defined(Q_OS_INTEGRITY) + hostSystem << "integrity"; +#endif +#if defined(Q_OS_IOS) + hostSystem << "ios"; +#endif +#if defined(Q_OS_IRIX) + hostSystem << "irix"; +#endif +#if defined(Q_OS_LINUX) + hostSystem << "linux"; +#endif +#if defined(Q_OS_LYNX) + hostSystem << "lynx"; +#endif +#if defined(Q_OS_MACX) + hostSystem << "osx"; +#endif +#if defined(Q_OS_MSDOS) + hostSystem << "msdos"; +#endif +#if defined(Q_OS_NACL) + hostSystem << "nacl"; +#endif +#if defined(Q_OS_NETBSD) + hostSystem << "netbsd"; +#endif +#if defined(Q_OS_OPENBSD) + hostSystem << "openbsd"; +#endif +#if defined(Q_OS_OS2) + hostSystem << "os2"; +#endif +#if defined(Q_OS_OS2EMX) + hostSystem << "os2emx"; +#endif +#if defined(Q_OS_OSF) + hostSystem << "osf"; +#endif +#if defined(Q_OS_QNX) + hostSystem << "qnx"; +#endif +#if defined(Q_OS_ONX6) + hostSystem << "qnx6"; +#endif +#if defined(Q_OS_RELIANT) + hostSystem << "reliant"; +#endif +#if defined(Q_OS_SCO) + hostSystem << "sco"; +#endif +#if defined(Q_OS_SOLARIS) + hostSystem << "solaris"; +#endif +#if defined(Q_OS_SYMBIAN) + hostSystem << "symbian"; +#endif +#if defined(Q_OS_ULTRIX) + hostSystem << "ultrix"; +#endif +#if defined(Q_OS_UNIX) + hostSystem << "unix"; +#endif +#if defined(Q_OS_UNIXWARE) + hostSystem << "unixware"; +#endif +#if defined(Q_OS_VXWORKS) + hostSystem << "vxworks"; +#endif +#if defined(Q_OS_WIN32) + hostSystem << "windows"; +#endif +#if defined(Q_OS_WINCE) + hostSystem << "windowsce"; +#endif +#if defined(Q_OS_WINPHONE) + hostSystem << "windowsphone"; +#endif +#if defined(Q_OS_WINRT) + hostSystem << "winrt"; +#endif + + return engine->toScriptValue(hostSystem); +} + +QScriptValue EvaluatorScriptClass::js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("canonicalArchitecture expects 1 argument")); + } + const QString architecture = context->argument(0).toString(); + return engine->toScriptValue(HostOsInfo::canonicalArchitecture(architecture)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h new file mode 100644 index 000000000..4803a21b9 --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_EVALUATORSCRIPTCLASS_H +#define QBS_EVALUATORSCRIPTCLASS_H + +#include "value.h" +#include "builtinvalue.h" +#include <logging/logger.h> + +#include <QScriptClass> + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class EvaluationData; +class ScriptEngine; + +class EvaluatorScriptClass : public QScriptClass +{ +public: + EvaluatorScriptClass(ScriptEngine *scriptEngine, const Logger &logger); + + QueryFlags queryProperty(const QScriptValue &object, + const QScriptString &name, + QueryFlags flags, uint *id); + QScriptValue property(const QScriptValue &object, + const QScriptString &name, uint id); + + QScriptValue scriptValueForBuiltin(BuiltinValue::Builtin builtin) const; + +private: + QueryFlags queryItemProperty(const EvaluationData *data, + const QString &name, + bool ignoreParent = false); + static QString resultToString(const QScriptValue &scriptValue); + static Item *findParentOfType(const Item *item, const QString &typeName); + static QScriptValue js_getNativeSetting(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getHostOS(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine); + + struct QueryResult + { + QueryResult() + : data(0), inPrototype(false) + {} + + bool isNull() const + { + return !data; + } + + const EvaluationData *data; + bool inPrototype; + ValuePtr value; + }; + QueryResult m_queryResult; + Logger m_logger; + QScriptValue m_getNativeSettingBuiltin; + QScriptValue m_getEnvBuiltin; + QScriptValue m_getHostOSBuiltin; + QScriptValue m_canonicalArchitectureBuiltin; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATORSCRIPTCLASS_H diff --git a/src/lib/corelib/language/filecontext.cpp b/src/lib/corelib/language/filecontext.cpp new file mode 100644 index 000000000..570b40c66 --- /dev/null +++ b/src/lib/corelib/language/filecontext.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 "filecontext.h" +#include <tools/fileinfo.h> + +namespace qbs { +namespace Internal { + +FileContext::FileContext() + : m_idScope(0) +{ +} + +FileContextPtr FileContext::create() +{ + return FileContextPtr(new FileContext); +} + +QString FileContext::dirPath() const +{ + return FileInfo::path(m_filePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filecontext.h b/src/lib/corelib/language/filecontext.h new file mode 100644 index 000000000..75f93f04c --- /dev/null +++ b/src/lib/corelib/language/filecontext.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_FILECONTEXT_H +#define QBS_FILECONTEXT_H + +#include "item.h" +#include "jsimports.h" + +#include <QStringList> + +namespace qbs { +namespace Internal { + +class FileContext +{ + friend class ItemReaderASTVisitor; + + FileContext(); + +public: + static FileContextPtr create(); + + QString filePath() const; + QString dirPath() const; + JsImports jsImports() const; + QStringList jsExtensions() const; + + Item *idScope() const; + +private: + QString m_filePath; + JsImports m_jsImports; + QStringList m_jsExtensions; + Item *m_idScope; +}; + +inline QString FileContext::filePath() const +{ + return m_filePath; +} + +inline JsImports FileContext::jsImports() const +{ + return m_jsImports; +} + +inline QStringList FileContext::jsExtensions() const +{ + return m_jsExtensions; +} + +inline Item *FileContext::idScope() const +{ + return m_idScope; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILECONTEXT_H diff --git a/src/lib/corelib/language/filetags.cpp b/src/lib/corelib/language/filetags.cpp new file mode 100644 index 000000000..a5af1f0b2 --- /dev/null +++ b/src/lib/corelib/language/filetags.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 "filetags.h" +#include <QStringList> + +namespace qbs { +namespace Internal { + +void FileTag::clear() +{ + Id::operator=(Id()); +} + +QStringList FileTags::toStringList() const +{ + QStringList strlst; + foreach (const FileTag &tag, *this) + strlst += tag.toString(); + return strlst; +} + +FileTags FileTags::fromStringList(const QStringList &strings) +{ + FileTags result; + foreach (const QString &str, strings) + result += FileTag(str.toLocal8Bit()); + return result; +} + +/*! + * \return \c{true} if this file tags set has file tags in common with \c{other}. + */ +bool FileTags::matches(const FileTags &other) const +{ + for (FileTags::const_iterator it = other.begin(); it != other.end(); ++it) + if (contains(*it)) + return true; + return false; +} + +LogWriter operator <<(LogWriter w, const FileTags &tags) +{ + bool firstLoop = true; + w.write('('); + foreach (const FileTag &tag, tags) { + if (firstLoop) + firstLoop = false; + else + w.write(QLatin1String(", ")); + w.write(tag.toString()); + } + w.write(')'); + return w; +} + +QDataStream &operator >>(QDataStream &s, FileTags &tags) +{ + int i; + s >> i; + tags.clear(); + tags.reserve(i); + QVariant v; + while (--i >= 0) { + s >> v; + tags += FileTag::fromSetting(v); + } + return s; +} + +QDataStream &operator <<(QDataStream &s, const FileTags &tags) +{ + s << tags.count(); + foreach (const FileTag &ft, tags) + s << ft.toSetting(); + return s; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filetags.h b/src/lib/corelib/language/filetags.h new file mode 100644 index 000000000..44effc509 --- /dev/null +++ b/src/lib/corelib/language/filetags.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_FILETAGS_H +#define QBS_FILETAGS_H + +#include <logging/logger.h> +#include <tools/id.h> +#include <QDataStream> +#include <QSet> + +namespace qbs { +namespace Internal { + +class FileTag : public Id +{ +public: + FileTag() + : Id() + {} + + FileTag(const Id &other) + : Id(other) + {} + + FileTag(const char *str) + : Id(str) + {} + + explicit FileTag(const QByteArray &ba) + : Id(ba) + {} + + void clear(); +}; + +class FileTags : public QSet<FileTag> +{ +public: + QStringList toStringList() const; + static FileTags fromStringList(const QStringList &strings); + bool matches(const FileTags &other) const; +}; + +LogWriter operator <<(LogWriter w, const FileTags &tags); +QDataStream &operator >>(QDataStream &s, FileTags & tags); +QDataStream &operator <<(QDataStream &s, const FileTags &tags); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILETAGS_H + diff --git a/src/lib/corelib/language/forward_decls.h b/src/lib/corelib/language/forward_decls.h new file mode 100644 index 000000000..dc7c572df --- /dev/null +++ b/src/lib/corelib/language/forward_decls.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#ifndef QBS_LANG_FORWARD_DECLS_H +#define QBS_LANG_FORWARD_DECLS_H + +#include <QSharedPointer> + +namespace qbs { +namespace Internal { + +class Value; +typedef QSharedPointer<Value> ValuePtr; +typedef QSharedPointer<const Value> ValueConstPtr; + +class ItemValue; +typedef QSharedPointer<ItemValue> ItemValuePtr; +typedef QSharedPointer<const ItemValue> ItemValueConstPtr; + +class JSSourceValue; +typedef QSharedPointer<JSSourceValue> JSSourceValuePtr; +typedef QSharedPointer<const JSSourceValue> JSSourceValueConstPtr; + +class VariantValue; +typedef QSharedPointer<VariantValue> VariantValuePtr; +typedef QSharedPointer<const VariantValue> VariantValueConstPtr; + +class BuiltinValue; +typedef QSharedPointer<BuiltinValue> BuiltinValuePtr; +typedef QSharedPointer<const BuiltinValue> BuiltinValueConstPtr; + +class FileContext; +typedef QSharedPointer<FileContext> FileContextPtr; +typedef QSharedPointer<const FileContext> FileContextConstPtr; + +class PropertyMapInternal; +typedef QSharedPointer<PropertyMapInternal> PropertyMapPtr; +typedef QSharedPointer<const PropertyMapInternal> PropertyMapConstPtr; + +class FileTagger; +typedef QSharedPointer<FileTagger> FileTaggerPtr; +typedef QSharedPointer<const FileTagger> FileTaggerConstPtr; + +class ResolvedProduct; +typedef QSharedPointer<ResolvedProduct> ResolvedProductPtr; +typedef QSharedPointer<const ResolvedProduct> ResolvedProductConstPtr; + +class ResolvedProject; +typedef QSharedPointer<ResolvedProject> ResolvedProjectPtr; +typedef QSharedPointer<const ResolvedProject> ResolvedProjectConstPtr; + +class TopLevelProject; +typedef QSharedPointer<TopLevelProject> TopLevelProjectPtr; +typedef QSharedPointer<const TopLevelProject> TopLevelProjectConstPtr; + +class ResolvedFileContext; +typedef QSharedPointer<ResolvedFileContext> ResolvedFileContextPtr; +typedef QSharedPointer<const ResolvedFileContext> ResolvedFileContextConstPtr; + +class Rule; +typedef QSharedPointer<Rule> RulePtr; +typedef QSharedPointer<const Rule> RuleConstPtr; + +class SourceArtifact; +typedef QSharedPointer<SourceArtifact> SourceArtifactPtr; +typedef QSharedPointer<const SourceArtifact> SourceArtifactConstPtr; + +class ScriptFunction; +typedef QSharedPointer<ScriptFunction> ScriptFunctionPtr; +typedef QSharedPointer<const ScriptFunction> ScriptFunctionConstPtr; + +class RuleArtifact; +typedef QSharedPointer<RuleArtifact> RuleArtifactPtr; +typedef QSharedPointer<const RuleArtifact> RuleArtifactConstPtr; + +class ResolvedModule; +typedef QSharedPointer<ResolvedModule> ResolvedModulePtr; +typedef QSharedPointer<const ResolvedModule> ResolvedModuleConstPtr; + +class ResolvedGroup; +typedef QSharedPointer<ResolvedGroup> GroupPtr; +typedef QSharedPointer<const ResolvedGroup> GroupConstPtr; + +class ResolvedTransformer; +typedef QSharedPointer<ResolvedTransformer> ResolvedTransformerPtr; +typedef QSharedPointer<const ResolvedTransformer> ResolvedTransformerConstPtr; + +class ArtifactProperties; +typedef QSharedPointer<ArtifactProperties> ArtifactPropertiesPtr; +typedef QSharedPointer<const ArtifactProperties> ArtifactPropertiesConstPtr; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LANG_FORWARD_DECLS_H diff --git a/src/lib/corelib/language/functiondeclaration.h b/src/lib/corelib/language/functiondeclaration.h new file mode 100644 index 000000000..656997e7a --- /dev/null +++ b/src/lib/corelib/language/functiondeclaration.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_FUNCTIONDECLARATION_H +#define QBS_FUNCTIONDECLARATION_H + +#include <tools/codelocation.h> + +namespace qbs { +namespace Internal { + +class FunctionDeclaration +{ +public: + FunctionDeclaration() {} + + void setName(const QString &name) { m_name = name; } + const QString &name() const { return m_name; } + + void setSourceCode(const QString &code) { m_sourceCode = code; } + const QString &sourceCode() const { return m_sourceCode; } + + void setLocation(const CodeLocation &location) { m_location = location; } + const CodeLocation &location() const { return m_location; } + +private: + QString m_name; + QString m_sourceCode; + CodeLocation m_location; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FUNCTIONDECLARATION_H diff --git a/src/lib/corelib/language/identifiersearch.cpp b/src/lib/corelib/language/identifiersearch.cpp new file mode 100644 index 000000000..813e87922 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 "identifiersearch.h" +#include <parser/qmljsast_p.h> + +namespace qbs { +namespace Internal { + +IdentifierSearch::IdentifierSearch() +{ +} + +void IdentifierSearch::start(QbsQmlJS::AST::Node *node) +{ + foreach (bool *found, m_requests) + *found = false; + m_numberOfFoundIds = 0; + node->accept(this); +} + +void IdentifierSearch::add(const QString &name, bool *found) +{ + m_requests.insert(name, found); +} + +bool IdentifierSearch::preVisit(QbsQmlJS::AST::Node *) +{ + return m_numberOfFoundIds < m_requests.count(); +} + +bool IdentifierSearch::visit(QbsQmlJS::AST::IdentifierExpression *e) +{ + bool *found = m_requests.value(e->name.toString()); + if (found && !*found) { + *found = true; + m_numberOfFoundIds++; + } + return m_numberOfFoundIds < m_requests.count(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/identifiersearch.h b/src/lib/corelib/language/identifiersearch.h new file mode 100644 index 000000000..f82ee3262 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_IDENTIFIERSEARCHVISITOR_H +#define QBS_IDENTIFIERSEARCHVISITOR_H + +#include <parser/qmljsastfwd_p.h> +#include <parser/qmljsastvisitor_p.h> +#include <QMap> +#include <QString> + +namespace qbs { +namespace Internal { + +class IdentifierSearch : private QbsQmlJS::AST::Visitor +{ +public: + IdentifierSearch(); + void start(QbsQmlJS::AST::Node *node); + void add(const QString &name, bool *found); + +private: + bool preVisit(QbsQmlJS::AST::Node *); + bool visit(QbsQmlJS::AST::IdentifierExpression *e); + + QMap<QString, bool *> m_requests; + int m_numberOfFoundIds; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IDENTIFIERSEARCHVISITOR_H diff --git a/src/lib/corelib/language/importversion.cpp b/src/lib/corelib/language/importversion.cpp new file mode 100644 index 000000000..848775016 --- /dev/null +++ b/src/lib/corelib/language/importversion.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 "importversion.h" +#include <logging/translator.h> +#include <tools/error.h> +#include <QStringList> + +namespace qbs { +namespace Internal { + +ImportVersion::ImportVersion() + : m_major(0), m_minor(0) +{ +} + +ImportVersion ImportVersion::fromString(const QString &str, const CodeLocation &location) +{ + QStringList lst = str.split(QLatin1Char('.')); + if (Q_UNLIKELY(lst.count() < 1 || lst.count() > 2)) + throw ErrorInfo(Tr::tr("Wrong number of components in import version."), location); + ImportVersion v; + int *parts[] = {&v.m_major, &v.m_minor, 0}; + for (int i = 0; i < lst.count(); ++i) { + if (!parts[i]) + break; + bool ok; + *parts[i] = lst.at(i).toInt(&ok); + if (Q_UNLIKELY(!ok)) + throw ErrorInfo(Tr::tr("Cannot parse import version."), location); + } + return v; +} + +bool ImportVersion::operator <(const ImportVersion &rhs) const +{ + return m_major < rhs.m_major || (m_major == rhs.m_major && m_minor < rhs.m_minor); +} + +bool ImportVersion::operator ==(const ImportVersion &rhs) const +{ + return m_major == rhs.m_major && m_minor == rhs.m_minor; +} + +bool ImportVersion::operator !=(const ImportVersion &rhs) const +{ + return !operator ==(rhs); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/importversion.h b/src/lib/corelib/language/importversion.h new file mode 100644 index 000000000..034258793 --- /dev/null +++ b/src/lib/corelib/language/importversion.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_IMPORTVERSION_H +#define QBS_IMPORTVERSION_H + +#include <tools/codelocation.h> + +namespace qbs { +namespace Internal { + +class ImportVersion +{ +public: + ImportVersion(); + + static ImportVersion fromString(const QString &str, + const CodeLocation &location = CodeLocation()); + + bool operator <(const ImportVersion &rhs) const; + bool operator ==(const ImportVersion &rhs) const; + bool operator !=(const ImportVersion &rhs) const; + +private: + int m_major; + int m_minor; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IMPORTVERSION_H diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp new file mode 100644 index 000000000..7b42df5e6 --- /dev/null +++ b/src/lib/corelib/language/item.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "item.h" +#include "itempool.h" +#include "filecontext.h" +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +Item::Item(ItemPool *pool) + : m_pool(pool) + , m_propertyObserver(0) + , m_moduleInstance(false) + , m_prototype(0) + , m_scope(0) + , m_outerItem(0) + , m_parent(0) +{ +} + +Item::~Item() +{ + if (m_propertyObserver) + m_propertyObserver->onItemDestroyed(this); +} + +Item *Item::create(ItemPool *pool) +{ + return pool->allocateItem(); +} + +Item *Item::clone(ItemPool *pool) const +{ + Item *dup = create(pool); + dup->m_id = m_id; + dup->m_typeName = m_typeName; + dup->m_location = m_location; + dup->m_prototype = m_prototype; + dup->m_scope = m_scope; + dup->m_outerItem = m_outerItem; + dup->m_parent = m_parent; + dup->m_children = m_children; + dup->m_file = m_file; + dup->m_properties = m_properties; + dup->m_propertyDeclarations = m_propertyDeclarations; + dup->m_functions = m_functions; + dup->m_modules = m_modules; + return dup; +} + +bool Item::hasProperty(const QString &name) const +{ + for (const Item *item = this; item; item = item->m_prototype) + if (item->m_properties.contains(name)) + return true; + + return false; +} + +bool Item::hasOwnProperty(const QString &name) const +{ + return m_properties.contains(name); +} + +ValuePtr Item::property(const QString &name) const +{ + ValuePtr value; + for (const Item *item = this; item; item = item->m_prototype) + if ((value = item->m_properties.value(name))) + break; + return value; +} + +ItemValuePtr Item::itemProperty(const QString &name, bool create) +{ + ItemValuePtr result; + ValuePtr v = property(name); + if (v && v->type() == Value::ItemValueType) { + result = v.staticCast<ItemValue>(); + } else if (create) { + result = ItemValue::create(Item::create(m_pool)); + setProperty(name, result); + } + return result; +} + +JSSourceValuePtr Item::sourceProperty(const QString &name) const +{ + ValuePtr v = property(name); + if (!v || v->type() != Value::JSSourceValueType) + return JSSourceValuePtr(); + return v.staticCast<JSSourceValue>(); +} + +const PropertyDeclaration Item::propertyDeclaration(const QString &name) const +{ + const PropertyDeclaration decl = m_propertyDeclarations.value(name); + return (!decl.isValid() && m_prototype) ? m_prototype->propertyDeclaration(name) : decl; +} + +void Item::setPropertyObserver(ItemObserver *observer) const +{ + QBS_ASSERT(!observer || !m_propertyObserver, return); // warn if accidentally overwritten + m_propertyObserver = observer; +} + +Item *Item::child(const QString &type, bool checkForMultiple) const +{ + Item *child = 0; + foreach (Item * const currentChild, children()) { + if (currentChild->typeName() == type) { + if (!checkForMultiple) + return currentChild; + if (child) { + ErrorInfo error(Tr::tr("Multiple instances of item '%1' found where at most one " + "is allowed.").arg(type)); + error.append(Tr::tr("First item"), child->location()); + error.append(Tr::tr("Second item"), currentChild->location()); + throw error; + } + child = currentChild; + } + } + return child; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h new file mode 100644 index 000000000..866927a0e --- /dev/null +++ b/src/lib/corelib/language/item.h @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEM_H +#define QBS_ITEM_H + +#include "forward_decls.h" +#include "itemobserver.h" +#include "value.h" +#include "functiondeclaration.h" +#include "propertydeclaration.h" +#include <parser/qmljsmemorypool_p.h> +#include <tools/codelocation.h> +#include <tools/error.h> +#include <tools/weakpointer.h> + +#include <QList> +#include <QMap> +#include <QSharedPointer> +#include <QStringList> + +namespace qbs { +namespace Internal { + +class ItemPool; +class ProjectFile; + +class Item : public QbsQmlJS::Managed +{ + friend class BuiltinDeclarations; + friend class ItemPool; + friend class ItemReaderASTVisitor; + Q_DISABLE_COPY(Item) + Item(ItemPool *pool); + +public: + ~Item(); + + struct Module + { + Module() + : item(0) + {} + + QStringList name; + Item *item; + }; + typedef QList<Module> Modules; + typedef QMap<QString, PropertyDeclaration> PropertyDeclarationMap; + typedef QMap<QString, ValuePtr> PropertyMap; + + static Item *create(ItemPool *pool); + Item *clone(ItemPool *pool) const; + ItemPool *pool() const; + + const QString &id() const; + const QString &typeName() const; + const CodeLocation &location() const; + Item *prototype() const; + Item *scope() const; + bool isModuleInstance() const; + Item *outerItem() const; + Item *parent() const; + const FileContextPtr file() const; + QList<Item *> children() const; + Item *child(const QString &type, bool checkForMultiple = true) const; + const PropertyMap &properties() const; + const PropertyDeclarationMap &propertyDeclarations() const; + const PropertyDeclaration propertyDeclaration(const QString &name) const; + const Modules &modules() const; + Modules &modules(); + const ErrorInfo &error() const { return m_error; } + + bool hasProperty(const QString &name) const; + bool hasOwnProperty(const QString &name) const; + ValuePtr property(const QString &name) const; + ItemValuePtr itemProperty(const QString &name, bool create = false); + JSSourceValuePtr sourceProperty(const QString &name) const; + void setPropertyObserver(ItemObserver *observer) const; + void setProperty(const QString &name, const ValuePtr &value); + void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); + void setTypeName(const QString &name); + void setLocation(const CodeLocation &location); + void setPrototype(Item *prototype); + void setFile(const FileContextPtr &file); + void setScope(Item *item); + void setModuleInstanceFlag(bool b); + void setOuterItem(Item *item); + void setChildren(const QList<Item *> &children); + void setParent(Item *item); + static void addChild(Item *parent, Item *child); + void setError(const ErrorInfo &error) { m_error = error; } + +private: + ItemPool *m_pool; + mutable ItemObserver *m_propertyObserver; + QString m_id; + QString m_typeName; + CodeLocation m_location; + bool m_moduleInstance; + Item *m_prototype; + Item *m_scope; + Item *m_outerItem; + Item *m_parent; + QList<Item *> m_children; + FileContextPtr m_file; + PropertyMap m_properties; + PropertyDeclarationMap m_propertyDeclarations; + QList<FunctionDeclaration> m_functions; + Modules m_modules; + ErrorInfo m_error; // For SubProject items. May or may not be reported depending on their condition. +}; + +inline ItemPool *Item::pool() const +{ + return m_pool; +} + +inline const QString &Item::id() const +{ + return m_id; +} + +inline const QString &Item::typeName() const +{ + return m_typeName; +} + +inline const CodeLocation &Item::location() const +{ + return m_location; +} + +inline Item *Item::prototype() const +{ + return m_prototype; +} + +inline Item *Item::scope() const +{ + return m_scope; +} + +inline bool Item::isModuleInstance() const +{ + return m_moduleInstance; +} + +inline Item *Item::outerItem() const +{ + return m_outerItem; +} + +inline Item *Item::parent() const +{ + return m_parent; +} + +inline const FileContextPtr Item::file() const +{ + return m_file; +} + +inline QList<Item *> Item::children() const +{ + return m_children; +} + +inline const Item::PropertyMap &Item::properties() const +{ + return m_properties; +} + +inline const Item::PropertyDeclarationMap &Item::propertyDeclarations() const +{ + return m_propertyDeclarations; +} + +inline void Item::setProperty(const QString &name, const ValuePtr &value) +{ + m_properties.insert(name, value); + if (m_propertyObserver) + m_propertyObserver->onItemPropertyChanged(this); +} + +inline void Item::setPropertyDeclaration(const QString &name, + const PropertyDeclaration &declaration) +{ + m_propertyDeclarations.insert(name, declaration); +} + +inline void Item::setTypeName(const QString &name) +{ + m_typeName = name; +} + +inline void Item::setLocation(const CodeLocation &location) +{ + m_location = location; +} + +inline void Item::setPrototype(Item *prototype) +{ + m_prototype = prototype; +} + +inline void Item::setFile(const FileContextPtr &file) +{ + m_file = file; +} + +inline void Item::setScope(Item *item) +{ + m_scope = item; +} + +inline void Item::setModuleInstanceFlag(bool b) +{ + m_moduleInstance = b; +} + +inline void Item::setOuterItem(Item *item) +{ + m_outerItem = item; +} + +inline void Item::setChildren(const QList<Item *> &children) +{ + m_children = children; +} + +inline void Item::setParent(Item *item) +{ + m_parent = item; +} + +inline void Item::addChild(Item *parent, Item *child) +{ + parent->m_children.append(child); + child->setParent(parent); +} + +inline const Item::Modules &Item::modules() const +{ + return m_modules; +} + +inline Item::Modules &Item::modules() +{ + return m_modules; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEM_H diff --git a/src/lib/corelib/language/itemdeclaration.cpp b/src/lib/corelib/language/itemdeclaration.cpp new file mode 100644 index 000000000..e2fdb9330 --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 "itemdeclaration.h" + +namespace qbs { +namespace Internal { + +ItemDeclaration::ItemDeclaration(const QString &typeName) + : m_typeName(typeName) +{ +} + +ItemDeclaration::ItemDeclaration(const qbs::Internal::ItemDeclaration &other) + : m_typeName(other.m_typeName) + , m_properties(other.m_properties) + , m_allowedChildTypes(other.m_allowedChildTypes) +{ +} + +ItemDeclaration &ItemDeclaration::operator<<(const PropertyDeclaration &decl) +{ + m_properties.append(decl); + return *this; +} + +bool ItemDeclaration::isChildTypeAllowed(const QString &typeName) const +{ + return m_allowedChildTypes.contains(typeName); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.h b/src/lib/corelib/language/itemdeclaration.h new file mode 100644 index 000000000..25a0e3fd3 --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEMDECLARATION_H +#define QBS_ITEMDECLARATION_H + +#include "propertydeclaration.h" +#include <QSet> + +namespace qbs { +namespace Internal { + +class ItemDeclaration +{ +public: + ItemDeclaration(const QString &typeName = QString()); + ItemDeclaration(const ItemDeclaration &other); + + const QString &typeName() const { return m_typeName; } + + typedef QList<PropertyDeclaration> Properties; + void setProperties(const Properties &props) { m_properties = props; } + const Properties &properties() const { return m_properties; } + + ItemDeclaration &operator<<(const PropertyDeclaration &decl); + + typedef QSet<QString> TypeNames; + void setAllowedChildTypes(const TypeNames &typeNames) { m_allowedChildTypes = typeNames; } + const TypeNames &allowedChildTypes() const { return m_allowedChildTypes; } + bool isChildTypeAllowed(const QString &typeName) const; + +private: + QString m_typeName; + Properties m_properties; + TypeNames m_allowedChildTypes; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMDECLARATION_H diff --git a/src/lib/corelib/language/itemobserver.h b/src/lib/corelib/language/itemobserver.h new file mode 100644 index 000000000..684b33d14 --- /dev/null +++ b/src/lib/corelib/language/itemobserver.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEMOBSERVER_H +#define QBS_ITEMOBSERVER_H + +#include <QString> + +namespace qbs { +namespace Internal { + +class Item; + +class ItemObserver +{ +public: + virtual void onItemPropertyChanged(Item *item) = 0; + virtual void onItemDestroyed(Item *item) = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMOBSERVER_H diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp new file mode 100644 index 000000000..a07022c3f --- /dev/null +++ b/src/lib/corelib/language/itempool.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 "itempool.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +ItemPool::ItemPool() +{ +} + +ItemPool::~ItemPool() +{ + for (ItemVector::const_iterator it = m_items.constBegin(); it != m_items.constEnd(); ++it) + (*it)->~Item(); +} + +Item *ItemPool::allocateItem() +{ + Item *item = new (&m_pool) Item(this); + m_items.push_back(item); + return item; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itempool.h b/src/lib/corelib/language/itempool.h new file mode 100644 index 000000000..04d7bbb40 --- /dev/null +++ b/src/lib/corelib/language/itempool.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEMPOOL_H +#define QBS_ITEMPOOL_H + +#include <parser/qmljsmemorypool_p.h> + +#include <QList> + +namespace qbs { +namespace Internal { + +class Item; + +class ItemPool +{ + Q_DISABLE_COPY(ItemPool) +public: + ItemPool(); + ~ItemPool(); + + Item *allocateItem(); + +private: + QbsQmlJS::MemoryPool m_pool; + typedef QList<Item *> ItemVector; + ItemVector m_items; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMPOOL_H diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp new file mode 100644 index 000000000..ba4859899 --- /dev/null +++ b/src/lib/corelib/language/itemreader.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** 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 "itemreader.h" +#include "asttools.h" +#include "itemreaderastvisitor.h" +#include <logging/translator.h> +#include <parser/qmljsengine_p.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/error.h> +#include <QExplicitlySharedDataPointer> +#include <QFile> +#include <QFileInfo> +#include <QSharedData> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +class ASTCacheValueData : public QSharedData +{ + Q_DISABLE_COPY(ASTCacheValueData) +public: + ASTCacheValueData() + : ast(0) + , processing(false) + { + } + + QString code; + QbsQmlJS::Engine engine; + QbsQmlJS::AST::UiProgram *ast; + bool processing; +}; + +class ASTCacheValue +{ +public: + ASTCacheValue() + : d(new ASTCacheValueData) + { + } + + ASTCacheValue(const ASTCacheValue &other) + : d(other.d) + { + } + + void setProcessingFlag(bool b) { d->processing = b; } + bool isProcessing() const { return d->processing; } + + void setCode(const QString &code) { d->code = code; } + QString code() const { return d->code; } + + QbsQmlJS::Engine *engine() const { return &d->engine; } + + void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; } + QbsQmlJS::AST::UiProgram *ast() const { return d->ast; } + bool isValid() const { return d->ast; } + +private: + QExplicitlySharedDataPointer<ASTCacheValueData> d; +}; + +class ItemReader::ASTCache : public QHash<QString, ASTCacheValue> {}; + + +ItemReader::ItemReader(BuiltinDeclarations *builtins, const Logger &logger) + : m_pool(0) + , m_builtins(builtins) + , m_logger(logger) + , m_astCache(new ASTCache) +{ +} + +ItemReader::~ItemReader() +{ + delete m_astCache; +} + +void ItemReader::setSearchPaths(const QStringList &searchPaths) +{ + m_searchPaths = searchPaths; +} + +void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) +{ + m_extraSearchPaths.push(extraSearchPaths); +} + +void ItemReader::popExtraSearchPaths() +{ + m_extraSearchPaths.pop(); +} + +QStringList ItemReader::searchPaths() const +{ + QStringList paths = m_searchPaths; + if (!m_extraSearchPaths.isEmpty()) + paths += m_extraSearchPaths.top(); + return paths; +} + +void ItemReader::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) +{ + m_directoryEntries.insert(dirPath, entries); +} + +bool ItemReader::findDirectoryEntries(const QString &dirPath, QStringList *entries) const +{ + const QHash<QString, QStringList>::ConstIterator it = m_directoryEntries.constFind(dirPath); + if (it == m_directoryEntries.constEnd()) + return false; + *entries = it.value(); + return true; +} + +Item *ItemReader::readFile(const QString &filePath) +{ + Item * const item = internalReadFile(filePath).rootItem; + return item; +} + +QSet<QString> ItemReader::filesRead() const +{ + return m_filesRead; +} + +ItemReaderResult ItemReader::internalReadFile(const QString &filePath) +{ + ASTCacheValue &cacheValue = (*m_astCache)[filePath]; + if (cacheValue.isValid()) { + if (Q_UNLIKELY(cacheValue.isProcessing())) + throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); + } else { + QFile file(filePath); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(Tr::tr("Couldn't open '%1'.").arg(filePath)); + + m_filesRead.insert(filePath); + const QString code = QTextStream(&file).readAll(); + QbsQmlJS::Lexer lexer(cacheValue.engine()); + lexer.setCode(code, 1); + QbsQmlJS::Parser parser(cacheValue.engine()); + + file.close(); + if (!parser.parse()) { + QList<QbsQmlJS::DiagnosticMessage> parserMessages = parser.diagnosticMessages(); + if (Q_UNLIKELY(!parserMessages.isEmpty())) { + ErrorInfo err; + foreach (const QbsQmlJS::DiagnosticMessage &msg, parserMessages) + err.append(msg.message, toCodeLocation(filePath, msg.loc)); + throw err; + } + } + + cacheValue.setCode(code); + cacheValue.setAst(parser.ast()); + } + + ItemReaderResult result; + ItemReaderASTVisitor itemReader(this, &result); + itemReader.setFilePath(QFileInfo(filePath).absoluteFilePath()); + itemReader.setSourceCode(cacheValue.code()); + cacheValue.setProcessingFlag(true); + cacheValue.ast()->accept(&itemReader); + cacheValue.setProcessingFlag(false); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h new file mode 100644 index 000000000..266c30f93 --- /dev/null +++ b/src/lib/corelib/language/itemreader.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEMREADER_H +#define QBS_ITEMREADER_H + +#include "forward_decls.h" +#include <logging/logger.h> + +#include <QHash> +#include <QSet> +#include <QStack> +#include <QStringList> + +namespace qbs { +namespace Internal { + +class BuiltinDeclarations; +class Item; +class ItemPool; + +struct ItemReaderResult +{ + ItemReaderResult() + : rootItem(0) + {} + + Item *rootItem; + typedef QHash<const Item *, QSet<JSSourceValuePtr> > SourceValuesPerItem; + SourceValuesPerItem conditionalValuesPerScopeItem; +}; + +/* + * Reads a qbs file and creates a tree of Item objects. + * + * In this stage the following steps are performed: + * - The QML/JS parser creates the AST. + * - The AST is converted to a tree of Item objects. + * + * This class is also responsible for the QMLish inheritance semantics. + */ +class ItemReader +{ + friend class ItemReaderASTVisitor; +public: + ItemReader(BuiltinDeclarations *builtins, const Logger &logger); + ~ItemReader(); + + BuiltinDeclarations *builtins() const { return m_builtins; } + Logger logger() const { return m_logger; } + + void setPool(ItemPool *pool) { m_pool = pool; } + void setSearchPaths(const QStringList &searchPaths); + void pushExtraSearchPaths(const QStringList &extraSearchPaths); + void popExtraSearchPaths(); + QStringList searchPaths() const; + + Item *readFile(const QString &filePath); + + QSet<QString> filesRead() const; + +private: + ItemReaderResult internalReadFile(const QString &filePath); + + void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); + bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; + + ItemPool *m_pool; + BuiltinDeclarations *m_builtins; + Logger m_logger; + QStringList m_searchPaths; + QStack<QStringList> m_extraSearchPaths; + QHash<const Item *, QSet<JSSourceValuePtr> > m_conditionalValuesPerScopeItem; + + class ASTCache; + ASTCache *m_astCache; + QSet<QString> m_filesRead; + QHash<QString, QStringList> m_directoryEntries; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADER_H diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp new file mode 100644 index 000000000..a08a73648 --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.cpp @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** 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 "itemreaderastvisitor.h" +#include "asttools.h" +#include "builtindeclarations.h" +#include "identifiersearch.h" +#include "itemreader.h" +#include <jsextensions/jsextensions.h> +#include <parser/qmljsast_p.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> +#include <logging/translator.h> + +#include <QDirIterator> +#include <QFileInfo> +#include <QStringList> + +using namespace QbsQmlJS; + +namespace qbs { +namespace Internal { + +ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReader *reader, ItemReaderResult *result) + : m_reader(reader) + , m_readerResult(result) + , m_languageVersion(ImportVersion::fromString(reader->builtins()->languageVersion())) + , m_item(0) + , m_sourceValue(0) +{ +} + +ItemReaderASTVisitor::~ItemReaderASTVisitor() +{ +} + +bool ItemReaderASTVisitor::visit(AST::UiProgram *ast) +{ + Q_UNUSED(ast); + m_sourceValue.clear(); + m_file = FileContext::create(); + m_file->m_filePath = m_filePath; + + if (Q_UNLIKELY(!ast->members->member)) + throw ErrorInfo(Tr::tr("No root item found in %1.").arg(m_filePath)); + + return true; +} + +bool ItemReaderASTVisitor::addPrototype(const QString &fileName, const QString &filePath, + const QString &as, bool needsCheck) +{ + if (needsCheck && fileName.size() <= 4) + return false; + + const QString componentName = fileName.left(fileName.size() - 4); + // ### validate componentName + + if (needsCheck && !componentName.at(0).isUpper()) + return false; + + QStringList prototypeName; + if (!as.isEmpty()) + prototypeName.append(as); + prototypeName.append(componentName); + m_typeNameToFile.insert(prototypeName, filePath); + return true; +} + +void ItemReaderASTVisitor::collectPrototypes(const QString &path, const QString &as) +{ + QStringList fileNames; // Yes, file *names*. + if (m_reader->findDirectoryEntries(path, &fileNames)) { + foreach (const QString &fileName, fileNames) + addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); + return; + } + + QDirIterator dirIter(path, QStringList("*.qbs")); + while (dirIter.hasNext()) { + const QString filePath = dirIter.next(); + const QString fileName = dirIter.fileName(); + if (addPrototype(fileName, filePath, as, true)) + fileNames << fileName; + } + m_reader->cacheDirectoryEntries(path, fileNames); +} + +bool ItemReaderASTVisitor::visit(AST::UiImportList *uiImportList) +{ + foreach (const QString &searchPath, m_reader->searchPaths()) + collectPrototypes(searchPath + QLatin1String("/imports"), QString()); + + const QString path = FileInfo::path(m_filePath); + + // files in the same directory are available as prototypes + collectPrototypes(path, QString()); + + QSet<QString> importAsNames; + QHash<QString, JsImport> jsImports; + + for (const AST::UiImportList *it = uiImportList; it; it = it->next) { + const AST::UiImport *const import = it->import; + + QStringList importUri; + bool isBase = false; + if (import->importUri) { + importUri = toStringList(import->importUri); + isBase = (importUri.size() == 1 && importUri.first() == QLatin1String("qbs")) + || (importUri.size() == 2 && importUri.first() == QLatin1String("qbs") + && importUri.last() == QLatin1String("base")); + if (isBase) + checkImportVersion(import->versionToken); + else if (import->versionToken.length) + m_reader->logger().printWarning(ErrorInfo(Tr::tr("Superfluous version specification."), + toCodeLocation(import->versionToken))); + } + + QString as; + if (isBase) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of qbs.base must have no 'as <Name>'"), + toCodeLocation(import->importIdToken)); + } + } else { + if (importUri.count() == 2 && importUri.first() == QLatin1String("qbs")) { + const QString extensionName = importUri.last(); + if (JsExtensions::hasExtension(extensionName)) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of built-in extension '%1' " + "must not have 'as' specifier.").arg(extensionName)); + } + if (Q_UNLIKELY(m_file->m_jsExtensions.contains(extensionName))) { + m_reader->logger().printWarning(Tr::tr("Built-in extension '%1' already " + "imported.").arg(extensionName)); + } else { + m_file->m_jsExtensions << extensionName; + } + continue; + } + } + + if (import->importId.isNull()) { + if (!import->fileName.isNull()) { + throw ErrorInfo(Tr::tr("File imports require 'as <Name>'"), + toCodeLocation(import->importToken)); + } + if (importUri.isEmpty()) { + throw ErrorInfo(Tr::tr("Invalid import URI."), + toCodeLocation(import->importToken)); + } + as = importUri.last(); + } else { + as = import->importId.toString(); + } + + if (Q_UNLIKELY(importAsNames.contains(as))) { + throw ErrorInfo(Tr::tr("Can't import into the same name more than once."), + toCodeLocation(import->importIdToken)); + } + if (Q_UNLIKELY(JsExtensions::hasExtension(as))) { + throw ErrorInfo(Tr::tr("Cannot reuse the name of built-in extension '%1'.") + .arg(as)); + } + importAsNames.insert(as); + } + + if (!import->fileName.isNull()) { + QString name = FileInfo::resolvePath(path, import->fileName.toString()); + + QFileInfo fi(name); + if (Q_UNLIKELY(!fi.exists())) + throw ErrorInfo(Tr::tr("Can't find imported file %0.").arg(name), + CodeLocation(m_filePath, import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + name = fi.canonicalFilePath(); + if (fi.isDir()) { + collectPrototypes(name, as); + } else { + if (name.endsWith(".js", Qt::CaseInsensitive)) { + JsImport &jsImport = jsImports[as]; + jsImport.scopeName = as; + jsImport.fileNames.append(name); + jsImport.location = toCodeLocation(import->firstSourceLocation()); + } else if (name.endsWith(".qbs", Qt::CaseInsensitive)) { + m_typeNameToFile.insert(QStringList(as), name); + } else { + throw ErrorInfo(Tr::tr("Can only import .qbs and .js files"), + CodeLocation(m_filePath, import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + } + } + } else if (!importUri.isEmpty()) { + const QString importPath = isBase + ? QLatin1String("qbs/base") : importUri.join(QDir::separator()); + bool found = m_typeNameToFile.contains(importUri); + if (!found) { + foreach (const QString &searchPath, m_reader->searchPaths()) { + const QFileInfo fi(FileInfo::resolvePath( + FileInfo::resolvePath(searchPath, "imports"), importPath)); + if (fi.isDir()) { + // ### versioning, qbsdir file, etc. + const QString &resultPath = fi.absoluteFilePath(); + collectPrototypes(resultPath, as); + + QDirIterator dirIter(resultPath, QStringList("*.js")); + while (dirIter.hasNext()) { + dirIter.next(); + JsImport &jsImport = jsImports[as]; + if (jsImport.scopeName.isNull()) { + jsImport.scopeName = as; + jsImport.location = toCodeLocation(import->firstSourceLocation()); + } + jsImport.fileNames.append(dirIter.filePath()); + } + found = true; + break; + } + } + } + if (Q_UNLIKELY(!found)) { + throw ErrorInfo(Tr::tr("import %1 not found").arg(importUri.join(".")), + toCodeLocation(import->fileNameToken)); + } + } + } + + for (QHash<QString, JsImport>::const_iterator it = jsImports.constBegin(); + it != jsImports.constEnd(); ++it) + { + m_file->m_jsImports += it.value(); + } + + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) +{ + const QString typeName = ast->qualifiedTypeNameId->name.toString(); + + Item *item = Item::create(m_reader->m_pool); + item->m_file = m_file; + item->m_parent = m_item; + item->m_typeName = typeName; + item->m_location = ::qbs::Internal::toCodeLocation(m_file->filePath(), + ast->qualifiedTypeNameId->identifierToken); + + if (m_item) { + // Add this item to the children of the parent item. + m_item->m_children += item; + } else { + // This is the root item. + m_item = item; + m_readerResult->rootItem = item; + } + + if (ast->initializer) { + qSwap(m_item, item); + ast->initializer->accept(this); + qSwap(m_item, item); + } + + m_reader->m_builtins->setupItemForBuiltinType(item); + + if (item->typeName() != QLatin1String("Properties") + && item->typeName() != QLatin1String("SubProject")) { + setupAlternatives(item); + } + + // resolve inheritance + const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId); + const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName); + if (!baseTypeFileName.isEmpty()) { + const ItemReaderResult baseFile = m_reader->internalReadFile(baseTypeFileName); + mergeItem(item, baseFile.rootItem, baseFile); + if (baseFile.rootItem->m_file->m_idScope) { + // Make ids from the derived file visible in the base file. + // ### Do we want to turn off this feature? It's QMLish but kind of strange. + ensureIdScope(item->m_file); + baseFile.rootItem->m_file->m_idScope->setPrototype(item->m_file->m_idScope); + } + } + + return false; +} + +void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName, + const AST::SourceLocation &sourceLocation) +{ + if (Q_UNLIKELY(item->properties().contains(bindingName.last()))) { + QString msg = Tr::tr("Duplicate binding for '%1'"); + throw ErrorInfo(msg.arg(bindingName.join(".")), + qbs::Internal::toCodeLocation(m_file->filePath(), sourceLocation)); + } +} + +bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast) +{ + PropertyDeclaration p; + if (Q_UNLIKELY(ast->name.isEmpty())) + throw ErrorInfo(Tr::tr("public member without name")); + if (Q_UNLIKELY(ast->memberType.isEmpty())) + throw ErrorInfo(Tr::tr("public member without type")); + if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal)) + throw ErrorInfo(Tr::tr("public member with signal type not supported")); + p.name = ast->name.toString(); + p.type = PropertyDeclaration::propertyTypeFromString(ast->memberType.toString()); + if (p.type == PropertyDeclaration::UnknownType) + throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.") + .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken)); + if (ast->typeModifier.compare(QLatin1String("list"))) + p.flags |= PropertyDeclaration::ListProperty; + else if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) + throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg( + ast->typeModifier.toString())); + + m_item->m_propertyDeclarations.insert(p.name, p); + + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + if (ast->statement) { + m_sourceValue.swap(value); + visitStatement(ast->statement); + m_sourceValue.swap(value); + const QStringList bindingName(p.name); + checkDuplicateBinding(m_item, bindingName, ast->colonToken); + } + + m_item->m_properties.insert(p.name, value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) +{ + QBS_CHECK(ast->qualifiedId); + QBS_CHECK(!ast->qualifiedId->name.isEmpty()); + + const QStringList bindingName = toStringList(ast->qualifiedId); + + if (bindingName.length() == 1 && bindingName.first() == QLatin1String("id")) { + AST::ExpressionStatement *expStmt = + AST::cast<AST::ExpressionStatement *>(ast->statement); + if (Q_UNLIKELY(!expStmt)) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + AST::IdentifierExpression *idExp = + AST::cast<AST::IdentifierExpression *>(expStmt->expression); + if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + m_item->m_id = idExp->name.toString(); + ensureIdScope(m_file); + m_file->m_idScope->m_properties[m_item->m_id] = ItemValue::create(m_item); + return false; + } + + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + m_sourceValue.swap(value); + visitStatement(ast->statement); + m_sourceValue.swap(value); + + Item *targetItem = targetItemForBinding(m_item, bindingName, value->location()); + checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken); + targetItem->m_properties.insert(bindingName.last(), value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::FunctionDeclaration *ast) +{ + FunctionDeclaration f; + if (Q_UNLIKELY(ast->name.isNull())) + throw ErrorInfo(Tr::tr("function decl without name")); + f.setName(ast->name.toString()); + + // remove the name + QString funcNoName = textOf(m_sourceCode, ast); + funcNoName.replace(QRegExp("^(\\s*function\\s*)\\w*"), "(\\1"); + funcNoName.append(")"); + f.setSourceCode(funcNoName); + + f.setLocation(toCodeLocation(ast->firstSourceLocation())); + m_item->m_functions += f; + return false; +} + +bool ItemReaderASTVisitor::visitStatement(AST::Statement *statement) +{ + QBS_CHECK(statement); + QBS_CHECK(m_sourceValue); + + QString sourceCode = textOf(m_sourceCode, statement); + if (AST::cast<AST::Block *>(statement)) { + // rewrite blocks to be able to use return statements in property assignments + sourceCode.prepend("(function()"); + sourceCode.append(")()"); + m_sourceValue->m_hasFunctionForm = true; + } + + m_sourceValue->setSourceCode(sourceCode); + m_sourceValue->setLocation(toCodeLocation(statement->firstSourceLocation())); + + IdentifierSearch idsearch; + idsearch.add(QLatin1String("base"), &m_sourceValue->m_sourceUsesBase); + idsearch.add(QLatin1String("outer"), &m_sourceValue->m_sourceUsesOuter); + idsearch.start(statement); + return false; +} + +CodeLocation ItemReaderASTVisitor::toCodeLocation(AST::SourceLocation location) const +{ + return CodeLocation(m_filePath, location.startLine, location.startColumn); +} + +Item *ItemReaderASTVisitor::targetItemForBinding(Item *item, + const QStringList &bindingName, + const CodeLocation &bindingLocation) +{ + Item *targetItem = item; + const int c = bindingName.count() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->m_properties.value(bindingName.at(i)); + if (!v) { + Item *newItem = Item::create(m_reader->m_pool); + v = ItemValue::create(newItem); + targetItem->m_properties.insert(bindingName.at(i), v); + } + if (Q_UNLIKELY(v->type() != Value::ItemValueType)) { + QString msg = Tr::tr("Binding to non-item property."); + throw ErrorInfo(msg, bindingLocation); + } + ItemValuePtr jsv = v.staticCast<ItemValue>(); + targetItem = jsv->item(); + } + return targetItem; +} + +void ItemReaderASTVisitor::checkImportVersion(const AST::SourceLocation &versionToken) const +{ + if (!versionToken.length) + return; + const QString importVersionString = m_sourceCode.mid(versionToken.offset, versionToken.length); + const ImportVersion importVersion + = ImportVersion::fromString(importVersionString, toCodeLocation(versionToken)); + if (Q_UNLIKELY(importVersion != m_languageVersion)) + throw ErrorInfo(Tr::tr("Incompatible qbs version %1. This is qbs %2.").arg( + importVersionString, m_reader->builtins()->languageVersion()), + toCodeLocation(versionToken)); +} + +void ItemReaderASTVisitor::mergeItem(Item *dst, const Item *src, + const ItemReaderResult &baseFile) +{ + if (!src->typeName().isEmpty()) + dst->setTypeName(src->typeName()); + + int insertPos = 0; + for (int i = 0; i < src->m_children.count(); ++i) { + Item *child = src->m_children.at(i); + dst->m_children.insert(insertPos++, child); + child->m_parent = dst; + } + + for (QMap<QString, ValuePtr>::const_iterator it = src->m_properties.constBegin(); + it != src->m_properties.constEnd(); ++it) + { + ValuePtr &v = dst->m_properties[it.key()]; + if (v) { + if (v->type() == it.value()->type()) { + if (v->type() == Value::JSSourceValueType) { + JSSourceValuePtr sv = v.staticCast<JSSourceValue>(); + while (sv->baseValue()) + sv = sv->baseValue(); + const JSSourceValuePtr baseValue = it.value().staticCast<JSSourceValue>(); + sv->setBaseValue(baseValue); + for (QList<JSSourceValue::Alternative>::iterator it + = sv->m_alternatives.begin(); it != sv->m_alternatives.end(); ++it) { + JSSourceValue::Alternative &alternative = *it; + alternative.value->setBaseValue(baseValue); + } + } else if (v->type() == Value::ItemValueType) { + QBS_CHECK(v.staticCast<ItemValue>()->item()); + QBS_CHECK(it.value().staticCast<const ItemValue>()->item()); + mergeItem(v.staticCast<ItemValue>()->item(), + it.value().staticCast<const ItemValue>()->item(), + baseFile); + } else { + QBS_CHECK(!"unexpected value type"); + } + } + } else { + v = it.value(); + } + } + + for (QMap<QString, PropertyDeclaration>::const_iterator it + = src->m_propertyDeclarations.constBegin(); + it != src->m_propertyDeclarations.constEnd(); ++it) { + dst->m_propertyDeclarations[it.key()] = it.value(); + } + foreach (const JSSourceValuePtr &valueWithAlternatives, + baseFile.conditionalValuesPerScopeItem.value(src)) { + replaceConditionScopes(valueWithAlternatives, dst); + } +} + +void ItemReaderASTVisitor::ensureIdScope(const FileContextPtr &file) +{ + if (!file->m_idScope) { + file->m_idScope = Item::create(m_reader->m_pool); + file->m_idScope->m_typeName = QLatin1String("IdScope"); + } +} + +void ItemReaderASTVisitor::setupAlternatives(Item *item) +{ + QList<Item *>::iterator it = item->m_children.begin(); + while (it != item->m_children.end()) { + Item *child = *it; + if (child->typeName() == QLatin1String("Properties")) { + handlePropertiesBlock(item, child); + it = item->m_children.erase(it); + } else { + ++it; + } + } +} + +void ItemReaderASTVisitor::replaceConditionScopes(const JSSourceValuePtr &value, + Item *newScope) +{ + for (QList<JSSourceValue::Alternative>::iterator it + = value->m_alternatives.begin(); it != value->m_alternatives.end(); ++it) + it->conditionScopeItem = newScope; +} + +class PropertiesBlockConverter +{ +public: + PropertiesBlockConverter(const QString &condition, Item *propertiesBlockContainer, + const Item *propertiesBlock, + QSet<JSSourceValuePtr> *valuesWithAlternatives) + : m_propertiesBlockContainer(propertiesBlockContainer) + , m_propertiesBlock(propertiesBlock) + , m_valuesWithAlternatives(valuesWithAlternatives) + { + m_alternative.condition = condition; + m_alternative.conditionScopeItem = propertiesBlockContainer; + } + + void operator()() + { + apply(m_propertiesBlockContainer, m_propertiesBlock); + } + +private: + JSSourceValue::Alternative m_alternative; + Item *m_propertiesBlockContainer; + const Item *m_propertiesBlock; + QSet<JSSourceValuePtr> *m_valuesWithAlternatives; + + void apply(Item *a, const Item *b) + { + for (QMap<QString, ValuePtr>::const_iterator it = b->properties().constBegin(); + it != b->properties().constEnd(); ++it) { + if (b == m_propertiesBlock && it.key() == QLatin1String("condition")) + continue; + if (it.value()->type() == Value::ItemValueType) { + apply(a->itemProperty(it.key(), true)->item(), + it.value().staticCast<ItemValue>()->item()); + } else if (it.value()->type() == Value::JSSourceValueType) { + ValuePtr aval = a->property(it.key()); + if (Q_UNLIKELY(aval && aval->type() != Value::JSSourceValueType)) + throw ErrorInfo(Tr::tr("Incompatible value type in unconditional value at %1.").arg( + aval->location().toString())); + apply(it.key(), a, aval.staticCast<JSSourceValue>(), + it.value().staticCast<JSSourceValue>()); + } else { + QBS_CHECK(!"Unexpected value type in conditional value."); + } + } + } + + void apply(const QString &propertyName, Item *item, JSSourceValuePtr value, + const JSSourceValuePtr &conditionalValue) + { + QBS_ASSERT(!value || value->file() == conditionalValue->file(), return); + if (!value) { + value = JSSourceValue::create(); + value->setFile(conditionalValue->file()); + item->setProperty(propertyName, value); + value->setSourceCode(QLatin1String("undefined")); + } + m_alternative.value = conditionalValue; + value->addAlternative(m_alternative); + m_valuesWithAlternatives->insert(value); + } +}; + +void ItemReaderASTVisitor::handlePropertiesBlock(Item *item, const Item *block) +{ + ValuePtr value = block->property(QLatin1String("condition")); + if (Q_UNLIKELY(!value)) + throw ErrorInfo(Tr::tr("Properties.condition must be provided."), + block->location()); + if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) + throw ErrorInfo(Tr::tr("Properties.condition must be a value binding."), + block->location()); + JSSourceValuePtr srcval = value.staticCast<JSSourceValue>(); + const QString condition = srcval->sourceCode(); + PropertiesBlockConverter convertBlock(condition, item, block, + &m_readerResult->conditionalValuesPerScopeItem[item]); + convertBlock(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h new file mode 100644 index 000000000..409b5104e --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_ITEMREADERASTVISITOR_H +#define QBS_ITEMREADERASTVISITOR_H + +#include "importversion.h" +#include "item.h" +#include "filecontext.h" +#include <parser/qmljsastvisitor_p.h> +#include <QHash> + +namespace qbs { +namespace Internal { + +class ItemReader; +struct ItemReaderResult; + +class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor +{ +public: + ItemReaderASTVisitor(ItemReader *reader, ItemReaderResult *result); + ~ItemReaderASTVisitor(); + + void setFilePath(const QString &filePath) { m_filePath = filePath; } + void setSourceCode(const QString &sourceCode) { m_sourceCode = sourceCode; } + + bool visit(QbsQmlJS::AST::UiProgram *ast); + bool visit(QbsQmlJS::AST::UiImportList *uiImportList); + bool visit(QbsQmlJS::AST::UiObjectDefinition *ast); + bool visit(QbsQmlJS::AST::UiPublicMember *ast); + bool visit(QbsQmlJS::AST::UiScriptBinding *ast); + bool visit(QbsQmlJS::AST::FunctionDeclaration *ast); + +private: + bool visitStatement(QbsQmlJS::AST::Statement *statement); + CodeLocation toCodeLocation(QbsQmlJS::AST::SourceLocation location) const; + void checkDuplicateBinding(Item *item, const QStringList &bindingName, + const QbsQmlJS::AST::SourceLocation &sourceLocation); + Item *targetItemForBinding(Item *item, const QStringList &binding, + const CodeLocation &bindingLocation); + void checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const; + static void mergeItem(Item *dst, const Item *src, + const ItemReaderResult &baseFile); + void ensureIdScope(const FileContextPtr &file); + void setupAlternatives(Item *item); + static void replaceConditionScopes(const JSSourceValuePtr &value, Item *newScope); + void handlePropertiesBlock(Item *item, const Item *block); + void collectPrototypes(const QString &path, const QString &as); + bool addPrototype(const QString &fileName, const QString &filePath, const QString &as, + bool needsCheck); + + ItemReader *m_reader; + ItemReaderResult *m_readerResult; + const ImportVersion m_languageVersion; + QString m_filePath; + QString m_sourceCode; + FileContextPtr m_file; + QHash<QStringList, QString> m_typeNameToFile; + Item *m_item; + JSSourceValuePtr m_sourceValue; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADERASTVISITOR_H diff --git a/src/lib/corelib/language/jsimports.h b/src/lib/corelib/language/jsimports.h new file mode 100644 index 000000000..4e0ef9130 --- /dev/null +++ b/src/lib/corelib/language/jsimports.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_JSIMPORTS_H +#define QBS_JSIMPORTS_H + +#include <tools/codelocation.h> +#include <QSet> +#include <QStringList> + +namespace qbs { +namespace Internal { + +/** + * Represents JavaScript import of the form + * import 'fileOrDirectory' as scopeName + * + * There can be several filenames per scope + * if we import a whole directory. + */ +class JsImport +{ +public: + QString scopeName; + QStringList fileNames; + CodeLocation location; +}; + +typedef QList<JsImport> JsImports; + +inline bool operator==(const JsImport &jsi1, const JsImport &jsi2) +{ + return jsi1.scopeName == jsi2.scopeName && jsi1.fileNames.toSet() == jsi2.fileNames.toSet(); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_JSIMPORTS_H diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp new file mode 100644 index 000000000..92622787f --- /dev/null +++ b/src/lib/corelib/language/language.cpp @@ -0,0 +1,1124 @@ +/**************************************************************************** +** +** 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 "language.h" + +#include "artifactproperties.h" +#include "scriptengine.h" +#include <buildgraph/artifact.h> +#include <buildgraph/productbuilddata.h> +#include <buildgraph/projectbuilddata.h> +#include <buildgraph/rulegraph.h> // TODO: Move to language? +#include <jsextensions/jsextensions.h> +#include <logging/translator.h> +#include <tools/hostosinfo.h> +#include <tools/error.h> +#include <tools/propertyfinder.h> +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QDirIterator> +#include <QMap> +#include <QMutexLocker> +#include <QScriptValue> + +QT_BEGIN_NAMESPACE +inline QDataStream& operator>>(QDataStream &stream, qbs::Internal::JsImport &jsImport) +{ + stream >> jsImport.scopeName + >> jsImport.fileNames + >> jsImport.location; + return stream; +} + +inline QDataStream& operator<<(QDataStream &stream, const qbs::Internal::JsImport &jsImport) +{ + return stream << jsImport.scopeName + << jsImport.fileNames + << jsImport.location; +} +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +FileTagger::FileTagger(const QStringList &patterns, const FileTags &fileTags) + : m_fileTags(fileTags) +{ + setPatterns(patterns); +} + +void FileTagger::setPatterns(const QStringList &patterns) +{ + m_patterns.clear(); + foreach (const QString &pattern, patterns) { + QBS_CHECK(!pattern.isEmpty()); + m_patterns << QRegExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + } +} + +/*! + * \class FileTagger + * \brief The \c FileTagger class maps 1:1 to the respective item in a qbs source file. + */ +void FileTagger::load(PersistentPool &pool) +{ + setPatterns(pool.idLoadStringList()); + pool.stream() >> m_fileTags; +} + +void FileTagger::store(PersistentPool &pool) const +{ + QStringList patterns; + foreach (const QRegExp ®Exp, m_patterns) + patterns << regExp.pattern(); + pool.storeStringList(patterns); + pool.stream() << m_fileTags; +} + +/*! + * \class SourceArtifact + * \brief The \c SourceArtifact class represents a source file. + * Everything except the file path is inherited from the surrounding \c ResolvedGroup. + * (TODO: Not quite true. Artifacts in transformers will be generated by the transformer, but are + * still represented as source artifacts. We may or may not want to change this; if we do, + * SourceArtifact could simply have a back pointer to the group in addition to the file path.) + * \sa ResolvedGroup + */ +void SourceArtifact::load(PersistentPool &pool) +{ + pool.stream() >> absoluteFilePath; + pool.stream() >> fileTags; + pool.stream() >> overrideFileTags; + properties = pool.idLoadS<PropertyMapInternal>(); +} + +void SourceArtifact::store(PersistentPool &pool) const +{ + pool.stream() << absoluteFilePath; + pool.stream() << fileTags; + pool.stream() << overrideFileTags; + pool.store(properties); +} + +void SourceWildCards::load(PersistentPool &pool) +{ + prefix = pool.idLoadString(); + patterns = pool.idLoadStringList(); + excludePatterns = pool.idLoadStringList(); + pool.loadContainerS(files); +} + +void SourceWildCards::store(PersistentPool &pool) const +{ + pool.storeString(prefix); + pool.storeStringList(patterns); + pool.storeStringList(excludePatterns); + pool.storeContainer(files); +} + +/*! + * \class ResolvedGroup + * \brief The \c ResolvedGroup class corresponds to the Group item in a qbs source file. + */ + + /*! + * \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. + * 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. + */ +QList<SourceArtifactPtr> ResolvedGroup::allFiles() const +{ + QList<SourceArtifactPtr> lst = files; + if (wildcards) + lst.append(wildcards->files); + return lst; +} + +void ResolvedGroup::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + pool.stream() + >> enabled + >> location; + prefix = pool.idLoadString(); + pool.loadContainerS(files); + wildcards = pool.idLoadS<SourceWildCards>(); + properties = pool.idLoadS<PropertyMapInternal>(); + pool.stream() + >> fileTags + >> overrideTags; +} + +void ResolvedGroup::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.stream() + << enabled + << location; + pool.storeString(prefix); + pool.storeContainer(files); + pool.store(wildcards); + pool.store(properties); + pool.stream() + << fileTags + << overrideTags; +} + +/*! + * \class RuleArtifact + * \brief The \c RuleArtifact class represents an Artifact item encountered in the context + * of a Rule item. + * When applying the rule, one \c Artifact object will be constructed from each \c RuleArtifact + * object. During that process, the \c RuleArtifact's bindings are evaluated and the results + * are inserted into the corresponding \c Artifact's properties. + * \sa Rule + */ +void RuleArtifact::load(PersistentPool &pool) +{ + Q_UNUSED(pool); + pool.stream() + >> fileName + >> fileTags + >> alwaysUpdated; + + int i; + pool.stream() >> i; + bindings.clear(); + bindings.reserve(i); + Binding binding; + for (; --i >= 0;) { + pool.stream() >> binding.name >> binding.code >> binding.location; + bindings += binding; + } +} + +void RuleArtifact::store(PersistentPool &pool) const +{ + Q_UNUSED(pool); + pool.stream() + << fileName + << fileTags + << alwaysUpdated; + + pool.stream() << bindings.count(); + for (int i = bindings.count(); --i >= 0;) { + const Binding &binding = bindings.at(i); + pool.stream() << binding.name << binding.code << binding.location; + } +} + +void ResolvedFileContext::load(PersistentPool &pool) +{ + filePath = pool.idLoadString(); + jsExtensions = pool.idLoadStringList(); + pool.stream() >> jsImports; +} + +void ResolvedFileContext::store(PersistentPool &pool) const +{ + pool.storeString(filePath); + pool.storeStringList(jsExtensions); + pool.stream() << jsImports; +} + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ + if (&a == &b) + return true; + if (!!&a != !!&b) + return false; + return a.filePath == b.filePath + && a.jsExtensions == b.jsExtensions + && a.jsImports == b.jsImports; +} + + +/*! + * \class ScriptFunction + * \brief The \c ScriptFunction class represents the JavaScript code found in the "prepare" binding + * of a \c Rule or \c Transformer item in a qbs file. + * \sa Rule + * \sa ResolvedTransformer + */ + + /*! + * \variable ScriptFunction::script + * \brief The actual Javascript code, taken verbatim from the qbs source file. + */ + + /*! + * \variable ScriptFunction::location + * \brief The exact location of the script in the qbs source file. + * This is mostly needed for diagnostics. + */ + +void ScriptFunction::load(PersistentPool &pool) +{ + pool.stream() + >> sourceCode + >> argumentNames + >> location; + fileContext = pool.idLoadS<ResolvedFileContext>(); +} + +void ScriptFunction::store(PersistentPool &pool) const +{ + pool.stream() + << sourceCode + << argumentNames + << location; + pool.store(fileContext); +} + +bool operator==(const ScriptFunction &a, const ScriptFunction &b) +{ + if (&a == &b) + return true; + if (!!&a != !!&b) + return false; + return a.sourceCode == b.sourceCode + && a.location == b.location + && *a.fileContext == *b.fileContext; +} + +void ResolvedModule::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + moduleDependencies = pool.idLoadStringList(); + setupBuildEnvironmentScript = pool.idLoadS<ScriptFunction>(); + setupRunEnvironmentScript = pool.idLoadS<ScriptFunction>(); +} + +void ResolvedModule::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.storeStringList(moduleDependencies); + pool.store(setupBuildEnvironmentScript); + pool.store(setupRunEnvironmentScript); +} + +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2) +{ + if (&m1 == &m2) + return true; + if (!!&m1 != !!&m2) + return false; + return m1.name == m2.name + && m1.moduleDependencies.toSet() == m2.moduleDependencies.toSet() + && *m1.setupBuildEnvironmentScript == *m2.setupBuildEnvironmentScript + && *m1.setupRunEnvironmentScript == *m2.setupRunEnvironmentScript; +} + +static bool modulesAreEqual(const ResolvedModuleConstPtr &m1, const ResolvedModuleConstPtr &m2) +{ + return *m1 == *m2; +} + +QString Rule::toString() const +{ + return QLatin1Char('[') + inputs.toStringList().join(QLatin1String(",")) + QLatin1String(" -> ") + + outputFileTags().toStringList().join(QLatin1String(",")) + QLatin1Char(']'); +} + +FileTags Rule::outputFileTags() const +{ + FileTags result; + foreach (const RuleArtifactConstPtr &artifact, artifacts) + result.unite(artifact->fileTags); + return result; +} + +void Rule::load(PersistentPool &pool) +{ + script = pool.idLoadS<ScriptFunction>(); + module = pool.idLoadS<ResolvedModule>(); + pool.stream() + >> inputs + >> auxiliaryInputs + >> usings + >> explicitlyDependsOn + >> multiplex; + + pool.loadContainerS(artifacts); +} + +void Rule::store(PersistentPool &pool) const +{ + pool.store(script); + pool.store(module); + pool.stream() + << inputs + << auxiliaryInputs + << usings + << explicitlyDependsOn + << multiplex; + + pool.storeContainer(artifacts); +} + +ResolvedProduct::ResolvedProduct() + : enabled(true) +{ +} + +ResolvedProduct::~ResolvedProduct() +{ +} + +/*! + * \brief Returns all files of all groups as source artifacts. + * This includes the expanded list of wildcards. + */ +QList<SourceArtifactPtr> ResolvedProduct::allFiles() const +{ + QList<SourceArtifactPtr> lst; + foreach (const GroupConstPtr &group, groups) + lst += group->allFiles(); + return lst; +} + +/*! + * \brief Returns all files of all enabled groups as source artifacts. + * \sa ResolvedProduct::allFiles() + */ +QList<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const +{ + QList<SourceArtifactPtr> lst; + foreach (const GroupConstPtr &group, groups) { + if (group->enabled) + lst += group->allFiles(); + } + return lst; +} + +FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const +{ + FileTags result; + foreach (FileTaggerConstPtr tagger, fileTaggers) { + foreach (const QRegExp &pattern, tagger->patterns()) { + if (FileInfo::globMatches(pattern, fileName)) { + result.unite(tagger->fileTags()); + break; + } + } + } + return result; +} + +void ResolvedProduct::load(PersistentPool &pool) +{ + pool.stream() + >> enabled + >> fileTags + >> additionalFileTags + >> name + >> targetName + >> sourceDirectory + >> destinationDirectory + >> location; + properties = pool.idLoadS<PropertyMapInternal>(); + pool.loadContainerS(rules); + pool.loadContainerS(dependencies); + pool.loadContainerS(fileTaggers); + pool.loadContainerS(modules); + pool.loadContainerS(transformers); + pool.loadContainerS(groups); + pool.loadContainerS(artifactProperties); + buildData.reset(pool.idLoad<ProductBuildData>()); +} + +void ResolvedProduct::store(PersistentPool &pool) const +{ + pool.stream() + << enabled + << fileTags + << additionalFileTags + << name + << targetName + << sourceDirectory + << destinationDirectory + << location; + + pool.store(properties); + pool.storeContainer(rules); + pool.storeContainer(dependencies); + pool.storeContainer(fileTaggers); + pool.storeContainer(modules); + pool.storeContainer(transformers); + pool.storeContainer(groups); + pool.storeContainer(artifactProperties); + pool.store(buildData.data()); +} + +QList<const ResolvedModule*> topSortModules(const QHash<const ResolvedModule*, QList<const ResolvedModule*> > &moduleChildren, + const QList<const ResolvedModule*> &modules, + QSet<QString> &seenModuleNames) +{ + QList<const ResolvedModule*> result; + foreach (const ResolvedModule *m, modules) { + if (m->name.isNull()) + continue; + result.append(topSortModules(moduleChildren, moduleChildren.value(m), seenModuleNames)); + if (!seenModuleNames.contains(m->name)) { + seenModuleNames.insert(m->name); + result.append(m); + } + } + return result; +} + +static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getEnv expects 1 argument")); + QVariant v = engine->property("_qbs_procenv"); + QProcessEnvironment *procenv = reinterpret_cast<QProcessEnvironment*>(v.value<void*>()); + return engine->toScriptValue(procenv->value(context->argument(0).toString())); +} + +static QScriptValue js_putEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 2)) + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("putEnv expects 2 arguments")); + QVariant v = engine->property("_qbs_procenv"); + QProcessEnvironment *procenv = reinterpret_cast<QProcessEnvironment*>(v.value<void*>()); + procenv->insert(context->argument(0).toString(), context->argument(1).toString()); + return engine->undefinedValue(); +} + +enum EnvType +{ + BuildEnv, RunEnv +}; + +static QProcessEnvironment getProcessEnvironment(ScriptEngine *engine, EnvType envType, + const QList<ResolvedModuleConstPtr> &modules, + const PropertyMapConstPtr &productConfiguration, + TopLevelProject *project, + const QProcessEnvironment &env) +{ + QProcessEnvironment procenv = env; + + // Copy the environment of the platform configuration to the process environment. + const QVariantMap &platformEnv = project->platformEnvironment; + for (QVariantMap::const_iterator it = platformEnv.constBegin(); it != platformEnv.constEnd(); ++it) + procenv.insert(it.key(), it.value().toString()); + + QMap<QString, const ResolvedModule *> moduleMap; + foreach (const ResolvedModuleConstPtr &module, modules) + moduleMap.insert(module->name, module.data()); + + QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleParents; + QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleChildren; + foreach (ResolvedModuleConstPtr module, modules) { + foreach (const QString &moduleName, module->moduleDependencies) { + const ResolvedModule * const depmod = moduleMap.value(moduleName); + QBS_ASSERT(depmod, return env); + moduleParents[depmod].append(module.data()); + moduleChildren[module.data()].append(depmod); + } + } + + QList<const ResolvedModule *> rootModules; + foreach (ResolvedModuleConstPtr module, modules) { + if (moduleParents.value(module.data()).isEmpty()) { + QBS_ASSERT(module, return env); + rootModules.append(module.data()); + } + } + + { + QVariant v; + v.setValue<void*>(&procenv); + engine->setProperty("_qbs_procenv", v); + } + + engine->clearImportsCache(); + QScriptValue scope = engine->newObject(); + + const QScriptValue getEnvValue = engine->newFunction(js_getEnv, 1); + const QScriptValue putEnvValue = engine->newFunction(js_putEnv, 1); + + // TODO: Remove in 1.3 + scope.setProperty("getenv", getEnvValue); + scope.setProperty("putenv", putEnvValue); + + scope.setProperty("getEnv", getEnvValue); + scope.setProperty("putEnv", putEnvValue); + + QSet<QString> seenModuleNames; + QList<const ResolvedModule *> topSortedModules = topSortModules(moduleChildren, rootModules, seenModuleNames); + foreach (const ResolvedModule *module, topSortedModules) { + if ((envType == BuildEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty()) || + (envType == RunEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty() + && module->setupRunEnvironmentScript->sourceCode.isEmpty())) + continue; + + ScriptFunctionConstPtr setupScript; + if (envType == BuildEnv) { + setupScript = module->setupBuildEnvironmentScript; + } else { + if (!module->setupRunEnvironmentScript) + setupScript = module->setupBuildEnvironmentScript; + else + setupScript = module->setupRunEnvironmentScript; + } + + // handle imports + engine->import(setupScript->fileContext->jsImports, scope, scope); + JsExtensions::setupExtensions(setupScript->fileContext->jsExtensions, scope); + + // expose properties of direct module dependencies + QScriptValue scriptValue; + QVariantMap productModules = productConfiguration->value().value("modules").toMap(); + foreach (const ResolvedModule * const depmod, moduleChildren.value(module)) { + scriptValue = engine->newObject(); + QVariantMap moduleCfg = productModules.value(depmod->name).toMap(); + for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) + scriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); + scope.setProperty(depmod->name, scriptValue); + } + + // expose the module's properties + QVariantMap moduleCfg = productModules.value(module->name).toMap(); + for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) + scope.setProperty(it.key(), engine->toScriptValue(it.value())); + + QScriptContext *ctx = engine->currentContext(); + ctx->pushScope(scope); + scriptValue = engine->evaluate(setupScript->sourceCode + QLatin1String("()")); + ctx->popScope(); + if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) { + QString envTypeStr = (envType == BuildEnv ? "build" : "run"); + throw ErrorInfo(QString("Error while setting up %1 environment: %2").arg(envTypeStr, scriptValue.toString())); + } + } + + engine->setProperty("_qbs_procenv", QVariant()); + return procenv; +} + +void ResolvedProduct::setupBuildEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const +{ + if (!buildEnvironment.isEmpty()) + return; + + buildEnvironment = getProcessEnvironment(engine, BuildEnv, modules, properties, + topLevelProject(), env); +} + +void ResolvedProduct::setupRunEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const +{ + if (!runEnvironment.isEmpty()) + return; + + runEnvironment = getProcessEnvironment(engine, RunEnv, modules, properties, + topLevelProject(), env); +} + +const QList<RuleConstPtr> &ResolvedProduct::topSortedRules() const +{ + QBS_CHECK(buildData); + if (buildData->topSortedRules.isEmpty()) { + FileTags productFileTags = fileTags; + productFileTags += additionalFileTags; + RuleGraph ruleGraph; + ruleGraph.build(rules, productFileTags); +// ruleGraph.dump(); + buildData->topSortedRules = ruleGraph.topSorted(); +// int i=0; +// foreach (RulePtr r, m_topSortedRules) +// qDebug() << ++i << r->toString() << (void*)r.data(); + } + return buildData->topSortedRules; +} + +TopLevelProject *ResolvedProduct::topLevelProject() const +{ + return project->topLevelProject(); +} + +static QStringList findGeneratedFiles(const Artifact *base, const FileTags &tags) +{ + QStringList result; + foreach (const Artifact *parent, base->parents) { + if (tags.isEmpty() || parent->fileTags.matches(tags)) + result << parent->filePath(); + } + + if (result.isEmpty() || tags.isEmpty()) + foreach (const Artifact *parent, base->parents) + result << findGeneratedFiles(parent, tags); + + return result; +} + +QStringList ResolvedProduct::generatedFiles(const QString &baseFile, const FileTags &tags) const +{ + ProductBuildData *data = buildData.data(); + if (!data) + return QStringList(); + + foreach (const Artifact *art, data->artifacts) { + if (art->filePath() == baseFile) + return findGeneratedFiles(art, tags); + } + return QStringList(); +} + +ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(0) +{ +} + +TopLevelProject *ResolvedProject::topLevelProject() +{ + if (m_topLevelProject) + return m_topLevelProject; + TopLevelProject *tlp = dynamic_cast<TopLevelProject *>(this); + if (tlp) { + m_topLevelProject = tlp; + return m_topLevelProject; + } + QBS_CHECK(!parentProject.isNull()); + m_topLevelProject = parentProject->topLevelProject(); + return m_topLevelProject; +} + +QList<ResolvedProjectPtr> ResolvedProject::allSubProjects() const +{ + QList<ResolvedProjectPtr> projectList = subProjects; + foreach (const ResolvedProjectConstPtr &subProject, subProjects) + projectList << subProject->allSubProjects(); + return projectList; +} + +QList<ResolvedProductPtr> ResolvedProject::allProducts() const +{ + QList<ResolvedProductPtr> productList = products; + foreach (const ResolvedProjectConstPtr &subProject, subProjects) + productList << subProject->allProducts(); + return productList; +} + +void ResolvedProject::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + int count; + pool.stream() + >> location + >> enabled + >> count; + products.clear(); + products.reserve(count); + for (; --count >= 0;) { + ResolvedProductPtr rProduct = pool.idLoadS<ResolvedProduct>(); + if (rProduct->buildData) { + foreach (Artifact * const a, rProduct->buildData->artifacts) + a->product = rProduct; + } + products.append(rProduct); + } + + pool.stream() >> count; + subProjects.clear(); + subProjects.reserve(count); + for (; --count >= 0;) { + ResolvedProjectPtr p = pool.idLoadS<ResolvedProject>(); + subProjects.append(p); + } + + pool.stream() >> m_projectProperties; +} + +void ResolvedProject::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.stream() + << location + << enabled + << products.count(); + foreach (const ResolvedProductConstPtr &product, products) + pool.store(product); + pool.stream() << subProjects.count(); + foreach (const ResolvedProjectConstPtr &project, subProjects) + pool.store(project); + pool.stream() << m_projectProperties; +} + + +TopLevelProject::TopLevelProject() : locked(false) +{ +} + +TopLevelProject::~TopLevelProject() +{ +} + +QString TopLevelProject::deriveId(const QVariantMap &config) +{ + const QVariantMap qbsProperties = config.value(QLatin1String("qbs")).toMap(); + const QString buildVariant = qbsProperties.value(QLatin1String("buildVariant")).toString(); + const QString profile = qbsProperties.value(QLatin1String("profile")).toString(); + return profile + QLatin1Char('-') + buildVariant; +} + +QString TopLevelProject::deriveBuildDirectory(const QString &buildRoot, const QString &id) +{ + return buildRoot + QLatin1Char('/') + id; +} + +void TopLevelProject::setBuildConfiguration(const QVariantMap &config) +{ + m_buildConfiguration = config; + m_id = deriveId(config); +} + +QString TopLevelProject::buildGraphFilePath() const +{ + return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); +} + +void TopLevelProject::store(const Logger &logger) const +{ + // TODO: Use progress observer here. + + if (!buildData) + return; + if (!buildData->isDirty) { + logger.qbsDebug() << "[BG] build graph is unchanged in project " << id() << "."; + return; + } + const QString fileName = buildGraphFilePath(); + logger.qbsDebug() << "[BG] storing: " << fileName; + PersistentPool pool(logger); + PersistentPool::HeadData headData; + headData.projectConfig = buildConfiguration(); + pool.setHeadData(headData); + pool.setupWriteStream(fileName); + store(pool); + buildData->isDirty = false; +} + +void TopLevelProject::load(PersistentPool &pool) +{ + ResolvedProject::load(pool); + pool.stream() >> m_id; + pool.stream() >> platformEnvironment; + pool.stream() >> usedEnvironment; + pool.stream() >> fileExistsResults; + pool.stream() >> fileLastModifiedResults; + QHash<QString, QString> envHash; + pool.stream() >> envHash; + for (QHash<QString, QString>::const_iterator i = envHash.begin(); i != envHash.end(); ++i) + environment.insert(i.key(), i.value()); + pool.stream() >> buildSystemFiles; + buildData.reset(pool.idLoad<ProjectBuildData>()); + QBS_CHECK(buildData); + buildData->isDirty = false; +} + +void TopLevelProject::store(PersistentPool &pool) const +{ + ResolvedProject::store(pool); + pool.stream() << m_id; + pool.stream() << platformEnvironment << usedEnvironment << fileExistsResults + << fileLastModifiedResults; + QHash<QString, QString> envHash; + foreach (const QString &key, environment.keys()) + envHash.insert(key, environment.value(key)); + pool.stream() << envHash; + pool.stream() << buildSystemFiles; + pool.store(buildData.data()); +} + +/*! + * \class SourceWildCards + * \brief Objects of the \c SourceWildCards class result from giving wildcards in a + * \c ResolvedGroup's "files" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::prefix + * \brief Inherited from the \c ResolvedGroup + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::patterns + * \brief All elements of the \c ResolvedGroup's "files" binding that contain wildcards. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::excludePatterns + * \brief Corresponds to the \c ResolvedGroup's "excludeFiles" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::files + * \brief The \c SourceArtifacts resulting from the expanded list of matching files. + */ + +QSet<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QString &baseDir) const +{ + QSet<QString> files = expandPatterns(group, patterns, baseDir); + files -= expandPatterns(group, excludePatterns, baseDir); + return files; +} + +QSet<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QStringList &patterns, const QString &baseDir) const +{ + QSet<QString> files; + foreach (QString pattern, patterns) { + pattern.prepend(prefix); + pattern.replace('\\', '/'); + QStringList parts = pattern.split(QLatin1Char('/'), QString::SkipEmptyParts); + if (FileInfo::isAbsolute(pattern)) { + QString rootDir; + if (HostOsInfo::isWindowsHost()) { + rootDir = parts.takeFirst(); + if (!rootDir.endsWith(QLatin1Char('/'))) + rootDir.append(QLatin1Char('/')); + } else { + rootDir = QLatin1Char('/'); + } + expandPatterns(files, group, parts, rootDir); + } else { + expandPatterns(files, group, parts, baseDir); + } + } + + return files; +} + +void SourceWildCards::expandPatterns(QSet<QString> &result, const GroupConstPtr &group, + const QStringList &parts, + const QString &baseDir) const +{ + QStringList changed_parts = parts; + bool recursive = false; + QString part = changed_parts.takeFirst(); + + while (part == QLatin1String("**")) { + recursive = true; + + if (changed_parts.isEmpty()) { + part = QLatin1String("*"); + break; + } + + part = changed_parts.takeFirst(); + } + + const bool isDir = !changed_parts.isEmpty(); + + const QString &filePattern = part; + const QDirIterator::IteratorFlags itFlags = recursive + ? QDirIterator::Subdirectories + : QDirIterator::NoIteratorFlags; + QDir::Filters itFilters = isDir + ? QDir::Dirs + : QDir::Files; + + if (isDir && !FileInfo::isPattern(filePattern)) + itFilters |= QDir::Hidden; + if (filePattern != QLatin1String("..") && filePattern != QLatin1String(".")) + itFilters |= QDir::NoDotAndDotDot; + + QDirIterator it(baseDir, QStringList(filePattern), itFilters, itFlags); + while (it.hasNext()) { + const QString filePath = it.next(); + QBS_ASSERT(FileInfo(filePath).isDir() == isDir, break); + if (isDir) + expandPatterns(result, group, changed_parts, filePath); + else + result += QDir::cleanPath(filePath); + } +} + +void ResolvedTransformer::load(PersistentPool &pool) +{ + module = pool.idLoadS<ResolvedModule>(); + pool.stream() >> inputs; + pool.loadContainerS(outputs); + transform = pool.idLoadS<ScriptFunction>(); + pool.stream() >> explicitlyDependsOn; +} + +void ResolvedTransformer::store(PersistentPool &pool) const +{ + pool.store(module); + pool.stream() << inputs; + pool.storeContainer(outputs); + pool.store(transform); + pool.stream() << explicitlyDependsOn; +} + + +template<typename T> QMap<QString, T> listToMap(const QList<T> &list) +{ + QMap<QString, T> map; + foreach (const T &elem, list) + map.insert(keyFromElem(elem), elem); + return map; +} + +template<typename T> bool listsAreEqual(const QList<T> &l1, const QList<T> &l2) +{ + if (l1.count() != l2.count()) + return false; + const QMap<QString, T> map1 = listToMap(l1); + const QMap<QString, T> map2 = listToMap(l2); + foreach (const QString &key, map1.keys()) { + const T value2 = map2.value(key); + if (!value2) + return false; + if (*map1.value(key) != *value2) + return false; + } + return true; +} + +QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; } +QString keyFromElem(const ResolvedTransformerConstPtr &t) { return t->transform->sourceCode; } +QString keyFromElem(const RulePtr &r) { return r->toString(); } +QString keyFromElem(const ArtifactPropertiesPtr &ap) { + return ap->fileTagsFilter().toStringList().join(QLatin1String(",")); +} + +bool operator==(const SourceArtifact &sa1, const SourceArtifact &sa2) +{ + if (&sa1 == &sa2) + return true; + if (!!&sa1 != !!&sa2) + return false; + return sa1.absoluteFilePath == sa2.absoluteFilePath + && sa1.fileTags == sa2.fileTags + && sa1.overrideFileTags == sa2.overrideFileTags + && sa1.properties->value() == sa2.properties->value(); +} + +bool sourceArtifactListsAreEqual(const QList<SourceArtifactPtr> &l1, + const QList<SourceArtifactPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const ResolvedTransformer &t1, const ResolvedTransformer &t2) +{ + return modulesAreEqual(t1.module, t2.module) + && t1.inputs.toSet() == t2.inputs.toSet() + && sourceArtifactListsAreEqual(t1.outputs, t2.outputs) + && *t1.transform == *t2.transform + && t1.explicitlyDependsOn == t2.explicitlyDependsOn; +} + +bool transformerListsAreEqual(const QList<ResolvedTransformerConstPtr> &l1, + const QList<ResolvedTransformerConstPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const Rule &r1, const Rule &r2) +{ + if (&r1 == &r2) + return true; + if (!&r1 != !&r2) + return false; + if (r1.artifacts.count() != r2.artifacts.count()) + return false; + for (int i = 0; i < r1.artifacts.count(); ++i) { + if (*r1.artifacts.at(i) != *r2.artifacts.at(i)) + return false; + } + + return r1.module->name == r2.module->name + && r1.script->sourceCode == r2.script->sourceCode + && r1.inputs == r2.inputs + && r1.auxiliaryInputs == r2.auxiliaryInputs + && r1.usings == r2.usings + && r1.explicitlyDependsOn == r2.explicitlyDependsOn + && r1.multiplex == r2.multiplex; +} + +bool ruleListsAreEqual(const QList<RulePtr> &l1, const QList<RulePtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2) +{ + if (&a1 == &a2) + return true; + if (!&a1 != !&a2) + return false; + return a1.fileName == a2.fileName + && a1.fileTags == a2.fileTags + && a1.alwaysUpdated == a2.alwaysUpdated + && a1.bindings.toList().toSet() == a2.bindings.toList().toSet(); +} + +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) +{ + return b1.code == b2.code && b1.name == b2.name; +} + +uint qHash(const RuleArtifact::Binding &b) +{ + return qHash(qMakePair(b.code, b.name.join(QLatin1String(",")))); +} + +bool artifactPropertyListsAreEqual(const QList<ArtifactPropertiesPtr> &l1, + const QList<ArtifactPropertiesPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h new file mode 100644 index 000000000..d359686b1 --- /dev/null +++ b/src/lib/corelib/language/language.h @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_LANGUAGE_H +#define QBS_LANGUAGE_H + +#include "filetags.h" +#include "forward_decls.h" +#include "jsimports.h" +#include "propertymapinternal.h" +#include <buildgraph/forward_decls.h> +#include <tools/codelocation.h> +#include <tools/fileinfo.h> +#include <tools/persistentobject.h> +#include <tools/settings.h> +#include <tools/weakpointer.h> + +#include <QByteArray> +#include <QDataStream> +#include <QHash> +#include <QProcessEnvironment> +#include <QRegExp> +#include <QScriptProgram> +#include <QScriptValue> +#include <QScopedPointer> +#include <QSet> +#include <QString> +#include <QStringList> +#include <QVariant> + +QT_BEGIN_NAMESPACE +class QScriptEngine; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class BuildGraphLoader; + +class FileTagger : public PersistentObject +{ +public: + static FileTaggerPtr create() { return FileTaggerPtr(new FileTagger); } + static FileTaggerPtr create(const QStringList &patterns, const FileTags &fileTags) { + return FileTaggerPtr(new FileTagger(patterns, fileTags)); + } + + const QList<QRegExp> &patterns() const { return m_patterns; } + const FileTags &fileTags() const { return m_fileTags; } + +private: + FileTagger(const QStringList &patterns, const FileTags &fileTags); + FileTagger() {} + + void setPatterns(const QStringList &patterns); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + QList<QRegExp> m_patterns; + FileTags m_fileTags; +}; + +class RuleArtifact : public PersistentObject +{ +public: + static RuleArtifactPtr create() { return RuleArtifactPtr(new RuleArtifact); } + + QString fileName; + FileTags fileTags; + bool alwaysUpdated; + + class Binding + { + public: + QStringList name; + QString code; + CodeLocation location; + }; + + QVector<Binding> bindings; + +private: + RuleArtifact() + : alwaysUpdated(true) + {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +uint qHash(const RuleArtifact::Binding &b); +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2); +inline bool operator!=(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) { + return !(b1 == b2); +} +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2); +inline bool operator!=(const RuleArtifact &a1, const RuleArtifact &a2) { return !(a1 == a2); } + +class SourceArtifact : public PersistentObject +{ +public: + static SourceArtifactPtr create() { return SourceArtifactPtr(new SourceArtifact); } + + QString absoluteFilePath; + FileTags fileTags; + bool overrideFileTags; + PropertyMapPtr properties; + +private: + SourceArtifact() : overrideFileTags(true) {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const SourceArtifact &sa1, const SourceArtifact &sa2); +inline bool operator!=(const SourceArtifact &sa1, const SourceArtifact &sa2) { + return !(sa1 == sa2); +} + +bool sourceArtifactListsAreEqual(const QList<SourceArtifactPtr> &l1, + const QList<SourceArtifactPtr> &l2); + +class SourceWildCards : public PersistentObject +{ +public: + typedef QSharedPointer<SourceWildCards> Ptr; + typedef QSharedPointer<const SourceWildCards> ConstPtr; + + static Ptr create() { return Ptr(new SourceWildCards); } + + QSet<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir) const; + + // TODO: Use back pointer to Group instead? + QString prefix; + + QStringList patterns; + QStringList excludePatterns; + QList<SourceArtifactPtr> files; + +private: + SourceWildCards() {} + + QSet<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns, + const QString &baseDir) const; + void expandPatterns(QSet<QString> &result, const GroupConstPtr &group, + const QStringList &parts, const QString &baseDir) const; + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedGroup : public PersistentObject +{ +public: + static GroupPtr create() { return GroupPtr(new ResolvedGroup); } + + CodeLocation location; + + QString name; + bool enabled; + QString prefix; + QList<SourceArtifactPtr> files; + SourceWildCards::Ptr wildcards; + PropertyMapPtr properties; + FileTags fileTags; + bool overrideTags; + + QList<SourceArtifactPtr> allFiles() const; + +private: + ResolvedGroup() + : enabled(true) + {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedFileContext : public PersistentObject +{ +public: + static ResolvedFileContextPtr create() + { + return ResolvedFileContextPtr(new ResolvedFileContext); + } + + QString filePath; + QStringList jsExtensions; + JsImports jsImports; + +private: + ResolvedFileContext() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b); +inline bool operator!=(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ return !(a == b); } + +class ScriptFunction : public PersistentObject +{ +public: + static ScriptFunctionPtr create() { return ScriptFunctionPtr(new ScriptFunction); } + + QString sourceCode; + QStringList argumentNames; + CodeLocation location; + ResolvedFileContextConstPtr fileContext; + mutable QScriptValue scriptFunction; // cache + +private: + ScriptFunction() {} + + void load(PersistentPool &); + void store(PersistentPool &) const; +}; + +bool operator==(const ScriptFunction &a, const ScriptFunction &b); +inline bool operator!=(const ScriptFunction &a, const ScriptFunction &b) { return !(a == b); } + +class ResolvedModule : public PersistentObject +{ +public: + static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); } + + QString name; + QStringList moduleDependencies; + ScriptFunctionPtr setupBuildEnvironmentScript; + ScriptFunctionPtr setupRunEnvironmentScript; + +private: + ResolvedModule() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2); +inline bool operator!=(const ResolvedModule &m1, const ResolvedModule &m2) { return !(m1 == m2); } + +/** + * Per default each rule is a "non-multiplex rule". + * + * A "multiplex rule" creates one transformer that takes all + * input artifacts with the matching input file tag and creates + * one or more artifacts. (e.g. linker rule) + * + * A "non-multiplex rule" creates one transformer per matching input file. + */ +class Rule : public PersistentObject +{ +public: + static RulePtr create() { return RulePtr(new Rule); } + + ResolvedModuleConstPtr module; + ScriptFunctionPtr script; + FileTags inputs; + FileTags auxiliaryInputs; + FileTags usings; + FileTags explicitlyDependsOn; + bool multiplex; + QList<RuleArtifactPtr> artifacts; + + // members that we don't need to save + int ruleGraphId; + + QString toString() const; + FileTags outputFileTags() const; + +private: + Rule() : multiplex(false), ruleGraphId(-1) {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const Rule &r1, const Rule &r2); +inline bool operator!=(const Rule &r1, const Rule &r2) { return !(r1 == r2); } +bool ruleListsAreEqual(const QList<RulePtr> &l1, const QList<RulePtr> &l2); + +class ResolvedTransformer : public PersistentObject +{ +public: + static ResolvedTransformerPtr create() + { + return ResolvedTransformerPtr(new ResolvedTransformer); + } + + ResolvedModuleConstPtr module; + QStringList inputs; + QList<SourceArtifactPtr> outputs; + ScriptFunctionPtr transform; + FileTags explicitlyDependsOn; + +private: + ResolvedTransformer() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +bool operator==(const ResolvedTransformer &t1, const ResolvedTransformer &t2); +inline bool operator!=(const ResolvedTransformer &t1, const ResolvedTransformer &t2) { + return !(t1 == t2); +} +bool transformerListsAreEqual(const QList<ResolvedTransformerConstPtr> &l1, + const QList<ResolvedTransformerConstPtr> &l2); + +class TopLevelProject; +class ScriptEngine; + +class ResolvedProduct : public PersistentObject +{ +public: + static ResolvedProductPtr create() { return ResolvedProductPtr(new ResolvedProduct); } + + ~ResolvedProduct(); + + bool enabled; + FileTags fileTags; + FileTags additionalFileTags; + QString name; + QString targetName; + QString sourceDirectory; + QString destinationDirectory; + CodeLocation location; + WeakPointer<ResolvedProject> project; + PropertyMapPtr properties; + QSet<RulePtr> rules; + QSet<ResolvedProductPtr> dependencies; + QList<FileTaggerConstPtr> fileTaggers; + QList<ResolvedModuleConstPtr> modules; + QList<ResolvedTransformerConstPtr> transformers; + QList<GroupPtr> groups; + QList<ArtifactPropertiesPtr> artifactProperties; + QScopedPointer<ProductBuildData> buildData; + + mutable QProcessEnvironment buildEnvironment; // must not be saved + mutable QProcessEnvironment runEnvironment; // must not be saved + QHash<QString, QString> executablePathCache; + + QList<SourceArtifactPtr> allFiles() const; + QList<SourceArtifactPtr> allEnabledFiles() const; + FileTags fileTagsForFileName(const QString &fileName) const; + void setupBuildEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; + void setupRunEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; + + const QList<RuleConstPtr> &topSortedRules() const; + TopLevelProject *topLevelProject() const; + + QStringList generatedFiles(const QString &baseFile, const FileTags &tags) const; + +private: + ResolvedProduct(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedProject : public PersistentObject +{ +public: + static ResolvedProjectPtr create() { return ResolvedProjectPtr(new ResolvedProject); } + + QString name; + CodeLocation location; + bool enabled; + QList<ResolvedProductPtr> products; + QList<ResolvedProjectPtr> subProjects; + WeakPointer<ResolvedProject> parentProject; + + void setProjectProperties(const QVariantMap &config) { m_projectProperties = config; } + const QVariantMap &projectProperties() const { return m_projectProperties; } + + TopLevelProject *topLevelProject(); + QList<ResolvedProjectPtr> allSubProjects() const; + QList<ResolvedProductPtr> allProducts() const; + +protected: + ResolvedProject(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +private: + QVariantMap m_projectProperties; + TopLevelProject *m_topLevelProject; +}; + +class TopLevelProject : public ResolvedProject +{ + friend class BuildGraphLoader; +public: + ~TopLevelProject(); + + static TopLevelProjectPtr create() { return TopLevelProjectPtr(new TopLevelProject); } + + static QString deriveId(const QVariantMap &config); + static QString deriveBuildDirectory(const QString &buildRoot, const QString &id); + + QString buildDirectory; // Not saved + QProcessEnvironment environment; + QVariantMap platformEnvironment; + QHash<QString, QString> usedEnvironment; // Environment variables requested by the project while resolving. + QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()". + QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()". + QScopedPointer<ProjectBuildData> buildData; + bool locked; + + QSet<QString> buildSystemFiles; + + void setBuildConfiguration(const QVariantMap &config); + const QVariantMap &buildConfiguration() const { return m_buildConfiguration; } + QString id() const { return m_id; } + + QString buildGraphFilePath() const; + void store(const Logger &logger) const; + +private: + TopLevelProject(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + + QString m_id; + QVariantMap m_buildConfiguration; +}; + +bool artifactPropertyListsAreEqual(const QList<ArtifactPropertiesPtr> &l1, + const QList<ArtifactPropertiesPtr> &l2); + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::JsImport, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::RuleArtifact::Binding, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_LANGUAGE_H diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri new file mode 100644 index 000000000..09d7e9a1b --- /dev/null +++ b/src/lib/corelib/language/language.pri @@ -0,0 +1,70 @@ +HEADERS += \ + $$PWD/artifactproperties.h \ + $$PWD/asttools.h \ + $$PWD/builtindeclarations.h \ + $$PWD/builtinvalue.h \ + $$PWD/evaluationdata.h \ + $$PWD/evaluator.h \ + $$PWD/evaluatorscriptclass.h \ + $$PWD/filecontext.h \ + $$PWD/filetags.h \ + $$PWD/forward_decls.h \ + $$PWD/functiondeclaration.h \ + $$PWD/identifiersearch.h \ + $$PWD/importversion.h \ + $$PWD/item.h \ + $$PWD/itemdeclaration.h \ + $$PWD/itemobserver.h \ + $$PWD/itempool.h \ + $$PWD/itemreader.h \ + $$PWD/itemreaderastvisitor.h \ + $$PWD/jsimports.h \ + $$PWD/language.h \ + $$PWD/loader.h \ + $$PWD/moduleloader.h \ + $$PWD/preparescriptobserver.h \ + $$PWD/projectresolver.h \ + $$PWD/property.h \ + $$PWD/propertydeclaration.h \ + $$PWD/propertymapinternal.h \ + $$PWD/scriptengine.h \ + $$PWD/scriptpropertyobserver.h \ + $$PWD/value.h + +SOURCES += \ + $$PWD/artifactproperties.cpp \ + $$PWD/asttools.cpp \ + $$PWD/builtindeclarations.cpp \ + $$PWD/builtinvalue.cpp \ + $$PWD/evaluator.cpp \ + $$PWD/evaluatorscriptclass.cpp \ + $$PWD/filecontext.cpp \ + $$PWD/filetags.cpp \ + $$PWD/identifiersearch.cpp \ + $$PWD/importversion.cpp \ + $$PWD/item.cpp \ + $$PWD/itemdeclaration.cpp \ + $$PWD/itempool.cpp \ + $$PWD/itemreader.cpp \ + $$PWD/itemreaderastvisitor.cpp \ + $$PWD/language.cpp \ + $$PWD/loader.cpp \ + $$PWD/moduleloader.cpp \ + $$PWD/preparescriptobserver.cpp \ + $$PWD/projectresolver.cpp \ + $$PWD/propertydeclaration.cpp \ + $$PWD/propertymapinternal.cpp \ + $$PWD/scriptengine.cpp \ + $$PWD/value.cpp + +all_tests { + HEADERS += $$PWD/tst_language.h + SOURCES += $$PWD/tst_language.cpp + OTHER_FILES += $$PWD/testdata/* +} + +!qbs_no_dev_install { + language_headers.files = $$PWD/forward_decls.h + language_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/language + INSTALLS += language_headers +} diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp new file mode 100644 index 000000000..8a3f3c98f --- /dev/null +++ b/src/lib/corelib/language/loader.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 "loader.h" + +#include "builtindeclarations.h" +#include "item.h" +#include "moduleloader.h" +#include "projectresolver.h" +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> +#include <tools/setupprojectparameters.h> + +#include <QDir> + +namespace qbs { +namespace Internal { + +Loader::Loader(ScriptEngine *engine, const Logger &logger) + : m_logger(logger) + , m_progressObserver(0) + , m_builtins(new BuiltinDeclarations) + , m_moduleLoader(new ModuleLoader(engine, m_builtins, logger)) + , m_projectResolver(new ProjectResolver(m_moduleLoader, m_builtins, logger)) + , m_engine(engine) +{ +} + +Loader::~Loader() +{ + delete m_projectResolver; + delete m_moduleLoader; + delete m_builtins; +} + +void Loader::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; + m_moduleLoader->setProgressObserver(observer); + m_projectResolver->setProgressObserver(observer); +} + +void Loader::setSearchPaths(const QStringList &_searchPaths) +{ + QStringList searchPaths; + foreach (const QString &searchPath, _searchPaths) { + if (!FileInfo::exists(searchPath)) { + m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.") + .arg(QDir::toNativeSeparators(searchPath)); + } else { + searchPaths += searchPath; + } + } + + m_moduleLoader->setSearchPaths(searchPaths); +} + +TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters ¶meters) +{ + QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); + + m_engine->setEnvironment(parameters.environment()); + m_engine->clearExceptions(); + + // At this point, we cannot set a sensible total effort, because we know nothing about + // the project yet. That's why we use a placeholder here, so the user at least + // sees that an operation is starting. The real total effort will be set later when + // we have enough information. + if (m_progressObserver) { + m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1") + .arg(TopLevelProject::deriveId(parameters.buildConfigurationTree())), 1); + } + + ModuleLoaderResult loadResult + = m_moduleLoader->load(parameters.projectFilePath(), + parameters.overriddenValuesTree(), + parameters.buildConfigurationTree(), + true); + const TopLevelProjectPtr project = m_projectResolver->resolve(loadResult, parameters); + + // E.g. if the top-level project is disabled. + if (m_progressObserver) + m_progressObserver->setFinished(); + + return project; +} + +QByteArray Loader::qmlTypeInfo() +{ + return m_builtins->qmlTypeInfo(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/language/loader.h new file mode 100644 index 000000000..64e3a2ad6 --- /dev/null +++ b/src/lib/corelib/language/loader.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#ifndef QBS_LOADER_H +#define QBS_LOADER_H + +#include "forward_decls.h" +#include <tools/qbs_export.h> +#include <logging/logger.h> + +#include <QStringList> + +namespace qbs { +class Settings; +class SetupProjectParameters; +namespace Internal { +class BuiltinDeclarations; +class Logger; +class ModuleLoader; +class ProgressObserver; +class ScriptEngine; +class ProjectResolver; + +class QBS_EXPORT Loader // FIXME: Exported for qbs-qmltypes +{ +public: + Loader(ScriptEngine *engine, const Logger &logger); + ~Loader(); + + void setProgressObserver(ProgressObserver *observer); + void setSearchPaths(const QStringList &searchPaths); + TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); + QByteArray qmlTypeInfo(); + +private: + Logger m_logger; + ProgressObserver *m_progressObserver; + BuiltinDeclarations *m_builtins; + ModuleLoader *m_moduleLoader; + ProjectResolver *m_projectResolver; + ScriptEngine * const m_engine; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOADER_H diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp new file mode 100644 index 000000000..a0c06bb61 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.cpp @@ -0,0 +1,1143 @@ +/**************************************************************************** +** +** 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 "moduleloader.h" + +#include "builtindeclarations.h" +#include "builtinvalue.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "itemreader.h" +#include "scriptengine.h" +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/hostosinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> + +#include <QDebug> +#include <QDir> +#include <QDirIterator> + +namespace qbs { +namespace Internal { + +class ModuleLoader::ItemModuleList : public QList<Item::Module> {}; + +const QString moduleSearchSubDir = QLatin1String("modules"); + +ModuleLoader::ModuleLoader(ScriptEngine *engine, BuiltinDeclarations *builtins, + const Logger &logger) + : m_engine(engine) + , m_pool(0) + , m_logger(logger) + , m_progressObserver(0) + , m_reader(new ItemReader(builtins, logger)) + , m_evaluator(new Evaluator(engine, logger)) +{ +} + +ModuleLoader::~ModuleLoader() +{ + delete m_evaluator; + delete m_reader; +} + +void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) +{ + m_progressObserver = progressObserver; +} + +static void addExtraModuleSearchPath(QStringList &list, const QString &searchPath) +{ + list += FileInfo::resolvePath(searchPath, moduleSearchSubDir); +} + +void ModuleLoader::setSearchPaths(const QStringList &searchPaths) +{ + m_reader->setSearchPaths(searchPaths); + + m_moduleDirListCache.clear(); + m_moduleSearchPaths.clear(); + foreach (const QString &path, searchPaths) + addExtraModuleSearchPath(m_moduleSearchPaths, path); +} + +ModuleLoaderResult ModuleLoader::load(const QString &filePath, + const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, + bool wrapWithProjectItem) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] load" << filePath; + m_overriddenProperties = overriddenProperties; + m_buildConfigProperties = buildConfigProperties; + m_validItemPropertyNamesPerItem.clear(); + m_disabledItems.clear(); + + ModuleLoaderResult result; + m_pool = result.itemPool.data(); + m_reader->setPool(m_pool); + + Item *root = m_reader->readFile(filePath); + if (!root) + return ModuleLoaderResult(); + + if (wrapWithProjectItem && root->typeName() != QLatin1String("Project")) + root = wrapWithProject(root); + + handleProject(&result, root, QSet<QString>() << QDir::cleanPath(filePath)); + result.root = root; + result.qbsFiles = m_reader->filesRead(); + return result; +} + +class PropertyDeclarationCheck : public ValueHandler +{ + const QHash<Item *, QSet<QString> > &m_validItemPropertyNamesPerItem; + const QSet<Item *> &m_disabledItems; + Item *m_parentItem; + QString m_currentName; +public: + PropertyDeclarationCheck(const QHash<Item *, QSet<QString> > &validItemPropertyNamesPerItem, + const QSet<Item *> &disabledItems) + : m_validItemPropertyNamesPerItem(validItemPropertyNamesPerItem) + , m_disabledItems(disabledItems) + , m_parentItem(0) + { + } + + void operator()(Item *item) + { + handleItem(item); + } + +private: + void handle(JSSourceValue *value) + { + if (!m_parentItem->propertyDeclaration(m_currentName).isValid()) { + throw ErrorInfo(Tr::tr("Property '%1' is not declared.").arg(m_currentName), + value->location()); + } + } + + void handle(ItemValue *value) + { + if (!value->item()->isModuleInstance() + && !m_validItemPropertyNamesPerItem.value(m_parentItem).contains(m_currentName) + && m_parentItem->file() + && !m_parentItem->file()->idScope()->hasProperty(m_currentName)) { + throw ErrorInfo(Tr::tr("Item '%1' is not declared. " + "Did you forget to add a Depends item?").arg(m_currentName), + value->location().isValid() ? value->location() + : m_parentItem->location()); + } + + handleItem(value->item()); + } + + void handleItem(Item *item) + { + if (m_disabledItems.contains(item) || item->typeName() == QLatin1String("SubProject")) + return; + + Item *oldParentItem = m_parentItem; + for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) { + if (item->propertyDeclaration(it.key()).isValid()) + continue; + m_currentName = it.key(); + m_parentItem = item; + it.value()->apply(this); + } + m_parentItem = oldParentItem; + foreach (Item *child, item->children()) + handleItem(child); + } + + void handle(VariantValue *) { /* only created internally - no need to check */ } + void handle(BuiltinValue *) { /* only created internally - no need to check */ } +}; + +void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, + const QSet<QString> &referencedFilePaths) +{ + if (!checkItemCondition(item)) + return; + ProjectContext projectContext; + projectContext.result = loadResult; + projectContext.localModuleSearchPath = FileInfo::resolvePath(item->file()->dirPath(), + moduleSearchSubDir); + + ProductContext dummyProductContext; + dummyProductContext.project = &projectContext; + loadBaseModule(&dummyProductContext, item); + overrideItemProperties(item, QLatin1String("project"), m_overriddenProperties); + + projectContext.extraSearchPaths = readExtraSearchPaths(item); + m_reader->pushExtraSearchPaths(projectContext.extraSearchPaths); + projectContext.item = item; + ItemValuePtr itemValue = ItemValue::create(item); + projectContext.scope = Item::create(m_pool); + projectContext.scope->setProperty(QLatin1String("project"), itemValue); + + foreach (Item *child, item->children()) { + child->setScope(projectContext.scope); + if (child->typeName() == QLatin1String("Product")) { + handleProduct(&projectContext, child); + } else if (child->typeName() == QLatin1String("SubProject")) { + handleSubProject(&projectContext, child, referencedFilePaths); + } else if (child->typeName() == QLatin1String("Project")) { + copyProperties(item, child); + handleProject(loadResult, child, referencedFilePaths); + } + } + + const QString projectFileDirPath = FileInfo::path(item->file()->filePath()); + const QStringList refs = m_evaluator->stringListValue(item, QLatin1String("references")); + foreach (const QString &filePath, refs) { + QString absReferencePath = FileInfo::resolvePath(projectFileDirPath, filePath); + if (FileInfo(absReferencePath).isDir()) { + QString qbsFilePath; + QDirIterator dit(absReferencePath, QStringList(QLatin1String("*.qbs"))); + while (dit.hasNext()) { + if (!qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " + "qbs file.").arg(absReferencePath), + item->property(QLatin1String("references"))->location()); + } + qbsFilePath = dit.next(); + } + if (qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") + .arg(absReferencePath), + item->property(QLatin1String("references"))->location()); + } + absReferencePath = qbsFilePath; + } + if (referencedFilePaths.contains(absReferencePath)) + throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(filePath), + item->property(QLatin1String("references"))->location()); + Item *subItem = m_reader->readFile(absReferencePath); + subItem->setScope(projectContext.scope); + subItem->setParent(projectContext.item); + QList<Item *> projectChildren = projectContext.item->children(); + projectChildren += subItem; + projectContext.item->setChildren(projectChildren); + if (subItem->typeName() == "Product") { + handleProduct(&projectContext, subItem); + } else if (subItem->typeName() == "Project") { + copyProperties(item, subItem); + handleProject(loadResult, subItem, + QSet<QString>(referencedFilePaths) << absReferencePath); + } else { + throw ErrorInfo(Tr::tr("The top-level item of a file in a \"references\" list must be " + "a Product or a Project, but it is \"%1\".").arg(subItem->typeName()), + subItem->location()); + } + } + + checkItemTypes(item); + + PropertyDeclarationCheck check(m_validItemPropertyNamesPerItem, m_disabledItems); + check(item); + + m_reader->popExtraSearchPaths(); +} + +void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) +{ + checkCancelation(); + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] handleProduct " << item->file()->filePath(); + + ProductContext productContext; + productContext.project = projectContext; + bool extraSearchPathsSet = false; + const QStringList extraSearchPaths = readExtraSearchPaths(item, &extraSearchPathsSet); + if (extraSearchPathsSet) { // Inherit from project if not set in product itself. + productContext.extraSearchPaths = extraSearchPaths; + m_reader->pushExtraSearchPaths(extraSearchPaths); + } else { + productContext.extraSearchPaths = projectContext->extraSearchPaths; + } + productContext.item = item; + ItemValuePtr itemValue = ItemValue::create(item); + productContext.scope = Item::create(m_pool); + productContext.scope->setProperty(QLatin1String("product"), itemValue); + productContext.scope->setScope(projectContext->scope); + DependsContext dependsContext; + dependsContext.product = &productContext; + dependsContext.productDependencies = &productContext.info.usedProducts; + setScopeForDescendants(item, productContext.scope); + resolveDependencies(&dependsContext, item); + if (!checkItemCondition(item)) + return; + createAdditionalModuleInstancesInProduct(&productContext); + + foreach (Item *child, item->children()) { + if (child->typeName() == QLatin1String("Group")) + handleGroup(&productContext, child); + else if (child->typeName() == QLatin1String("Artifact")) + handleArtifact(&productContext, child); + else if (child->typeName() == QLatin1String("Export")) + deferExportItem(&productContext, child); + else if (child->typeName() == QLatin1String("Probe")) + resolveProbe(item, child); + } + + mergeExportItems(&productContext); + projectContext->result->productInfos.insert(item, productContext.info); + if (extraSearchPathsSet) + m_reader->popExtraSearchPaths(); +} + +void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *item, + const QSet<QString> &referencedFilePaths) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] handleSubProject " << item->file()->filePath(); + + Item * const propertiesItem = item->child(QLatin1String("Properties")); + bool subProjectEnabled = true; + if (propertiesItem) + subProjectEnabled = checkItemCondition(propertiesItem); + if (!subProjectEnabled) + return; + + const QString projectFileDirPath = FileInfo::path(item->file()->filePath()); + const QString relativeFilePath = m_evaluator->property(item, + QLatin1String("filePath")).toString(); + QString subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), item->location()); + Item *loadedItem = m_reader->readFile(subProjectFilePath); + if (loadedItem->typeName() == QLatin1String("Product")) + loadedItem = wrapWithProject(loadedItem); + const bool inheritProperties + = m_evaluator->boolValue(item, QLatin1String("inheritProperties"), true); + + if (inheritProperties) + copyProperties(item->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin(); + it != overriddenProperties.constEnd(); ++it) { + loadedItem->setProperty(it.key(), overriddenProperties.value(it.key())); + } + } + + if (loadedItem->typeName() != QLatin1String("Project")) { + ErrorInfo error; + error.append(Tr::tr("Expected Project item, but encountered '%1'.") + .arg(loadedItem->typeName()), loadedItem->location()); + const ValuePtr &filePathProperty = item->properties().value(QLatin1String("filePath")); + error.append(Tr::tr("The problematic file was referenced from here."), + filePathProperty->location()); + throw error; + } + + Item::addChild(item, loadedItem); + item->setScope(projectContext->scope); + handleProject(projectContext->result, loadedItem, + QSet<QString>(referencedFilePaths) << subProjectFilePath); +} + +void ModuleLoader::createAdditionalModuleInstancesInProduct(ProductContext *productContext) +{ + Item::Modules modulesToCheck; + QSet<QStringList> modulesInProduct; + foreach (const Item::Module &module, productContext->item->modules()) { + modulesInProduct += module.name; + modulesToCheck += module.item->prototype()->modules(); + } + while (!modulesToCheck.isEmpty()) { + Item::Module module = modulesToCheck.takeFirst(); + if (modulesInProduct.contains(module.name)) + continue; + modulesInProduct += module.name; + modulesToCheck += module.item->prototype()->modules(); + Item *instance = Item::create(m_pool); + instantiateModule(productContext, productContext->item, instance, module.item->prototype(), + module.name); + module.item = instance; + productContext->item->modules().append(module); + } +} + +void ModuleLoader::handleGroup(ProductContext *productContext, Item *item) +{ + checkCancelation(); + propagateModulesFromProduct(productContext, item); + checkItemCondition(item); +} + +void ModuleLoader::handleArtifact(ProductContext *productContext, Item *item) +{ + checkCancelation(); + propagateModulesFromProduct(productContext, item); +} + +void ModuleLoader::deferExportItem(ModuleLoader::ProductContext *productContext, Item *item) +{ + productContext->exportItems.append(item); +} + +static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) +{ + if (value->type() == Value::ItemValueType) { + Item *valueItem = value.staticCast<ItemValue>()->item(); + if (!valueItem) + return; + Item *subItem = dst->itemProperty(name, true)->item(); + for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin(); + it != valueItem->properties().constEnd(); ++it) + mergeProperty(subItem, it.key(), it.value()); + } else { + dst->setProperty(name, value); + } +} + +void ModuleLoader::mergeExportItems(ModuleLoader::ProductContext *productContext) +{ + Item *merged = Item::create(productContext->item->pool()); + merged->setTypeName(QLatin1String("Export")); + QSet<Item *> exportItems; + foreach (Item *exportItem, productContext->exportItems) { + checkCancelation(); + if (Q_UNLIKELY(productContext->filesWithExportItem.contains(exportItem->file()))) + throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), + exportItem->location()); + merged->setLocation(exportItem->location()); + productContext->filesWithExportItem += exportItem->file(); + exportItems.insert(exportItem); + foreach (Item *child, exportItem->children()) + Item::addChild(merged, child); + for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); + it != exportItem->properties().constEnd(); ++it) { + mergeProperty(merged, it.key(), it.value()); + } + } + + QList<Item *> children = productContext->item->children(); + for (int i = 0; i < children.count();) { + if (exportItems.contains(children.at(i))) + children.removeAt(i); + else + ++i; + } + productContext->item->setChildren(children); + Item::addChild(productContext->item, merged); + + DependsContext dependsContext; + dependsContext.product = productContext; + dependsContext.productDependencies = &productContext->info.usedProductsFromExportItem; + resolveDependencies(&dependsContext, merged); +} + +void ModuleLoader::propagateModulesFromProduct(ProductContext *productContext, Item *item) +{ + for (Item::Modules::const_iterator it = productContext->item->modules().constBegin(); + it != productContext->item->modules().constEnd(); ++it) + { + Item::Module m = *it; + Item *targetItem = moduleInstanceItem(item, m.name); + targetItem->setPrototype(m.item); + targetItem->setModuleInstanceFlag(true); + targetItem->setScope(m.item->scope()); + targetItem->modules() = m.item->modules(); + + // "parent" should point to the group/artifact parent + targetItem->setParent(item->parent()); + + // the outer item of a module is the product's instance of it + targetItem->setOuterItem(m.item); // ### Is this always the same as the scope item? + + m.item = targetItem; + item->modules() += m; + } +} + +void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item) +{ + loadBaseModule(dependsContext->product, item); + + // Resolve all Depends items. + typedef QHash<Item *, ItemModuleList> ModuleHash; + ModuleHash loadedModules; + ProductDependencyResults productDependencies; + foreach (Item *child, item->children()) + if (child->typeName() == QLatin1String("Depends")) + resolveDependsItem(dependsContext, item, child, &loadedModules[child], + &productDependencies); + + QSet<QString> loadedModuleNames; + foreach (const ItemModuleList &moduleList, loadedModules) { + foreach (const Item::Module &module, moduleList) { + const QString fullName = fullModuleName(module.name); + if (loadedModuleNames.contains(fullName)) { + m_logger.printWarning(ErrorInfo(Tr::tr("Duplicate dependency '%1'.").arg(fullName), + item->location())); + continue; + } + loadedModuleNames.insert(fullName); + item->modules() += module; + resolveProbes(module.item); + } + } + + foreach (const ProductDependencyResult &pd, productDependencies) + dependsContext->productDependencies->append(pd.second); +} + +void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *item, + Item *dependsItem, ItemModuleList *moduleResults, + ProductDependencyResults *productResults) +{ + checkCancelation(); + if (!checkItemCondition(dependsItem)) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "Depends item disabled, ignoring."; + return; + } + const QString name = m_evaluator->property(dependsItem, "name").toString(); + const QStringList nameParts = name.split('.'); + if (Q_UNLIKELY(nameParts.count() > 2)) { + QString msg = Tr::tr("There cannot be more than one dot in a module name."); + throw ErrorInfo(msg, dependsItem->location()); + } + + QString superModuleName; + QStringList submodules = m_evaluator->stringListValue(dependsItem, QLatin1String("submodules")); + if (nameParts.count() == 2) { + if (Q_UNLIKELY(!submodules.isEmpty())) + throw ErrorInfo(Tr::tr("Depends.submodules cannot be used if name contains a dot."), + dependsItem->location()); + superModuleName = nameParts.first(); + submodules += nameParts.last(); + } + if (Q_UNLIKELY(submodules.count() > 1 && !dependsItem->id().isEmpty())) { + QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); + throw ErrorInfo(msg, dependsItem->location()); + } + if (superModuleName.isEmpty()) { + if (submodules.isEmpty()) + submodules += name; + else + superModuleName = name; + } + + QStringList moduleNames; + foreach (const QString &submoduleName, submodules) + moduleNames += submoduleName; + + Item::Module result; + foreach (const QString &moduleName, moduleNames) { + QStringList qualifiedModuleName(moduleName); + if (!superModuleName.isEmpty()) + qualifiedModuleName.prepend(superModuleName); + const bool isRequired + = m_evaluator->boolValue(dependsItem, QLatin1String("required")); + Item *moduleItem = loadModule(dependsContext->product, item, dependsItem->location(), + dependsItem->id(), qualifiedModuleName, false, isRequired); + if (moduleItem) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "module loaded: " << fullModuleName(qualifiedModuleName); + result.name = qualifiedModuleName; + result.item = moduleItem; + moduleResults->append(result); + } else { + ModuleLoaderResult::ProductInfo::Dependency dependency; + dependency.name = moduleName; + dependency.required = m_evaluator->property(item, QLatin1String("required")).toBool(); + dependency.failureMessage + = m_evaluator->property(item, QLatin1String("failureMessage")).toString(); + productResults->append(ProductDependencyResult(dependsItem, dependency)); + } + } +} + +Item *ModuleLoader::moduleInstanceItem(Item *item, const QStringList &moduleName) +{ + Item *instance = item; + for (int i = 0; i < moduleName.count(); ++i) { + const QString &moduleNameSegment = moduleName.at(i); + m_validItemPropertyNamesPerItem[instance].insert(moduleNameSegment); + bool createNewItem = true; + const ValuePtr v = instance->properties().value(moduleName.at(i)); + if (v && v->type() == Value::ItemValueType) { + const ItemValuePtr iv = v.staticCast<ItemValue>(); + if (iv->item()) { + createNewItem = false; + instance = iv->item(); + } + } + if (createNewItem) { + Item *newItem = Item::create(m_pool); + instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); + instance = newItem; + } + } + QBS_ASSERT(moduleName.isEmpty() || instance != item, return 0); + return instance; +} + +Item *ModuleLoader::loadModule(ProductContext *productContext, Item *item, + const CodeLocation &dependsItemLocation, + const QString &moduleId, const QStringList &moduleName, bool isBaseModule, bool isRequired) +{ + Item *moduleInstance = moduleId.isEmpty() + ? moduleInstanceItem(item, moduleName) + : moduleInstanceItem(item, QStringList(moduleId)); + if (!moduleInstance->typeName().isNull()) { + // already handled + return moduleInstance; + } + + QStringList moduleSearchPaths(productContext->project->localModuleSearchPath); + foreach (const QString &searchPath, productContext->extraSearchPaths) + addExtraModuleSearchPath(moduleSearchPaths, searchPath); + bool cacheHit; + Item *modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, + moduleName, moduleSearchPaths, isRequired, &cacheHit); + if (!modulePrototype) + return 0; + if (!cacheHit && isBaseModule) + setupBaseModulePrototype(modulePrototype); + instantiateModule(productContext, item, moduleInstance, modulePrototype, moduleName); + callValidateScript(moduleInstance); + return moduleInstance; +} + +// It's not necessarily an error if we don't find a required module with the given name, +// because the dependency could refer to a product instead. +Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QStringList &moduleName, + const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit) +{ + QStringList searchPaths = extraSearchPaths; + searchPaths.append(m_moduleSearchPaths); + + bool triedToLoadModule = moduleName.count() > 1; + const QString fullName = fullModuleName(moduleName); + foreach (const QString &path, searchPaths) { + const QString dirPath = findExistingModulePath(path, moduleName); + if (dirPath.isEmpty()) + continue; + QStringList moduleFileNames = m_moduleDirListCache.value(dirPath); + if (moduleFileNames.isEmpty()) { + QDirIterator dirIter(dirPath, QStringList(QLatin1String("*.qbs"))); + while (dirIter.hasNext()) + moduleFileNames += dirIter.next(); + + m_moduleDirListCache.insert(dirPath, moduleFileNames); + } + foreach (const QString &filePath, moduleFileNames) { + triedToLoadModule = true; + Item *module = loadModuleFile(productContext, fullName, + moduleName.count() == 1 + && moduleName.first() == QLatin1String("qbs"), + filePath, cacheHit); + if (module) + return module; + } + } + + if (!isRequired) { + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << "Non-required module '" << fullName << "' not found." + << "Creating dummy module for presence check."; + } + Item * const module = Item::create(m_pool); + module->setFile(FileContext::create()); + module->setProperty(QLatin1String("present"), VariantValue::create(false)); + return module; + } + + if (Q_UNLIKELY(triedToLoadModule)) + throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), + dependsItemLocation); + + return 0; +} + +// returns QVariant::Invalid for types that do not need conversion +static QVariant::Type variantType(PropertyDeclaration::Type t) +{ + switch (t) { + case PropertyDeclaration::UnknownType: + break; + case PropertyDeclaration::Boolean: + return QVariant::Bool; + case PropertyDeclaration::Integer: + return QVariant::Int; + case PropertyDeclaration::Path: + return QVariant::String; + case PropertyDeclaration::PathList: + return QVariant::StringList; + case PropertyDeclaration::String: + return QVariant::String; + case PropertyDeclaration::StringList: + return QVariant::StringList; + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Verbatim: + return QVariant::String; + } + return QVariant::Invalid; +} + +static QVariant convertToPropertyType(const QVariant &v, PropertyDeclaration::Type t, + const QStringList &namePrefix, const QString &key) +{ + if (v.isNull() || !v.isValid()) + return v; + const QVariant::Type vt = variantType(t); + if (vt == QVariant::Invalid) + return v; + + // Handle the foo,bar,bla stringlist syntax. + if (t == PropertyDeclaration::StringList && v.type() == QVariant::String) + return v.toString().split(QLatin1Char(',')); + + QVariant c = v; + if (!c.convert(vt)) { + QStringList name = namePrefix; + name << key; + throw ErrorInfo(Tr::tr("Value '%1' of property '%2' has incompatible type.") + .arg(v.toString(), name.join(QLatin1String(".")))); + } + return c; +} + +static PropertyDeclaration firstValidPropertyDeclaration(Item *item, const QString &name) +{ + PropertyDeclaration decl; + do { + decl = item->propertyDeclarations().value(name); + if (decl.isValid()) + return decl; + item = item->prototype(); + } while (item); + return decl; +} + +Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *cacheHit) +{ + checkCancelation(); + Item *module = productContext->moduleItemCache.value(filePath); + if (module) { + m_logger.qbsTrace() << "[LDR] loadModuleFile cache hit for " << filePath; + *cacheHit = true; + return module; + } + + module = productContext->project->moduleItemCache.value(filePath); + if (module) { + m_logger.qbsTrace() << "[LDR] loadModuleFile returns clone for " << filePath; + *cacheHit = true; + return module->clone(m_pool); + } + + m_logger.qbsTrace() << "[LDR] loadModuleFile " << filePath; + *cacheHit = false; + module = m_reader->readFile(filePath); + if (!isBaseModule) { + DependsContext dependsContext; + dependsContext.product = productContext; + dependsContext.productDependencies = &productContext->info.usedProducts; + resolveDependencies(&dependsContext, module); + } + if (!checkItemCondition(module)) { + m_logger.qbsTrace() << "[LDR] module condition is false"; + return 0; + } + + // Module properties that are defined in the profile are used as default values. + const QVariantMap profileModuleProperties + = m_buildConfigProperties.value(fullModuleName).toMap(); + for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); + vmit != profileModuleProperties.end(); ++vmit) + { + if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) + throw ErrorInfo(Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); + const PropertyDeclaration decl = firstValidPropertyDeclaration(module, vmit.key()); + module->setProperty(vmit.key(), + VariantValue::create(convertToPropertyType(vmit.value(), decl.type, + QStringList(fullModuleName), vmit.key()))); + } + + productContext->moduleItemCache.insert(filePath, module); + productContext->project->moduleItemCache.insert(filePath, module); + return module; +} + +void ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) +{ + const QStringList baseModuleName(QLatin1String("qbs")); + Item::Module baseModuleDesc; + baseModuleDesc.name = baseModuleName; + baseModuleDesc.item = loadModule(productContext, item, CodeLocation(), QString(), + baseModuleName, true, true); + if (Q_UNLIKELY(!baseModuleDesc.item)) + throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); + item->modules() += baseModuleDesc; +} + +void ModuleLoader::setupBaseModulePrototype(Item *prototype) +{ + prototype->setProperty(QLatin1String("getNativeSetting"), + BuiltinValue::create(BuiltinValue::GetNativeSettingFunction)); + const BuiltinValuePtr getEnvValue = BuiltinValue::create(BuiltinValue::GetEnvFunction); + prototype->setProperty(QLatin1String("getEnv"), getEnvValue); + prototype->setProperty(QLatin1String("getenv"), getEnvValue); // TODO: Remove in 1.3. + prototype->setProperty(QLatin1String("getHostOS"), + BuiltinValue::create(BuiltinValue::GetHostOSFunction)); + prototype->setProperty(QLatin1String("canonicalArchitecture"), + BuiltinValue::create(BuiltinValue::CanonicalArchitectureFunction)); +} + +static void collectItemsWithId_impl(Item *item, QList<Item *> *result) +{ + if (!item->id().isEmpty()) + result->append(item); + foreach (Item *child, item->children()) + collectItemsWithId_impl(child, result); +} + +static QList<Item *> collectItemsWithId(Item *item) +{ + QList<Item *> result; + collectItemsWithId_impl(item, &result); + return result; +} + +void ModuleLoader::instantiateModule(ProductContext *productContext, Item *instanceScope, + Item *moduleInstance, Item *modulePrototype, + const QStringList &moduleName) +{ + const QString fullName = fullModuleName(moduleName); + modulePrototype->setProperty(QLatin1String("name"), + VariantValue::create(fullName)); + + moduleInstance->setPrototype(modulePrototype); + moduleInstance->setFile(modulePrototype->file()); + moduleInstance->setLocation(modulePrototype->location()); + moduleInstance->setTypeName(modulePrototype->typeName()); + + // create module scope + Item *moduleScope = Item::create(m_pool); + moduleScope->setScope(instanceScope); + copyProperty(QLatin1String("project"), productContext->project->scope, moduleScope); + copyProperty(QLatin1String("product"), productContext->scope, moduleScope); + moduleInstance->setScope(moduleScope); + moduleInstance->setModuleInstanceFlag(true); + + QHash<Item *, Item *> prototypeInstanceMap; + prototypeInstanceMap[modulePrototype] = moduleInstance; + + // create instances for every child of the prototype + createChildInstances(productContext, moduleInstance, modulePrototype, &prototypeInstanceMap); + + // create ids from from the prototype in the instance + if (modulePrototype->file()->idScope()) { + foreach (Item *itemWithId, collectItemsWithId(modulePrototype)) { + Item *idProto = itemWithId; + Item *idInstance = prototypeInstanceMap.value(idProto); + QBS_ASSERT(idInstance, continue); + ItemValuePtr idInstanceValue = ItemValue::create(idInstance); + moduleScope->setProperty(itemWithId->id(), idInstanceValue); + } + } + + // create module instances for the dependencies of this module + foreach (Item::Module m, modulePrototype->modules()) { + Item *depinst = moduleInstanceItem(moduleInstance, m.name); + const bool safetyCheck = true; + if (safetyCheck) { + Item *obj = moduleInstance; + for (int i = 0; i < m.name.count(); ++i) { + ItemValuePtr iv = obj->itemProperty(m.name.at(i)); + QBS_CHECK(iv); + obj = iv->item(); + QBS_CHECK(obj); + } + QBS_CHECK(obj == depinst); + } + depinst->setPrototype(m.item); + depinst->setFile(m.item->file()); + depinst->setLocation(m.item->location()); + depinst->setTypeName(m.item->typeName()); + depinst->setScope(moduleInstance); + m.item = depinst; + moduleInstance->modules() += m; + } + + // override module properties given on the command line + const QVariantMap userModuleProperties = m_overriddenProperties.value(fullName).toMap(); + for (QVariantMap::const_iterator vmit = userModuleProperties.begin(); + vmit != userModuleProperties.end(); ++vmit) { + if (Q_UNLIKELY(!moduleInstance->hasProperty(vmit.key()))) { + throw ErrorInfo(Tr::tr("Unknown property: %1.%2") + .arg(fullModuleName(moduleName), vmit.key())); + } + const PropertyDeclaration decl = firstValidPropertyDeclaration(moduleInstance, vmit.key()); + moduleInstance->setProperty(vmit.key(), + VariantValue::create(convertToPropertyType(vmit.value(), decl.type, moduleName, + vmit.key()))); + } +} + +void ModuleLoader::createChildInstances(ProductContext *productContext, Item *instance, + Item *prototype, + QHash<Item *, Item *> *prototypeInstanceMap) const +{ + foreach (Item *childPrototype, prototype->children()) { + Item *childInstance = Item::create(m_pool); + prototypeInstanceMap->insert(childPrototype, childInstance); + childInstance->setPrototype(childPrototype); + childInstance->setTypeName(childPrototype->typeName()); + childInstance->setFile(childPrototype->file()); + childInstance->setLocation(childPrototype->location()); + childInstance->setScope(productContext->scope); + Item::addChild(instance, childInstance); + createChildInstances(productContext, childInstance, childPrototype, prototypeInstanceMap); + } +} + +void ModuleLoader::resolveProbes(Item *item) +{ + foreach (Item *child, item->children()) + if (child->typeName() == QLatin1String("Probe")) + resolveProbe(item, child); +} + +void ModuleLoader::resolveProbe(Item *parent, Item *probe) +{ + const JSSourceValueConstPtr configureScript = probe->sourceProperty(QLatin1String("configure")); + if (Q_UNLIKELY(!configureScript)) + throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); + typedef QPair<QString, QScriptValue> ProbeProperty; + QList<ProbeProperty> probeBindings; + for (Item *obj = probe; obj; obj = obj->prototype()) { + foreach (const QString &name, obj->properties().keys()) { + if (name == QLatin1String("configure")) + continue; + QScriptValue sv = m_evaluator->property(probe, name); + if (Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(sv))) { + ValuePtr value = obj->property(name); + throw ErrorInfo(sv.toString(), value ? value->location() : CodeLocation()); + } + probeBindings += ProbeProperty(name, sv); + } + } + QScriptValue scope = m_engine->newObject(); + m_engine->currentContext()->pushScope(m_evaluator->scriptValue(parent)); + m_engine->currentContext()->pushScope(scope); + m_engine->currentContext()->pushScope(m_evaluator->fileScope(configureScript->file())); + foreach (const ProbeProperty &b, probeBindings) + scope.setProperty(b.first, b.second); + QScriptValue sv = m_engine->evaluate(configureScript->sourceCode()); + if (Q_UNLIKELY(m_engine->hasErrorOrException(sv))) + throw ErrorInfo(sv.toString(), configureScript->location()); + foreach (const ProbeProperty &b, probeBindings) { + const QVariant newValue = scope.property(b.first).toVariant(); + if (newValue != b.second.toVariant()) + probe->setProperty(b.first, VariantValue::create(newValue)); + } + m_engine->currentContext()->popScope(); + m_engine->currentContext()->popScope(); + m_engine->currentContext()->popScope(); +} + +void ModuleLoader::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId(m_buildConfigProperties))); + } +} + +bool ModuleLoader::checkItemCondition(Item *item) +{ + if (m_evaluator->boolValue(item, QLatin1String("condition"), true)) + return true; + m_disabledItems += item; + return false; +} + +void ModuleLoader::checkItemTypes(Item *item) +{ + if (Q_UNLIKELY(!item->typeName().isEmpty() + && !m_reader->builtins()->containsType(item->typeName()))) { + const QString msg = Tr::tr("Unexpected item type '%1'."); + throw ErrorInfo(msg.arg(item->typeName()), item->location()); + } + + const ItemDeclaration decl = m_reader->builtins()->declarationsForType(item->typeName()); + foreach (Item *child, item->children()) { + if (child->typeName().isEmpty()) + continue; + checkItemTypes(child); + if (!decl.isChildTypeAllowed(child->typeName())) + throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") + .arg(item->typeName(), child->typeName()), item->location()); + } +} + +void ModuleLoader::callValidateScript(Item *module) +{ + m_evaluator->boolValue(module, QLatin1String("validate")); +} + +QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) +{ + QStringList result; + const QString propertyName = QLatin1String("qbsSearchPaths"); + const QStringList paths = m_evaluator->stringListValue(item, propertyName, wasSet); + const ValueConstPtr prop = item->property(propertyName); + foreach (const QString &path, paths) + result += FileInfo::resolvePath(FileInfo::path(prop->location().fileName()), path); + return result; +} + +void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) +{ + if (!sourceProject) + return; + const QList<PropertyDeclaration> &builtinProjectProperties + = m_reader->builtins()->declarationsForType(QLatin1String("Project")).properties(); + QSet<QString> builtinProjectPropertyNames; + foreach (const PropertyDeclaration &p, builtinProjectProperties) + builtinProjectPropertyNames << p.name; + + for (Item::PropertyDeclarationMap::ConstIterator it + = sourceProject->propertyDeclarations().constBegin(); + it != sourceProject->propertyDeclarations().constEnd(); ++it) { + + // We must not inherit built-in properties such as "name", + // but "qbsSearchPaths" is an exception. + if (it.key() == QLatin1String("qbsSearchPaths")) { + const JSSourceValueConstPtr &v + = targetProject->property(it.key()).dynamicCast<const JSSourceValue>(); + QBS_ASSERT(v, continue); + if (v->sourceCode() == QLatin1String("undefined")) + copyProperty(it.key(), sourceProject, targetProject); + continue; + } + + if (builtinProjectPropertyNames.contains(it.key())) + continue; + + if (targetProject->properties().contains(it.key())) + continue; // Ignore stuff the target project already has. + + targetProject->setPropertyDeclaration(it.key(), it.value()); + copyProperty(it.key(), sourceProject, targetProject); + } +} + +Item *ModuleLoader::wrapWithProject(Item *item) +{ + Item *prj = Item::create(item->pool()); + prj->setChildren(QList<Item *>() << item); + item->setParent(prj); + prj->setTypeName("Project"); + prj->setFile(item->file()); + prj->setLocation(item->location()); + m_reader->builtins()->setupItemForBuiltinType(prj); + return prj; +} + +QString ModuleLoader::findExistingModulePath(const QString &searchPath, + const QStringList &moduleName) +{ + QString dirPath = searchPath; + foreach (const QString &moduleNamePart, moduleName) { + dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); + if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) + return QString(); + } + return dirPath; +} + +void ModuleLoader::copyProperty(const QString &propertyName, const Item *source, + Item *destination) +{ + destination->setProperty(propertyName, source->property(propertyName)); +} + +void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) +{ + foreach (Item *child, item->children()) { + child->setScope(scope); + setScopeForDescendants(child, scope); + } +} + +QString ModuleLoader::fullModuleName(const QStringList &moduleName) +{ + // Currently the same as the module sub directory. + // ### Might be nicer to be a valid JS identifier. +#if QT_VERSION >= 0x050000 + return moduleName.join(QLatin1Char('/')); +#else + return moduleName.join(QLatin1String("/")); +#endif +} + +void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig) +{ + const QVariant buildConfigValue = buildConfig.value(buildConfigKey); + if (buildConfigValue.isNull()) + return; + const QVariantMap overridden = buildConfigValue.toMap(); + for (QVariantMap::const_iterator it = overridden.constBegin(); it != overridden.constEnd(); + ++it) { + const PropertyDeclaration decl = item->propertyDeclarations().value(it.key()); + if (!decl.isValid()) { + throw ErrorInfo( + Tr::tr("Unknown property: %1.%2").arg(buildConfigKey, it.key())); + } + item->setProperty(it.key(), + VariantValue::create(convertToPropertyType(it.value(), decl.type, + QStringList(buildConfigKey), it.key()))); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h new file mode 100644 index 000000000..f6f62a2c3 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.h @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_MODULELOADER_H +#define QBS_MODULELOADER_H + +#include "forward_decls.h" +#include "itempool.h" +#include <logging/logger.h> + +#include <QMap> +#include <QSet> +#include <QStringList> +#include <QVariantMap> + +QT_BEGIN_NAMESPACE +class QScriptContext; +class QScriptEngine; +QT_END_NAMESPACE + +namespace qbs { + +class CodeLocation; + +namespace Internal { + +class BuiltinDeclarations; +class Evaluator; +class Item; +class ItemReader; +class ProgressObserver; +class ScriptEngine; + +struct ModuleLoaderResult +{ + ModuleLoaderResult() + : itemPool(new ItemPool), root(0) + {} + + struct ProductInfo + { + struct Dependency + { + QString name; + bool required; + QString failureMessage; + }; + + QList<Dependency> usedProducts; + QList<Dependency> usedProductsFromExportItem; + }; + + QSharedPointer<ItemPool> itemPool; + Item *root; + QHash<Item *, ProductInfo> productInfos; + QSet<QString> qbsFiles; +}; + +/* + * Loader stage II. Responsible for + * - loading modules and module dependencies, + * - project references, + * - Probe items. + */ +class ModuleLoader +{ +public: + ModuleLoader(ScriptEngine *engine, BuiltinDeclarations *builtins, const Logger &logger); + ~ModuleLoader(); + + void setProgressObserver(ProgressObserver *progressObserver); + void setSearchPaths(const QStringList &searchPaths); + Evaluator *evaluator() const { return m_evaluator; } + + ModuleLoaderResult load(const QString &filePath, + const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, + bool wrapWithProjectItem = false); + + static QString fullModuleName(const QStringList &moduleName); + static void overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig); + +private: + class ContextBase + { + public: + ContextBase() + : item(0), scope(0) + {} + + Item *item; + Item *scope; + QStringList extraSearchPaths; + QMap<QString, Item *> moduleItemCache; + }; + + class ProjectContext : public ContextBase + { + public: + ModuleLoaderResult *result; + QString localModuleSearchPath; + }; + + class ProductContext : public ContextBase + { + public: + ProjectContext *project; + ModuleLoaderResult::ProductInfo info; + QSet<FileContextConstPtr> filesWithExportItem; + QList<Item *> exportItems; + }; + + class DependsContext + { + public: + ProductContext *product; + QList<ModuleLoaderResult::ProductInfo::Dependency> *productDependencies; + }; + + typedef QPair<Item *, ModuleLoaderResult::ProductInfo::Dependency> ProductDependencyResult; + typedef QList<ProductDependencyResult> ProductDependencyResults; + + void handleProject(ModuleLoaderResult *loadResult, Item *item, + const QSet<QString> &referencedFilePaths); + void handleProduct(ProjectContext *projectContext, Item *item); + void handleSubProject(ProjectContext *projectContext, Item *item, + const QSet<QString> &referencedFilePaths); + void createAdditionalModuleInstancesInProduct(ProductContext *productContext); + void handleGroup(ProductContext *productContext, Item *group); + void handleArtifact(ProductContext *productContext, Item *item); + void deferExportItem(ProductContext *productContext, Item *item); + void mergeExportItems(ProductContext *productContext); + void propagateModulesFromProduct(ProductContext *productContext, Item *item); + void resolveDependencies(DependsContext *productContext, Item *item); + class ItemModuleList; + void resolveDependsItem(DependsContext *dependsContext, Item *item, Item *dependsItem, ItemModuleList *moduleResults, ProductDependencyResults *productResults); + Item *moduleInstanceItem(Item *item, const QStringList &moduleName); + Item *loadModule(ProductContext *productContext, Item *item, + const CodeLocation &dependsItemLocation, const QString &moduleId, + const QStringList &moduleName, bool isBaseModule, bool isRequired); + Item *searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QStringList &moduleName, + const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit); + Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *cacheHit); + void loadBaseModule(ProductContext *productContext, Item *item); + void setupBaseModulePrototype(Item *prototype); + void instantiateModule(ProductContext *productContext, Item *instanceScope, Item *moduleInstance, Item *modulePrototype, const QStringList &moduleName); + void createChildInstances(ProductContext *productContext, Item *instance, + Item *prototype, QHash<Item *, Item *> *prototypeInstanceMap) const; + void resolveProbes(Item *item); + void resolveProbe(Item *parent, Item *probe); + void checkCancelation() const; + bool checkItemCondition(Item *item); + void checkItemTypes(Item *item); + void callValidateScript(Item *module); + QStringList readExtraSearchPaths(Item *item, bool *wasSet = 0); + void copyProperties(const Item *sourceProject, Item *targetProject); + Item *wrapWithProject(Item *item); + static QString findExistingModulePath(const QString &searchPath, + const QStringList &moduleName); + static void copyProperty(const QString &propertyName, const Item *source, Item *destination); + static void setScopeForDescendants(Item *item, Item *scope); + + ScriptEngine *m_engine; + ItemPool *m_pool; + Logger m_logger; + ProgressObserver *m_progressObserver; + ItemReader *m_reader; + Evaluator *m_evaluator; + QStringList m_moduleSearchPaths; + QMap<QString, QStringList> m_moduleDirListCache; + QHash<Item *, QSet<QString> > m_validItemPropertyNamesPerItem; + QSet<Item *> m_disabledItems; + QVariantMap m_overriddenProperties; + QVariantMap m_buildConfigProperties; +}; + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_MODULELOADER_H diff --git a/src/lib/corelib/language/preparescriptobserver.cpp b/src/lib/corelib/language/preparescriptobserver.cpp new file mode 100644 index 000000000..c4f61fd29 --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 "preparescriptobserver.h" + +#include "property.h" +#include "scriptengine.h" + +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +PrepareScriptObserver::PrepareScriptObserver(ScriptEngine *engine) + : m_engine(engine) + , m_productObjectId(-1) + , m_projectObjectId(-1) +{ +} + +void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) +{ + if (object.objectId() == m_productObjectId) { + m_engine->addPropertyRequestedInScript( + Property(QString(), name, value.toVariant(), Property::PropertyInProduct)); + } else if (object.objectId() == m_projectObjectId) { + m_engine->addPropertyRequestedInScript( + Property(QString(), name, value.toVariant(), Property::PropertyInProject)); + } + +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/preparescriptobserver.h b/src/lib/corelib/language/preparescriptobserver.h new file mode 100644 index 000000000..21b101a2f --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#ifndef QBS_PREPARESCRIPTOBSERVER_H +#define QBS_PREPARESCRIPTOBSERVER_H + +#include "scriptpropertyobserver.h" + +namespace qbs { +namespace Internal { +class ScriptEngine; + +class PrepareScriptObserver : public ScriptPropertyObserver +{ +public: + PrepareScriptObserver(ScriptEngine *engine); + + void setProductObjectId(qint64 productId) { m_productObjectId = productId; } + void setProjectObjectId(qint64 projectId) { m_projectObjectId = projectId; } + +private: + void onPropertyRead(const QScriptValue &object, const QString &name, const QScriptValue &value); + + ScriptEngine * const m_engine; + qint64 m_productObjectId; + qint64 m_projectObjectId; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. 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 diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h new file mode 100644 index 000000000..d468c8d5e --- /dev/null +++ b/src/lib/corelib/language/projectresolver.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef PROJECTRESOLVER_H +#define PROJECTRESOLVER_H + +#include "evaluator.h" +#include "filetags.h" +#include "language.h" +#include <logging/logger.h> +#include <tools/setupprojectparameters.h> + +#include <QMap> +#include <QSet> + +namespace qbs { +namespace Internal { + +class BuiltinDeclarations; +class Evaluator; +class Item; +class ModuleLoader; +class ProgressObserver; +class ScriptEngine; +class StringListSet; +struct ModuleLoaderResult; + +class ProjectResolver +{ +public: + ProjectResolver(ModuleLoader *ldr, const BuiltinDeclarations *builtins, const Logger &logger); + ~ProjectResolver(); + + void setProgressObserver(ProgressObserver *observer); + TopLevelProjectPtr resolve(ModuleLoaderResult &loadResult, + const SetupProjectParameters &setupParameters); + + static void applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product, const Logger &logger); + +private: + struct ProjectContext + { + ResolvedProjectPtr project; + QList<FileTaggerConstPtr> fileTaggers; + ModuleLoaderResult *loadResult; + QList<RulePtr> rules; + ResolvedModulePtr dummyModule; + }; + + struct ProductContext + { + ResolvedProductPtr product; + Item *item; + }; + + struct ModuleContext + { + ResolvedModulePtr module; + }; + + void checkCancelation() const; + QString verbatimValue(const ValueConstPtr &value) const; + QString verbatimValue(Item *item, const QString &name) const; + ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; + ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; + void ignoreItem(Item *item, ProjectContext *projectContext); + void resolveTopLevelProject(Item *item, ProjectContext *projectContext); + void resolveProject(Item *item, ProjectContext *projectContext); + void resolveSubProject(Item *item, ProjectContext *projectContext); + void resolveProduct(Item *item, ProjectContext *projectContext); + void resolveModule(const QStringList &moduleName, Item *item, ProjectContext *projectContext); + void resolveGroup(Item *item, ProjectContext *projectContext); + void resolveRule(Item *item, ProjectContext *projectContext); + void resolveRuleArtifact(const RulePtr &rule, Item *item, bool *hasAlwaysUpdatedArtifact); + static void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, + StringListSet *seenBindings); + void resolveFileTagger(Item *item, ProjectContext *projectContext); + void resolveTransformer(Item *item, ProjectContext *projectContext); + void resolveExport(Item *item, ProjectContext *projectContext); + void resolveProductDependencies(ProjectContext *projectContext); + void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; + void applyFileTaggers(const ResolvedProductPtr &product) const; + QVariantMap evaluateModuleValues(Item *item) const; + void evaluateModuleValues(Item *item, QVariantMap *modulesMap) const; + QVariantMap evaluateProperties(Item *item) const; + QVariantMap evaluateProperties(Item *item, Item *propertiesContainer, + const QVariantMap &tmplt) const; + QVariantMap createProductConfig() const; + QString convertPathProperty(const QString &path, const QString &dirPath) const; + QStringList convertPathListProperty(const QStringList &paths, const QString &dirPath) const; + ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; + + Evaluator *m_evaluator; + const BuiltinDeclarations *m_builtins; + Logger m_logger; + ScriptEngine *m_engine; + ProgressObserver *m_progressObserver; + ProductContext *m_productContext; + ModuleContext *m_moduleContext; + QMap<QString, ResolvedProductPtr> m_productsByName; + QHash<ResolvedProductPtr, Item *> m_productItemMap; + mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap; + QMap<QString, QVariantMap> m_exports; + SetupProjectParameters m_setupParams; + + typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); + typedef QMap<QByteArray, ItemFuncPtr> ItemFuncMap; + void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); +}; + +} // namespace Internal +} // namespace qbs + +#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/language/property.h b/src/lib/corelib/language/property.h new file mode 100644 index 000000000..b989e5316 --- /dev/null +++ b/src/lib/corelib/language/property.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#ifndef QBS_PROPERTY_H +#define QBS_PROPERTY_H + +#include <tools/qbsassert.h> + +#include <QSet> +#include <QString> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class Property +{ +public: + enum Kind + { + PropertyInModule, + PropertyInProduct, + PropertyInProject + }; + + Property() + : kind(PropertyInModule) + { + } + + Property(const QString &m, const QString &p, const QVariant &v, Kind k = PropertyInModule) + : moduleName(m), propertyName(p), value(v), kind(k) + { + QBS_CHECK(!moduleName.contains(QLatin1Char('.'))); + } + + QString moduleName; + QString propertyName; + QVariant value; + Kind kind; +}; + +inline bool operator==(const Property &p1, const Property &p2) +{ + return p1.moduleName == p2.moduleName && p1.propertyName == p2.propertyName; +} + +inline uint qHash(const Property &p) +{ + return QT_PREPEND_NAMESPACE(qHash)(p.moduleName + p.propertyName); +} + +typedef QSet<Property> PropertyList; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp new file mode 100644 index 000000000..79236e6da --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 "propertydeclaration.h" + +namespace qbs { +namespace Internal { + +PropertyDeclaration::PropertyDeclaration() + : type(UnknownType) + , flags(DefaultFlags) +{ +} + +PropertyDeclaration::PropertyDeclaration(const QString &name, Type type, Flags flags) + : name(name) + , type(type) + , flags(flags) +{ +} + +PropertyDeclaration::~PropertyDeclaration() +{ +} + +bool PropertyDeclaration::isValid() const +{ + return type != UnknownType; +} + +PropertyDeclaration::Type PropertyDeclaration::propertyTypeFromString(const QString &typeName) +{ + if (typeName == QLatin1String("bool")) + return PropertyDeclaration::Boolean; + if (typeName == QLatin1String("int")) + return PropertyDeclaration::Integer; + if (typeName == QLatin1String("path")) + return PropertyDeclaration::Path; + if (typeName == QLatin1String("pathList")) + return PropertyDeclaration::PathList; + if (typeName == QLatin1String("string")) + return PropertyDeclaration::String; + if (typeName == QLatin1String("stringList")) + return PropertyDeclaration::StringList; + if (typeName == QLatin1String("var") || typeName == QLatin1String("variant")) + return PropertyDeclaration::Variant; + return PropertyDeclaration::UnknownType; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h new file mode 100644 index 000000000..441e42af9 --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_PROPERTYDECLARATION_H +#define QBS_PROPERTYDECLARATION_H + +#include <QString> +#include <QStringList> +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class PropertyDeclaration +{ +public: + enum Type + { + UnknownType, + Boolean, + Integer, + Path, + PathList, + String, + StringList, + Variant, + Verbatim + }; + + enum Flag + { + DefaultFlags = 0, + ListProperty = 0x1, + PropertyNotAvailableInConfig = 0x2 // Is this property part of a project, product or file configuration? + }; + Q_DECLARE_FLAGS(Flags, Flag) + + PropertyDeclaration(); + PropertyDeclaration(const QString &name, Type type, Flags flags = DefaultFlags); + ~PropertyDeclaration(); + + bool isValid() const; + + static Type propertyTypeFromString(const QString &typeName); + + QString name; + Type type; + Flags flags; + QScriptValue allowedValues; + QString description; + QString initialValueSource; + QStringList functionArgumentNames; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYDECLARATION_H diff --git a/src/lib/corelib/language/propertymapinternal.cpp b/src/lib/corelib/language/propertymapinternal.cpp new file mode 100644 index 000000000..063f9e947 --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 "propertymapinternal.h" +#include <tools/propertyfinder.h> +#include <tools/scripttools.h> + +namespace qbs { +namespace Internal { + +/*! + * \class PropertyMapInternal + * \brief The \c PropertyMapInternal class contains a set of properties and their values. + * An instance of this class is attached to every \c ResolvedProduct. + * \c ResolvedGroups inherit their properties from the respective \c ResolvedProduct, \c SourceArtifacts + * inherit theirs from the respective \c ResolvedGroup. \c ResolvedGroups can override the value of an + * inherited property, \c SourceArtifacts cannot. If a property value is overridden, a new + * \c PropertyMapInternal object is allocated, otherwise the pointer is shared. + * \sa ResolvedGroup + * \sa ResolvedProduct + * \sa SourceArtifact + */ +PropertyMapInternal::PropertyMapInternal() +{ +} + +PropertyMapInternal::PropertyMapInternal(const PropertyMapInternal &other) + : PersistentObject(other), m_value(other.m_value) +{ +} + +QVariant PropertyMapInternal::qbsPropertyValue(const QString &key) const +{ + return PropertyFinder().propertyValue(value(), QLatin1String("qbs"), key); +} + +void PropertyMapInternal::setValue(const QVariantMap &map) +{ + m_value = map; +} + +static QString toJSLiteral(const QVariantMap &vm, int level = 0) +{ + QString indent; + for (int i = 0; i < level; ++i) + indent += QLatin1String(" "); + QString str; + for (QVariantMap::const_iterator it = vm.begin(); it != vm.end(); ++it) { + if (it.value().type() == QVariant::Map) { + str += indent + it.key() + QLatin1String(": {\n"); + str += toJSLiteral(it.value().toMap(), level + 1); + str += indent + QLatin1String("}\n"); + } else { + str += indent + it.key() + QLatin1String(": ") + toJSLiteral(it.value()) + + QLatin1Char('\n'); + } + } + return str; +} + +QString PropertyMapInternal::toJSLiteral() const +{ + return qbs::Internal::toJSLiteral(m_value); +} + +void PropertyMapInternal::load(PersistentPool &pool) +{ + pool.stream() >> m_value; +} + +void PropertyMapInternal::store(PersistentPool &pool) const +{ + pool.stream() << m_value; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h new file mode 100644 index 000000000..329f6ddb2 --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_PROPERTYMAPINTERNAL_H +#define QBS_PROPERTYMAPINTERNAL_H + +#include "forward_decls.h" +#include <tools/persistence.h> +#include <QVariantMap> + +namespace qbs { +namespace Internal { + +class PropertyMapInternal : public PersistentObject +{ +public: + static PropertyMapPtr create() { return PropertyMapPtr(new PropertyMapInternal); } + PropertyMapPtr clone() const { return PropertyMapPtr(new PropertyMapInternal(*this)); } + + const QVariantMap &value() const { return m_value; } + QVariant qbsPropertyValue(const QString &key) const; // Convenience function. + void setValue(const QVariantMap &value); + QString toJSLiteral() const; + +private: + PropertyMapInternal(); + PropertyMapInternal(const PropertyMapInternal &other); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + QVariantMap m_value; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYMAPINTERNAL_H diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp new file mode 100644 index 000000000..e7f475d4c --- /dev/null +++ b/src/lib/corelib/language/scriptengine.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** 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 "scriptengine.h" + +#include "item.h" +#include "filecontext.h" +#include "propertymapinternal.h" +#include "scriptpropertyobserver.h" +#include <buildgraph/artifact.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +#include <QDebug> +#include <QFile> +#include <QScriptProgram> +#include <QScriptValueIterator> +#include <QSet> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +const bool debugJSImports = false; + +ScriptEngine::ScriptEngine(const Logger &logger, QObject *parent) + : QScriptEngine(parent), m_logger(logger) +{ + QScriptValue objectProto = globalObject().property(QLatin1String("Object")); + m_definePropertyFunction = objectProto.property(QLatin1String("defineProperty")); + QBS_ASSERT(m_definePropertyFunction.isFunction(), /* ignore */); + m_emptyFunction = evaluate("(function(){})"); + QBS_ASSERT(m_emptyFunction.isFunction(), /* ignore */); + // Initially push a new context to turn off scope chain insanity mode. + QScriptEngine::pushContext(); + extendJavaScriptBuiltins(); +} + +ScriptEngine::~ScriptEngine() +{ +} + +void ScriptEngine::import(const JsImports &jsImports, QScriptValue scope, QScriptValue targetObject) +{ + for (JsImports::const_iterator it = jsImports.begin(); it != jsImports.end(); ++it) + import(*it, scope, targetObject); +} + +void ScriptEngine::import(const JsImport &jsImport, QScriptValue scope, QScriptValue targetObject) +{ + QBS_ASSERT(!scope.isValid() || scope.isObject(), return); + QBS_ASSERT(targetObject.isObject(), return); + QBS_ASSERT(targetObject.engine() == this, return); + + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] import into " << jsImport.scopeName; + + foreach (const QString &fileName, jsImport.fileNames) { + QScriptValue jsImportValue; + jsImportValue = m_jsImportCache.value(fileName); + if (jsImportValue.isValid()) { + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] " << fileName << " (cache hit)"; + targetObject.setProperty(jsImport.scopeName, jsImportValue); + } else { + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] " << fileName << " (cache miss)"; + QFile file(fileName); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(tr("Cannot open '%1'.").arg(fileName)); + const QString sourceCode = QTextStream(&file).readAll(); + file.close(); + QScriptProgram program(sourceCode, fileName); + importProgram(program, scope, jsImportValue); + targetObject.setProperty(jsImport.scopeName, jsImportValue); + m_jsImportCache.insert(fileName, jsImportValue); + } + } +} + +void ScriptEngine::clearImportsCache() +{ + m_jsImportCache.clear(); +} + +void ScriptEngine::addPropertyRequestedFromArtifact(const Artifact *artifact, + const Property &property) +{ + m_propertiesRequestedFromArtifact[artifact->filePath()] << property; +} + +void ScriptEngine::addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value) +{ + m_propertyCache.insert(qMakePair(moduleName + QLatin1Char('.') + propertyName, propertyMap), + value); +} + +QVariant ScriptEngine::retrieveFromPropertyCache(const QString &moduleName, + const QString &propertyName, const PropertyMapConstPtr &propertyMap) +{ + return m_propertyCache.value(qMakePair(moduleName + QLatin1Char('.') + propertyName, + propertyMap)); +} + +void ScriptEngine::defineProperty(QScriptValue &object, const QString &name, + const QScriptValue &descriptor) +{ + QScriptValue arguments = newArray(); + arguments.setProperty(0, object); + arguments.setProperty(1, name); + arguments.setProperty(2, descriptor); + QScriptValue result = m_definePropertyFunction.call(QScriptValue(), arguments); + QBS_ASSERT(!hasErrorOrException(result), qDebug() << name << result.toString()); +} + +static QScriptValue js_observedGet(QScriptContext *context, QScriptEngine *, void *arg) +{ + ScriptPropertyObserver * const observer = static_cast<ScriptPropertyObserver *>(arg); + const QScriptValue data = context->callee().property(QLatin1String("qbsdata")); + const QScriptValue value = data.property(2); + observer->onPropertyRead(data.property(0), data.property(1).toVariant().toString(), value); + return value; +} + +void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name, + const QScriptValue &value, ScriptPropertyObserver *observer) +{ + if (!observer) { + object.setProperty(name, value); + return; + } + + QScriptValue data = newArray(); + data.setProperty(0, object); + data.setProperty(1, name); + data.setProperty(2, value); + QScriptValue getterFunc = newFunction(js_observedGet, observer); + getterFunc.setProperty(QLatin1String("qbsdata"), data); + QScriptValue descriptor = newObject(); + descriptor.setProperty(QLatin1String("get"), getterFunc); + descriptor.setProperty(QLatin1String("set"), m_emptyFunction); + defineProperty(object, name, descriptor); +} + +QProcessEnvironment ScriptEngine::environment() const +{ + return m_environment; +} + +void ScriptEngine::setEnvironment(const QProcessEnvironment &env) +{ + m_environment = env; +} + +void ScriptEngine::importProgram(const QScriptProgram &program, const QScriptValue &scope, + QScriptValue &targetObject) +{ + QSet<QString> globalPropertyNames; + { + QScriptValueIterator it(globalObject()); + while (it.hasNext()) { + it.next(); + globalPropertyNames += it.name(); + } + } + + pushContext(); + if (scope.isObject()) + currentContext()->pushScope(scope); + QScriptValue result = evaluate(program); + QScriptValue activationObject = currentContext()->activationObject(); + if (scope.isObject()) + currentContext()->popScope(); + popContext(); + if (Q_UNLIKELY(hasErrorOrException(result))) + throw ErrorInfo(tr("Error when importing '%1': %2").arg(program.fileName(), result.toString())); + + // If targetObject is already an object, it doesn't get overwritten but enhanced by the + // contents of the .js file. + // This is necessary for library imports that consist of multiple js files. + if (!targetObject.isObject()) + targetObject = newObject(); + + // Copy every property of the activation object to the target object. + // We do not just save a reference to the activation object, because QScriptEngine contains + // special magic for activation objects that leads to unanticipated results. + { + QScriptValueIterator it(activationObject); + while (it.hasNext()) { + it.next(); + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] Copying property " << it.name(); + targetObject.setProperty(it.name(), it.value()); + } + } + + // Copy new global properties to the target object and remove them from + // the global object. This is to support direct variable assignments + // without the 'var' keyword in JavaScript files. + QScriptValueIterator it(globalObject()); + while (it.hasNext()) { + it.next(); + if (globalPropertyNames.contains(it.name())) + continue; + + if (debugJSImports) { + m_logger.qbsDebug() << "[ENGINE] inserting global property " + << it.name() << " " << it.value().toString(); + } + + targetObject.setProperty(it.name(), it.value()); + it.remove(); + } +} + +void ScriptEngine::addEnvironmentVariable(const QString &name, const QString &value) +{ + m_usedEnvironment.insert(name, value); +} + +void ScriptEngine::addFileExistsResult(const QString &filePath, bool exists) +{ + m_fileExistsResult.insert(filePath, exists); +} + +void ScriptEngine::addFileLastModifiedResult(const QString &filePath, FileTime fileTime) +{ + m_fileLastModifiedResult.insert(filePath, fileTime); +} + +QSet<QString> ScriptEngine::imports() const +{ + return QSet<QString>::fromList(m_jsImportCache.keys()); +} + +QScriptValueList ScriptEngine::argumentList(const QStringList &argumentNames, + const QScriptValue &context) +{ + QScriptValueList result; + for (int i = 0; i < argumentNames.count(); ++i) + result += context.property(argumentNames.at(i)); + return result; +} + +class JSTypeExtender +{ +public: + JSTypeExtender(ScriptEngine *engine, const QString &typeName) + : m_engine(engine) + { + m_proto = engine->globalObject().property(typeName) + .property(QLatin1String("prototype")); + QBS_ASSERT(m_proto.isObject(), return); + m_descriptor = engine->newObject(); + } + + void addFunction(const QString &name, const QString &code) + { + QScriptValue f = m_engine->evaluate(code); + QBS_ASSERT(f.isFunction(), return); + m_descriptor.setProperty(QLatin1String("value"), f); + m_engine->defineProperty(m_proto, name, m_descriptor); + } + +private: + ScriptEngine *const m_engine; + QScriptValue m_proto; + QScriptValue m_descriptor; +}; + +void ScriptEngine::extendJavaScriptBuiltins() +{ + JSTypeExtender arrayExtender(this, QLatin1String("Array")); + arrayExtender.addFunction(QLatin1String("contains"), + QLatin1String("(function(e){return this.indexOf(e) !== -1;})")); + + JSTypeExtender stringExtender(this, QLatin1String("String")); + stringExtender.addFunction(QLatin1String("contains"), + QLatin1String("(function(e){return this.indexOf(e) !== -1;})")); + stringExtender.addFunction(QLatin1String("startsWith"), + QLatin1String("(function(e){return this.slice(0, e.length) === e;})")); + stringExtender.addFunction(QLatin1String("endsWith"), + QLatin1String("(function(e){return this.slice(-e.length) === e;})")); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h new file mode 100644 index 000000000..a2de9d9ea --- /dev/null +++ b/src/lib/corelib/language/scriptengine.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_SCRIPTENGINE_H +#define QBS_SCRIPTENGINE_H + +#include "jsimports.h" +#include "forward_decls.h" +#include <language/property.h> +#include <logging/logger.h> +#include <tools/filetime.h> +#include <tools/qbs_export.h> + +#include <QHash> +#include <QPair> +#include <QProcessEnvironment> +#include <QScriptEngine> +#include <QString> + +namespace qbs { +namespace Internal { +class Artifact; + +class ScriptPropertyObserver; + +// FIXME: Exported for qbs-qmltypes +class QBS_EXPORT ScriptEngine : public QScriptEngine +{ + Q_OBJECT +public: + ScriptEngine(const Logger &logger, QObject *parent = 0); + ~ScriptEngine(); + + void setLogger(const Logger &logger) { m_logger = logger; } + void import(const JsImports &jsImports, QScriptValue scope, QScriptValue targetObject); + void import(const JsImport &jsImport, QScriptValue scope, QScriptValue targetObject); + void clearImportsCache(); + + void addPropertyRequestedInScript(const Property &property) { + m_propertiesRequestedInScript += property; + } + void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); + void clearRequestedProperties() { + m_propertiesRequestedInScript.clear(); + m_propertiesRequestedFromArtifact.clear(); + } + PropertyList propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } + QHash<QString, PropertyList> propertiesRequestedFromArtifact() const { + return m_propertiesRequestedFromArtifact; + } + + void addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value); + QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap); + + void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor); + void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value, + ScriptPropertyObserver *observer); + + QProcessEnvironment environment() const; + void setEnvironment(const QProcessEnvironment &env); + void addEnvironmentVariable(const QString &name, const QString &value); + QHash<QString, QString> usedEnvironment() const { return m_usedEnvironment; } + void addFileExistsResult(const QString &filePath, bool exists); + void addFileLastModifiedResult(const QString &filePath, FileTime fileTime); + QHash<QString, bool> fileExistsResults() const { return m_fileExistsResult; } + QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; } + QSet<QString> imports() const; + static QScriptValueList argumentList(const QStringList &argumentNames, + const QScriptValue &context); + + class ScriptValueCache + { + public: + ScriptValueCache() : observer(0), project(0), product(0) {} + const void *observer; + const void *project; + const void *product; + QScriptValue observerScriptValue; + QScriptValue projectScriptValue; + QScriptValue productScriptValue; + }; + + ScriptValueCache *scriptValueCache() { return &m_scriptValueCache; } + + bool hasErrorOrException(const QScriptValue &v) const { + return v.isError() || hasUncaughtException(); + } + +private: + void extendJavaScriptBuiltins(); + void importProgram(const QScriptProgram &program, const QScriptValue &scope, + QScriptValue &targetObject); + + ScriptValueCache m_scriptValueCache; + QHash<QString, QScriptValue> m_jsImportCache; + QHash<QPair<QString, PropertyMapConstPtr>, QVariant> m_propertyCache; + PropertyList m_propertiesRequestedInScript; + QHash<QString, PropertyList> m_propertiesRequestedFromArtifact; + Logger m_logger; + QScriptValue m_definePropertyFunction; + QScriptValue m_emptyFunction; + QProcessEnvironment m_environment; + QHash<QString, QString> m_usedEnvironment; + QHash<QString, bool> m_fileExistsResult; + QHash<QString, FileTime> m_fileLastModifiedResult; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTENGINE_H diff --git a/src/lib/corelib/language/scriptpropertyobserver.h b/src/lib/corelib/language/scriptpropertyobserver.h new file mode 100644 index 000000000..e40ccc1d2 --- /dev/null +++ b/src/lib/corelib/language/scriptpropertyobserver.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_SCRIPTPROPERTYOBSERVER_H +#define QBS_SCRIPTPROPERTYOBSERVER_H + +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class ScriptPropertyObserver +{ +public: + virtual void onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTPROPERTYOBSERVER_H diff --git a/src/lib/corelib/language/testdata/Banana b/src/lib/corelib/language/testdata/Banana new file mode 100644 index 000000000..53164be8a --- /dev/null +++ b/src/lib/corelib/language/testdata/Banana @@ -0,0 +1 @@ +Peanut butter jelly time! diff --git a/src/lib/corelib/language/testdata/aboutdialog.cpp b/src/lib/corelib/language/testdata/aboutdialog.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/aboutdialog.cpp diff --git a/src/lib/corelib/language/testdata/baseproperty.qbs b/src/lib/corelib/language/testdata/baseproperty.qbs new file mode 100644 index 000000000..74024aedc --- /dev/null +++ b/src/lib/corelib/language/testdata/baseproperty.qbs @@ -0,0 +1,7 @@ +import "baseproperty_base.qbs" as BaseProduct + +BaseProduct { + name: "product1" + narf: base.concat(["boo"]) + zort: base.concat(["boo"]) +} diff --git a/src/lib/corelib/language/testdata/baseproperty_base.qbs b/src/lib/corelib/language/testdata/baseproperty_base.qbs new file mode 100644 index 000000000..85b64b76e --- /dev/null +++ b/src/lib/corelib/language/testdata/baseproperty_base.qbs @@ -0,0 +1,4 @@ +Product { + property var narf + property var zort: ["bar"] +} diff --git a/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs b/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs new file mode 100644 index 000000000..623919317 --- /dev/null +++ b/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs @@ -0,0 +1,3 @@ +Project { + property stringList someStrings +} diff --git a/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs b/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs new file mode 100644 index 000000000..f8f1b4d17 --- /dev/null +++ b/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + qbsSearchPaths: { + if (!qbs.getenv("PATH")) + throw "getenv doesn't seem to work"; + } +} diff --git a/src/lib/corelib/language/testdata/canonicalArchitecture.qbs b/src/lib/corelib/language/testdata/canonicalArchitecture.qbs new file mode 100644 index 000000000..94da7b276 --- /dev/null +++ b/src/lib/corelib/language/testdata/canonicalArchitecture.qbs @@ -0,0 +1,3 @@ +Product { + name: qbs.canonicalArchitecture("i386") +} diff --git a/src/lib/corelib/language/testdata/conditionaldepends.qbs b/src/lib/corelib/language/testdata/conditionaldepends.qbs new file mode 100644 index 000000000..8ad3660ec --- /dev/null +++ b/src/lib/corelib/language/testdata/conditionaldepends.qbs @@ -0,0 +1,67 @@ +import qbs 1.0 +import "conditionaldepends_base.qbs" as CondBase + +Project { + CondBase { + name: 'conditionaldepends_derived' + someProp: true + } + + CondBase { + name: 'conditionaldepends_derived_false' + someProp: "knolf" === "narf" + } + + Product { + name: "product_props_true" + property bool someTrueProp: true + Depends { condition: someTrueProp; name: "dummy"} + } + + Product { + name: "product_props_false" + property bool someFalseProp: false + Depends { condition: someFalseProp; name: "dummy"} + } + + property bool someTruePrjProp: true + Product { + name: "project_props_true" + Depends { condition: project.someTruePrjProp; name: "dummy"} + } + + property bool someFalsePrjProp: false + Product { + name: "project_props_false" + Depends { condition: project.someFalsePrjProp; name: "dummy"} + } + + Product { + name: "module_props_true" + Depends { name: "dummy2" } + Depends { condition: dummy2.someTrueProp; name: "dummy" } + } + + Product { + name: "module_props_false" + Depends { name: "dummy2" } + Depends { condition: dummy2.someFalseProp; name: "dummy" } + } + + Product { + name: "contradictory_conditions1" + Depends { condition: false; name: "dummy" } + Depends { condition: true; name: "dummy" } // this one wins + } + + Product { + name: "contradictory_conditions2" + Depends { condition: true; name: "dummy" } // this one wins + Depends { condition: false; name: "dummy" } + } + + Product { + name: "unknown_dependency_condition_false" + Depends { condition: false; name: "doesonlyexistifhellfreezesover" } + } +} diff --git a/src/lib/corelib/language/testdata/conditionaldepends_base.qbs b/src/lib/corelib/language/testdata/conditionaldepends_base.qbs new file mode 100644 index 000000000..81782ba44 --- /dev/null +++ b/src/lib/corelib/language/testdata/conditionaldepends_base.qbs @@ -0,0 +1,10 @@ +import qbs 1.0 + +Application { + name: 'conditionaldepends_base' + property bool someProp: false + Depends { + condition: someProp + name: 'dummy' + } +} diff --git a/src/lib/corelib/language/testdata/drawline.asm b/src/lib/corelib/language/testdata/drawline.asm new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/drawline.asm diff --git a/src/lib/corelib/language/testdata/environmentvariable.qbs b/src/lib/corelib/language/testdata/environmentvariable.qbs new file mode 100644 index 000000000..b930e8511 --- /dev/null +++ b/src/lib/corelib/language/testdata/environmentvariable.qbs @@ -0,0 +1,3 @@ +Product { + name: qbs.getEnv("PRODUCT_NAME") +} diff --git a/src/lib/corelib/language/testdata/erroneous/importloop1.qbs b/src/lib/corelib/language/testdata/erroneous/importloop1.qbs new file mode 100644 index 000000000..91e8f620f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/importloop1.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 +import "importloop2.qbs" as X + +X {} + diff --git a/src/lib/corelib/language/testdata/erroneous/importloop2.qbs b/src/lib/corelib/language/testdata/erroneous/importloop2.qbs new file mode 100644 index 000000000..c41fe7e9f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/importloop2.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 +import "importloop1.qbs" as X + +X {} + diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs new file mode 100644 index 000000000..2341d4b9c --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + Depends { name: "foo" } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs new file mode 100644 index 000000000..18f2b044d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs @@ -0,0 +1,5 @@ +import qbs + +Application { + files: ["main.cpp", "other.h"] +} diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs new file mode 100644 index 000000000..b9b392736 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs @@ -0,0 +1,5 @@ +import qbs + +Product { + property nonsense esnesnon +} diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs new file mode 100644 index 000000000..fc30a2af6 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs @@ -0,0 +1,3 @@ +Product { + files: ["foo", ["zoo"], "bar"] +} diff --git a/src/lib/corelib/language/testdata/erroneous/main.cpp b/src/lib/corelib/language/testdata/erroneous/main.cpp new file mode 100644 index 000000000..8b8d58de0 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/main.cpp @@ -0,0 +1 @@ +int main() { } diff --git a/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs b/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs new file mode 100644 index 000000000..17c7f6a14 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs @@ -0,0 +1,4 @@ +Product { + Export {} + Export {} +} diff --git a/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs b/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs new file mode 100644 index 000000000..0ecb41b34 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + SubProject { + Properties { condition: false } + Properties { name: "blubb" } + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs b/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs new file mode 100644 index 000000000..6c5899b5d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Project { + Product { + name: outer + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs new file mode 100644 index 000000000..6d0960f09 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle2.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs new file mode 100644 index 000000000..0b0d2734d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle3.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs new file mode 100644 index 000000000..2a237d154 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs b/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs new file mode 100644 index 000000000..3940109d0 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs @@ -0,0 +1,4 @@ +import qbs +import "../idusagebase.qbs" as TextFile + +Product { } diff --git a/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs b/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs new file mode 100644 index 000000000..4254bb8f6 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "abc.def"; submodules: ["ghi"] } +} diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs new file mode 100644 index 000000000..0a9cd289f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle2.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs new file mode 100644 index 000000000..ab92d76dd --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle3.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs new file mode 100644 index 000000000..af1e50f5a --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs b/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs new file mode 100644 index 000000000..fc251b1a4 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Product { + name: { + throw "something is wrong"; + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs b/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs new file mode 100644 index 000000000..b2edbf013 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + cpp.defines: ["SUPERCRAZY"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs b/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs new file mode 100644 index 000000000..1dad5f747 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + doesntexist: 123 +} + diff --git a/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs b/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs new file mode 100644 index 000000000..9e34e9243 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs @@ -0,0 +1,3 @@ +Narf { + zort: 1 // This invalid binding should not hide the "Unexpected item type" error. +} diff --git a/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs b/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs new file mode 100644 index 000000000..dcfc79a9c --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "neitherModuleNorProduct" } +} diff --git a/src/lib/corelib/language/testdata/exports.qbs b/src/lib/corelib/language/testdata/exports.qbs new file mode 100644 index 000000000..b1adfc214 --- /dev/null +++ b/src/lib/corelib/language/testdata/exports.qbs @@ -0,0 +1,49 @@ +import qbs 1.0 +import "exports_product.qbs" as ProductWithInheritedExportItem + +Project { + Application { + name: "myapp" + Depends { name: "mylib" } + } + StaticLibrary { + name: "mylib" + Depends { name: "dummy" } + dummy.defines: ["BUILD_MYLIB"] + Export { + Depends { name: "dummy" } + dummy.defines: ["USE_MYLIB"] + } + } + + Application { + name: "A" + Depends { name: "B" } + } + StaticLibrary { + name: "B" + Export { + Depends { name: "C" } + } + } + StaticLibrary { + name: "C" + Export { + Depends { name: "D" } + } + } + StaticLibrary { + name: "D" + } + + Application { + name: "myapp2" + Depends { name: "productWithInheritedExportItem" } + } + ProductWithInheritedExportItem { + name: "productWithInheritedExportItem" + Export { + dummy.cxxFlags: ["-bar"] + } + } +} diff --git a/src/lib/corelib/language/testdata/exports_product.qbs b/src/lib/corelib/language/testdata/exports_product.qbs new file mode 100644 index 000000000..78136fc32 --- /dev/null +++ b/src/lib/corelib/language/testdata/exports_product.qbs @@ -0,0 +1,7 @@ +Product { + Export { + Depends { name: "dummy" } + dummy.cxxFlags: ["-foo"] + dummy.defines: ["ABC"] + } +} diff --git a/src/lib/corelib/language/testdata/filecontextproperties.qbs b/src/lib/corelib/language/testdata/filecontextproperties.qbs new file mode 100644 index 000000000..5c435b3ba --- /dev/null +++ b/src/lib/corelib/language/testdata/filecontextproperties.qbs @@ -0,0 +1,5 @@ +Product { + name: "product1" + property string narf: filePath + property string zort: path +} diff --git a/src/lib/corelib/language/testdata/filetags.qbs b/src/lib/corelib/language/testdata/filetags.qbs new file mode 100644 index 000000000..38182e5f1 --- /dev/null +++ b/src/lib/corelib/language/testdata/filetags.qbs @@ -0,0 +1,73 @@ +import qbs 1.0 + +Project { + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + + Product { + name: "filetagger_project_scope" + files: ["main.cpp"] + } + + Product { + name: "filetagger_product_scope" + files: ["drawline.asm"] + FileTagger { + patterns: "*.asm" + fileTags: ["asm"] + } + } + + Product { + name: "filetagger_static_pattern" + files: "Banana" + FileTagger { + patterns: "Banana" + fileTags: ["yellow"] + } + } + + Product { + name: "unknown_file_tag" + files: "narf.zort" + } + + Product { + name: "set_file_tag_via_group" + Group { + files: ["main.cpp"] + fileTags: ["c++"] + } + } + + Product { + name: "override_file_tag_via_group" + files: "main.cpp" // gets file tag "cpp" through the FileTagger + Group { + files: product.files + fileTags: ["c++"] + } + } + + Product { + name: "add_file_tag_via_group" + files: "main.cpp" + Group { + overrideTags: false + files: "main.cpp" + fileTags: ["zzz"] + } + } + + Product { + name: "add_file_tag_via_group_and_file_ref" + files: "main.cpp" + Group { + overrideTags: false + files: product.files + fileTags: ["zzz"] + } + } +} diff --git a/src/lib/corelib/language/testdata/getNativeSetting.qbs b/src/lib/corelib/language/testdata/getNativeSetting.qbs new file mode 100644 index 000000000..c414c79f9 --- /dev/null +++ b/src/lib/corelib/language/testdata/getNativeSetting.qbs @@ -0,0 +1,23 @@ +import qbs.FileInfo + +Project { + Product { + name: { + if (qbs.hostOS.contains("osx")) { + return qbs.getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductName"); + } else if (qbs.hostOS.contains("windows")) { + var productName = qbs.getNativeSetting("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); + if (productName.contains("Windows")) { + return "Windows"; + } + return undefined; + } else { + return qbs.getNativeSetting(FileInfo.joinPaths(path, "nativesettings.ini"), "osname"); + } + } + } + + Product { + name: qbs.getNativeSetting("/dev/null", undefined, "fallback"); + } +} diff --git a/src/lib/corelib/language/testdata/groupconditions.qbs b/src/lib/corelib/language/testdata/groupconditions.qbs new file mode 100644 index 000000000..244c80c2c --- /dev/null +++ b/src/lib/corelib/language/testdata/groupconditions.qbs @@ -0,0 +1,53 @@ +import qbs 1.0 + +Project { + property bool someTrueProperty: true + Product { + name: "no_condition_no_group" + files: ["main.cpp"] + } + Product { + name: "no_condition" + Group { + files: ["main.cpp"] + } + } + Product { + name: "true_condition" + Group { + condition: true + files: ["main.cpp"] + } + } + Product { + name: "false_condition" + Group { + condition: false + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_product" + property bool anotherTrueProperty: true + Group { + condition: anotherTrueProperty + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_project" + Group { + condition: project.someTrueProperty + files: ["main.cpp"] + } + } + + Product { + name: "condition_accessing_module_property" + Group { + condition: qbs.targetOS.contains("narf") + files: ["main.cpp"] + qbs.install: false + } + } +} diff --git a/src/lib/corelib/language/testdata/groupname.qbs b/src/lib/corelib/language/testdata/groupname.qbs new file mode 100644 index 000000000..22e58765c --- /dev/null +++ b/src/lib/corelib/language/testdata/groupname.qbs @@ -0,0 +1,20 @@ +Project { + Product { + name: "MyProduct" + Group { + name: product.name + ".MyGroup" + files: "*" + } + } + + Product { + name: "My2ndProduct" + Group { + name: product.name + ".MyGroup" + files: ["narf"] + } + Group { + files: ["zort"] + } + } +} diff --git a/src/lib/corelib/language/testdata/homeDirectory.qbs b/src/lib/corelib/language/testdata/homeDirectory.qbs new file mode 100644 index 000000000..1ceeb5bbd --- /dev/null +++ b/src/lib/corelib/language/testdata/homeDirectory.qbs @@ -0,0 +1,18 @@ +import qbs 1.0 + +Project { + Product { + name: "home" + + // These should resolve + property path home: "~" + property path homeSlash: "~/" + property path homeUp: "~/.." + property path homeFile: "~/a" + + // These are sanity checks and should not + property path bogus1: "a~b" + property path bogus2: "a/~/bb" + property path user: "~foo/bar" // we don't resolve other-user paths + } +} diff --git a/src/lib/corelib/language/testdata/idusage.qbs b/src/lib/corelib/language/testdata/idusage.qbs new file mode 100644 index 000000000..42dc43ad5 --- /dev/null +++ b/src/lib/corelib/language/testdata/idusage.qbs @@ -0,0 +1,20 @@ +import qbs 1.0 +import "idusagebase.qbs" as DerivedProduct + +Project { + id: theProject + property int initialNr: 0 + DerivedProduct { + id: product1 + } + Product { + id: product2 + property int nr: theProject.initialNr + product1.nr + 1 + name: "product2_" + nr + } + Product { + id: product3 + property int nr: product2.nr + 1 + name: "product3_" + nr + } +} diff --git a/src/lib/corelib/language/testdata/idusagebase.qbs b/src/lib/corelib/language/testdata/idusagebase.qbs new file mode 100644 index 000000000..483a00ccf --- /dev/null +++ b/src/lib/corelib/language/testdata/idusagebase.qbs @@ -0,0 +1,5 @@ +Product { + id: baseProduct + property int nr: theProject.initialNr + 1 + name: "product1_" + nr +} diff --git a/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs b/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs new file mode 100644 index 000000000..e3e03a319 --- /dev/null +++ b/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs @@ -0,0 +1,16 @@ +import qbs 1.0 + +Project { + Product { + name: "product1" + condition: false + someNonsense: "Bitte stellen Sie die Tassen auf den Tisch." + } + Product { + name: "product2" + Group { + condition: false + moreNonsense: "Follen. Follen. Hünuntergefollen. Auf dön Töppüch." + } + } +} diff --git a/src/lib/corelib/language/testdata/jsextensions.js b/src/lib/corelib/language/testdata/jsextensions.js new file mode 100644 index 000000000..d26936911 --- /dev/null +++ b/src/lib/corelib/language/testdata/jsextensions.js @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +(function() { // Function wrapper to keep the environment clean. + +/* + * poor man's JS test suite + */ +var testctx = {}; + +function initTestContext(name) +{ + testctx.nr = 1; + testctx.name = name; +} + +function verify(c) +{ + if (!c) + throw testctx.name + ": verification #" + testctx.nr + " failed."; + testctx.nr++; +} + + +/* + * Tests for extensions of JavaScript builtin types. + */ + +var a = ["one", "two", "three"]; +initTestContext("Array.prototype.contains"); +for (var k in a) + verify(k !== "contains"); +verify(a.contains("one")); +verify(a.contains("two")); +verify(a.contains("three")); +verify(!a.contains("four")); + +})() // END function wrapper diff --git a/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js new file mode 100644 index 000000000..4e939505c --- /dev/null +++ b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js @@ -0,0 +1,12 @@ +function getName(qbsModule) +{ + if (qbsModule.debugInformation) + return "MyProduct_debug"; + else + return "MyProduct"; +} + +function getInstallDir() +{ + return "somewhere"; +} diff --git a/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs new file mode 100644 index 000000000..388cf974b --- /dev/null +++ b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs @@ -0,0 +1,7 @@ +import "jsimportsinmultiplescopes.js" as MyFunctions + +Product { + name: MyFunctions.getName(qbs) + qbs.installDir: MyFunctions.getInstallDir() + files: "main.cpp" +} diff --git a/src/lib/corelib/language/testdata/main.cpp b/src/lib/corelib/language/testdata/main.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/main.cpp diff --git a/src/lib/corelib/language/testdata/moduleproperties.qbs b/src/lib/corelib/language/testdata/moduleproperties.qbs new file mode 100644 index 000000000..c466a15cf --- /dev/null +++ b/src/lib/corelib/language/testdata/moduleproperties.qbs @@ -0,0 +1,21 @@ +import qbs 1.0 + +Project { + Product { + name: "merge_lists" + Depends { name: "dummyqt"; submodules: ["gui", "network"] } + Depends { name: "dummy" } + dummy.defines: ["THE_PRODUCT"] + } + Product { + name: "merge_lists_and_values" + Depends { name: "dummyqt"; submodules: ["network", "gui"] } + Depends { name: "dummy" } + dummy.defines: "THE_PRODUCT" + } + Product { + name: "merge_lists_with_duplicates" + Depends { name: "dummy" } + dummy.cxxFlags: ["-foo", "BAR", "-foo", "BAZ"] + } +} diff --git a/src/lib/corelib/language/testdata/modules.qbs b/src/lib/corelib/language/testdata/modules.qbs new file mode 100644 index 000000000..7841def35 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules.qbs @@ -0,0 +1,34 @@ +Project { + Product { + name: "no_modules" + property var foo + } + Product { + name: "qt_core" + dummyqt.core.version: "1.2.3" + property var foo: dummyqt.core.coreVersion + Depends { + name: "dummyqt.core" + } + } + Product { + name: "qt_gui" + property var foo: dummyqt.gui.guiProperty + Depends { + name: "dummyqt.gui" + } + } + Product { + name: "qt_gui_network" + property var foo: dummyqt.gui.guiProperty + ',' + dummyqt.network.networkProperty + Depends { + name: "dummyqt" + submodules: ["gui", "network"] + } + } + Product { + name: "dummy_twice" + Depends { name: "dummy" } + Depends { name: "dummy" } + } +} diff --git a/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs b/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs new file mode 100644 index 000000000..9484a70b8 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs @@ -0,0 +1,8 @@ +import "dummy_base.qbs" as DummyBase + +DummyBase { + condition: true + property stringList defines + property stringList cFlags + property stringList cxxFlags +} diff --git a/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs b/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs new file mode 100644 index 000000000..0ecd8a1d8 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs @@ -0,0 +1,4 @@ +Module { + condition: false + property pathList includePaths +} diff --git a/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs b/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs new file mode 100644 index 000000000..f60c38a71 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Module { + property var defines + property var someTrueProp: true + property var someFalseProp: false +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs new file mode 100644 index 000000000..13f5e6fc1 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 + +Module { + id: qtcore + property int versionMajor: 5 + property int versionMinor: 0 + property int versionPatch: 0 + property string version: versionMajor.toString() + "." + versionMinor.toString() + "." + versionPatch.toString() + property string coreProperty: "coreProperty" + property string coreVersion: qtcore.version + + Depends { name: "dummy" } + dummy.defines: ["QT_CORE"] +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs new file mode 100644 index 000000000..a42003c34 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs @@ -0,0 +1,9 @@ +import qbs 1.0 + +Module { + Depends { name: "dummyqt.core" } + property string guiProperty: "guiProperty" + + Depends { name: "dummy" } + dummy.defines: ["QT_GUI"] +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs new file mode 100644 index 000000000..2da3af050 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs @@ -0,0 +1,9 @@ +import qbs 1.0 + +Module { + Depends { name: "dummyqt"; submodules: ["core"] } + property string networkProperty: "networkProperty" + + Depends { name: "dummy" } + dummy.defines: ["QT_NETWORK"] +} diff --git a/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs b/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs new file mode 100644 index 000000000..ba7dbcbf0 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs @@ -0,0 +1,12 @@ +import qbs 1.0 + +Module { + property int a: 1 + property int b: 1 + property int c: a + 1 + property int d: b + 1 + property int e: 1 + property int f: 1 + property int g: 1 + property int h: 1 +} diff --git a/src/lib/corelib/language/testdata/modulescope.qbs b/src/lib/corelib/language/testdata/modulescope.qbs new file mode 100644 index 000000000..c127f0c61 --- /dev/null +++ b/src/lib/corelib/language/testdata/modulescope.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 +import "modulescope_base.qbs" as MyProduct + +Project { + MyProduct { + name: "product1" + property int e: 12 + property int f: 13 + scopemod.a: 2 + scopemod.f: 2 + scopemod.g: e * f + scopemod.h: base + 2 + } +} diff --git a/src/lib/corelib/language/testdata/modulescope_base.qbs b/src/lib/corelib/language/testdata/modulescope_base.qbs new file mode 100644 index 000000000..16a9875fa --- /dev/null +++ b/src/lib/corelib/language/testdata/modulescope_base.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + Depends { name: "scopemod" } + scopemod.h: e * f +} diff --git a/src/lib/corelib/language/testdata/narf b/src/lib/corelib/language/testdata/narf new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/narf diff --git a/src/lib/corelib/language/testdata/narf.zort b/src/lib/corelib/language/testdata/narf.zort new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/narf.zort diff --git a/src/lib/corelib/language/testdata/nativesettings.ini b/src/lib/corelib/language/testdata/nativesettings.ini new file mode 100644 index 000000000..4caf32e56 --- /dev/null +++ b/src/lib/corelib/language/testdata/nativesettings.ini @@ -0,0 +1 @@ +osname = Unix diff --git a/src/lib/corelib/language/testdata/outerInGroup.qbs b/src/lib/corelib/language/testdata/outerInGroup.qbs new file mode 100644 index 000000000..751392a4d --- /dev/null +++ b/src/lib/corelib/language/testdata/outerInGroup.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 + +Project { + Product { + name: "OuterInGroup" + qbs.installDir: "/somewhere" + files: ["main.cpp"] + Group { + name: "Special Group" + files: ["aboutdialog.cpp"] + qbs.installDir: outer + "/else" + } + } +} diff --git a/src/lib/corelib/language/testdata/pathproperties.qbs b/src/lib/corelib/language/testdata/pathproperties.qbs new file mode 100644 index 000000000..f0eeabf57 --- /dev/null +++ b/src/lib/corelib/language/testdata/pathproperties.qbs @@ -0,0 +1,9 @@ +import "subdir/pathproperties_base.qbs" as ProductBase + +ProductBase { + name: "product1" + property path projectFileDir: "." + property pathList filesInProjectFileDir: ["./aboutdialog.h", "aboutdialog.cpp"] + Depends { name: "dummy" } + dummy.includePaths: ["."] +} diff --git a/src/lib/corelib/language/testdata/productconditions.qbs b/src/lib/corelib/language/testdata/productconditions.qbs new file mode 100644 index 000000000..336c41340 --- /dev/null +++ b/src/lib/corelib/language/testdata/productconditions.qbs @@ -0,0 +1,19 @@ +import qbs 1.0 + +Project { + Product { + name: "product_no_condition" + } + Product { + name: "product_true_condition" + condition: 1 === 1 + } + Product { + name: "product_false_condition" + condition: 1 === 2 + } + Product { + name: "product_condition_dependent_of_module" + condition: qbs.endianness !== (qbs.endianness + "foo") + } +} diff --git a/src/lib/corelib/language/testdata/productdirectories.qbs b/src/lib/corelib/language/testdata/productdirectories.qbs new file mode 100644 index 000000000..dc4315207 --- /dev/null +++ b/src/lib/corelib/language/testdata/productdirectories.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 + +Product { + name: "MyApp" +} diff --git a/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs new file mode 100644 index 000000000..cc1b7b2a2 --- /dev/null +++ b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs @@ -0,0 +1,19 @@ +import qbs 1.0 + +Project { + Application { + name: { + if (!(dummy.cFlags instanceof Array)) + throw new Error("dummy.cFlags: Array type expected."); + if (!(dummy.cxxFlags instanceof Array)) + throw new Error("dummy.cxxFlags: Array type expected."); + if (!(dummy.defines instanceof Array)) + throw new Error("dummy.defines: Array type expected."); + return "product1"; + } + Depends { name: "dummy" } + // dummy.cxxFlags is set via profile and is not overridden + dummy.defines: ["IN_FILE"] // set in profile, overridden in file + dummy.cFlags: ["IN_FILE"] // set in profile, overridden on command line + } +} diff --git a/src/lib/corelib/language/testdata/propertiesblocks.qbs b/src/lib/corelib/language/testdata/propertiesblocks.qbs new file mode 100644 index 000000000..d61f0dd86 --- /dev/null +++ b/src/lib/corelib/language/testdata/propertiesblocks.qbs @@ -0,0 +1,150 @@ +import qbs 1.0 +import "propertiesblocks_base.qbs" as ProductBase + +Project { + Product { + name: "property_overwrite" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["SOMETHING"] + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_overwrite_no_outer" + Depends { id: cpp; name: "dummy" } + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_append_to_outer" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + } + Product { + name: "multiple_exclusive_properties" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["SOMETHING"] + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_no_outer" + Depends { id: cpp; name: "dummy" } + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_append_to_outer" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "ambiguous_properties" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + cpp.defines: outer.concat(["IMPOSSIBLE"]) + } + Properties { // will be ignored + condition: true + cpp.defines: outer.concat(["THREE"]) + } + } + Product { + name: "condition_refers_to_product_property" + property string narf: true + Depends { name: "dummy" } + Properties { + condition: narf + dummy.defines: ["OVERWRITTEN"] + } + } + property string zort: true + Product { + name: "condition_refers_to_project_property" + Depends { name: "dummy" } + Properties { + condition: project.zort + dummy.defines: ["OVERWRITTEN"] + } + } + ProductBase { + name: "inheritance_overwrite_in_subitem" + dummy.defines: ["OVERWRITTEN_IN_SUBITEM"] + } + ProductBase { + name: "inheritance_retain_base1" + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_retain_base2" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + dummy.defines: ["GNAMPF"] + } + ProductBase { + name: "inheritance_retain_base3" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + // no dummy.defines binding + } + ProductBase { + name: "inheritance_condition_in_subitem1" + defineBase: false + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_condition_in_subitem2" + defineBase: false + // no dummy.defines binding + } + Product { + id: knolf + name: "gnampf" + } + Product { + name: "condition_references_id" + Depends { id: cpp; name: "dummy" } + Properties { + condition: knolf.name === "gnampf" + cpp.defines: ["OVERWRITTEN"] + } + } +} diff --git a/src/lib/corelib/language/testdata/propertiesblocks_base.qbs b/src/lib/corelib/language/testdata/propertiesblocks_base.qbs new file mode 100644 index 000000000..71b09a3da --- /dev/null +++ b/src/lib/corelib/language/testdata/propertiesblocks_base.qbs @@ -0,0 +1,11 @@ +import qbs 1.0 + +Product { + property bool defineBase: true + Depends { name: "dummy" } + Properties { + condition: defineBase + dummy.defines: ["BASE"] + } + dummy.defines: ["SOMETHING"] +} diff --git a/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs b/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs new file mode 100644 index 000000000..62427169f --- /dev/null +++ b/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs @@ -0,0 +1,4 @@ +Product { + property path base_fileInProductDir: "foo" + property path base_fileInBaseProductDir: path + "/bar" +} diff --git a/src/lib/corelib/language/testdata/zort b/src/lib/corelib/language/testdata/zort new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/zort diff --git a/src/lib/corelib/language/tst_language.cpp b/src/lib/corelib/language/tst_language.cpp new file mode 100644 index 000000000..2308a7aa2 --- /dev/null +++ b/src/lib/corelib/language/tst_language.cpp @@ -0,0 +1,1439 @@ +/**************************************************************************** +** +** 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 "tst_language.h" + +#include <language/evaluator.h> +#include <language/identifiersearch.h> +#include <language/item.h> +#include <language/itempool.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/scripttools.h> +#include <tools/error.h> +#include <tools/hostosinfo.h> +#include <tools/propertyfinder.h> + +#include <QProcessEnvironment> + +Q_DECLARE_METATYPE(QList<bool>) + +namespace qbs { +namespace Internal { +static QString testDataDir() { return FileInfo::resolvePath(QLatin1String(SRCDIR), + QLatin1String("language/testdata")); } +static QString testProject(const char *fileName) { + return testDataDir() + QLatin1Char('/') + QLatin1String(fileName); +} + +TestLanguage::TestLanguage(ILogSink *logSink) + : m_logSink(logSink) + , m_wildcardsTestDirPath(QDir::tempPath() + QLatin1String("/_wildcards_test_dir_")) +{ + qsrand(QTime::currentTime().msec()); + qRegisterMetaType<QList<bool> >("QList<bool>"); + defaultParameters.setBuildRoot("/some/build/directory"); +} + +TestLanguage::~TestLanguage() +{ +} + +QHash<QString, ResolvedProductPtr> TestLanguage::productsFromProject(ResolvedProjectPtr project) +{ + QHash<QString, ResolvedProductPtr> result; + foreach (ResolvedProductPtr product, project->products) + result.insert(product->name, product); + return result; +} + +ResolvedModuleConstPtr TestLanguage::findModuleByName(ResolvedProductPtr product, const QString &name) +{ + foreach (const ResolvedModuleConstPtr &module, product->modules) + if (module->name == name) + return module; + return ResolvedModuleConstPtr(); +} + +QVariant TestLanguage::productPropertyValue(ResolvedProductPtr product, QString propertyName) +{ + QStringList propertyNameComponents = propertyName.split(QLatin1Char('.')); + if (propertyNameComponents.count() > 1) + propertyNameComponents.prepend(QLatin1String("modules")); + return getConfigProperty(product->properties->value(), propertyNameComponents); +} + +void TestLanguage::handleInitCleanupDataTags(const char *projectFileName, bool *handled) +{ + const QByteArray dataTag = QTest::currentDataTag(); + if (dataTag == "init") { + *handled = true; + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject(projectFileName)); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + } else if (dataTag == "cleanup") { + *handled = true; + project.clear(); + } else { + *handled = false; + } +} + +QString TestLanguage::buildDir(const SetupProjectParameters ¶ms) const +{ + return FileInfo::resolvePath(params.buildRoot(), + getConfigProperty(params.buildConfigurationTree(), + QStringList() << "qbs" << "profile").toString() + QLatin1Char('-') + + getConfigProperty(params.buildConfigurationTree(), + QStringList() << "qbs" << "buildVariant").toString()); +} + +#define HANDLE_INIT_CLEANUP_DATATAGS(fn) {\ + bool handled;\ + handleInitCleanupDataTags(fn, &handled);\ + if (handled)\ + return;\ + QVERIFY(project);\ +} + +void TestLanguage::initTestCase() +{ + m_logger = Logger(m_logSink); + m_engine = new ScriptEngine(m_logger, this); + loader = new Loader(m_engine, m_logger); + loader->setSearchPaths(QStringList() + << QLatin1String(SRCDIR "/../../../share/qbs")); + QVariantMap buildConfig = defaultParameters.buildConfigurationTree(); + buildConfig.insert("qbs.targetOS", "linux"); + buildConfig.insert("qbs.architecture", "x86_64"); + buildConfig.insert("qbs.profile", "qbs_autotests"); + defaultParameters.setBuildConfiguration(buildConfig); + QVERIFY(QFileInfo(m_wildcardsTestDirPath).isAbsolute()); +} + +void TestLanguage::cleanupTestCase() +{ + delete loader; +} + +void TestLanguage::baseProperty() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("baseproperty.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QCOMPARE(cfg.value("narf").toStringList(), QStringList() << "boo"); + QCOMPARE(cfg.value("zort").toStringList(), QStringList() << "bar" << "boo"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::buildConfigStringListSyntax() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + QVariantMap overriddenValues; + overriddenValues.insert("project.someStrings", "foo,bar,baz"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("buildconfigstringlistsyntax.qbs")); + project = loader->loadProject(parameters); + QVERIFY(project); + QCOMPARE(project->projectProperties().value("someStrings").toStringList(), + QStringList() << "foo" << "bar" << "baz"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::builtinFunctionInSearchPathsProperty() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + parameters.setProjectFilePath(testProject("builtinFunctionInSearchPathsProperty.qbs")); + QVERIFY(loader->loadProject(parameters)); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::canonicalArchitecture() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("canonicalArchitecture.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(QLatin1String("x86")); + QVERIFY(product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::conditionalDepends() +{ + bool exceptionCaught = false; + ResolvedProductPtr product; + ResolvedModuleConstPtr dependency; + try { + defaultParameters.setProjectFilePath(testProject("conditionaldepends.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + + product = products.value("conditionaldepends_derived"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("conditionaldepends_derived_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("product_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("product_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("project_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("project_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("module_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(dependency); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("module_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(dependency); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("contradictory_conditions1"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("contradictory_conditions2"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("unknown_dependency_condition_false"); + QVERIFY(product); + dependency = findModuleByName(product, "doesonlyexistifhellfreezesover"); + QVERIFY(!dependency); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::environmentVariable() +{ + bool exceptionCaught = false; + try { + // Create new environment: + const QString varName = QLatin1String("PRODUCT_NAME"); + const QString productName = QLatin1String("MyApp") + QString::number(qrand()); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(varName, productName); + + QProcessEnvironment origEnv = defaultParameters.environment(); // store orig environment + + defaultParameters.setEnvironment(env); + defaultParameters.setProjectFilePath(testProject("environmentvariable.qbs")); + project = loader->loadProject(defaultParameters); + + defaultParameters.setEnvironment(origEnv); // reset environment + + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::erroneousFiles_data() +{ + QTest::addColumn<QString>("errorMessage"); + QTest::newRow("unknown_module") + << "Product dependency 'neitherModuleNorProduct' not found"; + QTest::newRow("submodule_syntax") + << "Depends.submodules cannot be used if name contains a dot"; + QTest::newRow("multiple_exports") + << "Multiple Export items in one product are prohibited."; + QTest::newRow("multiple_properties_in_subproject") + << "Multiple instances of item 'Properties' found where at most one " + "is allowed."; + QTest::newRow("importloop1") + << "Loop detected when importing"; + QTest::newRow("nonexistentouter") + << "Can't find variable: outer"; + QTest::newRow("invalid_file") + << "does not exist"; + QTest::newRow("invalid_property_type") + << "Unknown type 'nonsense' in property declaration."; + QTest::newRow("reserved_name_in_import") + << "Cannot reuse the name of built-in extension 'TextFile'."; + QTest::newRow("throw_in_property_binding") + << "something is wrong"; + QTest::newRow("references_cycle") + << "Cycle detected while referencing file 'references_cycle.qbs'."; + QTest::newRow("subproject_cycle") + << "Cycle detected while loading subproject file 'subproject_cycle.qbs'."; + QTest::newRow("invalid_stringlist_element") + << "Expected array element of type String at index 1."; + QTest::newRow("undeclared_item") + << "Item 'cpp' is not declared."; + QTest::newRow("undeclared_property") + << "Property 'doesntexist' is not declared."; + QTest::newRow("unknown_item_type") + << "Unexpected item type 'Narf'"; + QTest::newRow("invalid_child_item_type") + << "Items of type 'Project' cannot contain items of type 'Depends'."; +} + +void TestLanguage::erroneousFiles() +{ + QFETCH(QString, errorMessage); + QString fileName = QString::fromLocal8Bit(QTest::currentDataTag()) + QLatin1String(".qbs"); + try { + defaultParameters.setProjectFilePath(testProject("/erroneous/") + fileName); + loader->loadProject(defaultParameters); + } catch (const ErrorInfo &e) { + if (!e.toString().contains(errorMessage)) { + qDebug() << "Message: " << e.toString(); + qDebug() << "Expected: " << errorMessage; + QFAIL("Unexpected error message."); + } + return; + } + QFAIL("No error thrown on invalid input."); +} + +void TestLanguage::exports() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("exports.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 8); + ResolvedProductPtr product; + product = products.value("myapp"); + QVERIFY(product); + QStringList propertyName = QStringList() << "modules" << "mylib" + << "modules" << "dummy" << "defines"; + QVariant propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "USE_MYLIB"); + product = products.value("mylib"); + + QVERIFY(product); + propertyName = QStringList() << "modules" << "dummy" << "defines"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "BUILD_MYLIB"); + + product = products.value("A"); + QVERIFY(product); + QVERIFY(product->dependencies.contains(products.value("B"))); + QVERIFY(product->dependencies.contains(products.value("C"))); + QVERIFY(product->dependencies.contains(products.value("D"))); + product = products.value("B"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + product = products.value("C"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + product = products.value("D"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + + product = products.value("myapp2"); + QVERIFY(product); + propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + << "modules" << "dummy" << "cxxFlags"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "-bar"); + propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + << "modules" << "dummy" << "defines"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "ABC"); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::fileContextProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("filecontextproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QCOMPARE(cfg.value("narf").toString(), defaultParameters.projectFilePath()); + QString dirPath = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + QCOMPARE(cfg.value("zort").toString(), dirPath); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::getNativeSetting() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("getNativeSetting.qbs")); + project = loader->loadProject(defaultParameters); + + QString expectedProductName; + if (HostOsInfo::isOsxHost()) + expectedProductName = QLatin1String("Mac OS X"); + else if (HostOsInfo::isWindowsHost()) + expectedProductName = QLatin1String("Windows"); + else + expectedProductName = QLatin1String("Unix"); + + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(expectedProductName); + QVERIFY(product); + ResolvedProductPtr product2 = products.value(QLatin1String("fallback")); + QVERIFY(product2); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::groupConditions_data() +{ + QTest::addColumn<int>("groupCount"); + QTest::addColumn<QList<bool> >("groupEnabled"); + QTest::newRow("init") << 0 << QList<bool>(); + QTest::newRow("no_condition_no_group") + << 1 << (QList<bool>() << true); + QTest::newRow("no_condition") + << 2 << (QList<bool>() << true << true); + QTest::newRow("true_condition") + << 2 << (QList<bool>() << true << true); + QTest::newRow("false_condition") + << 2 << (QList<bool>() << true << false); + QTest::newRow("true_condition_from_product") + << 2 << (QList<bool>() << true << true); + QTest::newRow("true_condition_from_project") + << 2 << (QList<bool>() << true << true); + QTest::newRow("condition_accessing_module_property") + << 2 << (QList<bool>() << true << false); + QTest::newRow("cleanup") << 0 << QList<bool>(); +} + +void TestLanguage::groupConditions() +{ + HANDLE_INIT_CLEANUP_DATATAGS("groupconditions.qbs"); + QFETCH(int, groupCount); + QFETCH(QList<bool>, groupEnabled); + QCOMPARE(groupCount, groupEnabled.count()); + const QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QCOMPARE(product->groups.count(), groupCount); + for (int i = 0; i < groupCount; ++i) { + if (product->groups.at(i)->enabled != groupEnabled.at(i)) { + QFAIL(qPrintable( + QString("groups.at(%1)->enabled != %2").arg(i).arg(groupEnabled.at(i)))); + } + } +} + +void TestLanguage::groupName() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("groupname.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 2); + + ResolvedProductPtr product = products.value("MyProduct"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 2); + GroupConstPtr group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, QString("MyProduct")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("MyProduct.MyGroup")); + + product = products.value("My2ndProduct"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 3); + group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, QString("My2ndProduct")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("My2ndProduct.MyGroup")); + group = product->groups.at(2); + QVERIFY(group); + QCOMPARE(group->name, QString("Group 2")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::homeDirectory() +{ + try { + defaultParameters.setProjectFilePath(testProject("homeDirectory.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + + ResolvedProductPtr product = products.value("home"); + QVERIFY(product); + + QDir dir = QDir::home(); + QCOMPARE(product->properties->value().value("home").toString(), dir.canonicalPath()); + QCOMPARE(product->properties->value().value("homeSlash").toString(), dir.canonicalPath()); + + dir.cdUp(); + QCOMPARE(product->properties->value().value("homeUp").toString(), dir.canonicalPath()); + + dir = QDir::home(); + QCOMPARE(product->properties->value().value("homeFile").toString(), dir.filePath("a")); + + QCOMPARE(product->properties->value().value("bogus1").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("a~b"))); + QCOMPARE(product->properties->value().value("bogus2").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("a/~/bb"))); + QCOMPARE(product->properties->value().value("user").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("~foo/bar"))); + } + catch (const ErrorInfo &e) { + qDebug() << e.toString(); + } +} + +void TestLanguage::identifierSearch_data() +{ + QTest::addColumn<bool>("expectedHasNarf"); + QTest::addColumn<bool>("expectedHasZort"); + QTest::addColumn<QString>("sourceCode"); + QTest::newRow("no narf, no zort") << false << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'bar';\n" + " print(foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, no zort") << true << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'zort';\n" + " print(narf + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("no narf, zort") << false << true << QString( + "Product {\n" + " name: {\n" + " var foo = 'narf';\n" + " print(zort + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("2 narfs, 1 zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = narf + foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); +} + +void TestLanguage::identifierSearch() +{ + QFETCH(bool, expectedHasNarf); + QFETCH(bool, expectedHasZort); + QFETCH(QString, sourceCode); + + bool hasNarf = !expectedHasNarf; + bool hasZort = !expectedHasZort; + IdentifierSearch isearch; + isearch.add("narf", &hasNarf); + isearch.add("zort", &hasZort); + + QbsQmlJS::Engine engine; + QbsQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, 1); + QbsQmlJS::Parser parser(&engine); + QVERIFY(parser.parse()); + QVERIFY(parser.ast()); + isearch.start(parser.ast()); + QCOMPARE(hasNarf, expectedHasNarf); + QCOMPARE(hasZort, expectedHasZort); +} + +void TestLanguage::idUsage() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("idusage.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 3); + QVERIFY(products.contains("product1_1")); + QVERIFY(products.contains("product2_2")); + QVERIFY(products.contains("product3_3")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::invalidBindingInDisabledItem() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("invalidBindingInDisabledItem.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 2); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +class JSSourceValueCreator +{ + FileContextPtr m_fileContext; +public: + JSSourceValueCreator(const FileContextPtr &fileContext) + : m_fileContext(fileContext) + { + } + + JSSourceValuePtr create(const QString &sourceCode) + { + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_fileContext); + value->setSourceCode(sourceCode); + return value; + } +}; + +void TestLanguage::itemPrototype() +{ + FileContextPtr fileContext = FileContext::create(); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *proto = Item::create(&pool); + proto->setProperty("x", sourceValueCreator.create("1")); + proto->setProperty("y", sourceValueCreator.create("1")); + Item *item = Item::create(&pool); + item->setPrototype(proto); + item->setProperty("y", sourceValueCreator.create("x + 1")); + item->setProperty("z", sourceValueCreator.create("2")); + + Evaluator evaluator(m_engine, m_logger); + QCOMPARE(evaluator.property(proto, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(proto, "y").toVariant().toInt(), 1); + QVERIFY(!evaluator.property(proto, "z").isValid()); + QCOMPARE(evaluator.property(item, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(item, "y").toVariant().toInt(), 2); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 2); +} + +void TestLanguage::itemScope() +{ + FileContextPtr fileContext = FileContext::create(); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *scope1 = Item::create(&pool); + scope1->setProperty("x", sourceValueCreator.create("1")); + Item *scope2 = Item::create(&pool); + scope2->setScope(scope1); + scope2->setProperty("y", sourceValueCreator.create("x + 1")); + Item *item = Item::create(&pool); + item->setScope(scope2); + item->setProperty("z", sourceValueCreator.create("x + y")); + + Evaluator evaluator(m_engine, m_logger); + QCOMPARE(evaluator.property(scope1, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(scope2, "y").toVariant().toInt(), 2); + QVERIFY(!evaluator.property(scope2, "x").isValid()); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 3); +} + +void TestLanguage::jsExtensions() +{ + QFile file(testProject("jsextensions.js")); + QVERIFY(file.open(QFile::ReadOnly)); + QTextStream ts(&file); + QString code = ts.readAll(); + QVERIFY(!code.isEmpty()); + QScriptValue evaluated = m_engine->evaluate(code, file.fileName(), 1); + if (m_engine->hasErrorOrException(evaluated)) { + qDebug() << m_engine->uncaughtExceptionBacktrace(); + QFAIL(qPrintable(evaluated.toString())); + } +} + +void TestLanguage::jsImportUsedInMultipleScopes_data() +{ + QTest::addColumn<QString>("buildVariant"); + QTest::addColumn<QString>("expectedProductName"); + QTest::newRow("debug") << QString("debug") << QString("MyProduct_debug"); + QTest::newRow("release") << QString("release") << QString("MyProduct"); +} + +void TestLanguage::jsImportUsedInMultipleScopes() +{ + QFETCH(QString, buildVariant); + QFETCH(QString, expectedProductName); + + bool exceptionCaught = false; + try { + QVariantMap customBuildConfig = defaultParameters.buildConfiguration(); + customBuildConfig.insert(QLatin1String("qbs.buildVariant"), buildVariant); + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("jsimportsinmultiplescopes.qbs")); + params.setBuildConfiguration(customBuildConfig); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.values().first(); + QVERIFY(product); + QCOMPARE(product->name, expectedProductName); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::moduleProperties_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QStringList>("expectedValues"); + QTest::newRow("init") << QString() << QStringList(); + QTest::newRow("merge_lists") + << "defines" + << (QStringList() << "THE_PRODUCT" << "QT_CORE" << "QT_GUI" << "QT_NETWORK"); + QTest::newRow("merge_lists_and_values") + << "defines" + << (QStringList() << "THE_PRODUCT" << "QT_CORE" << "QT_GUI" << "QT_NETWORK"); + QTest::newRow("merge_lists_with_duplicates") + << "cxxFlags" + << (QStringList() << "-foo" << "BAR" << "-foo" << "BAZ"); + QTest::newRow("cleanup") << QString() << QStringList(); +} + +void TestLanguage::moduleProperties() +{ + HANDLE_INIT_CLEANUP_DATATAGS("moduleproperties.qbs"); + QFETCH(QString, propertyName); + QFETCH(QStringList, expectedValues); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QVariantList values = PropertyFinder().propertyValues(product->properties->value(), + "dummy", propertyName, + PropertyFinder::DoMergeLists); + QStringList valueStrings; + foreach (const QVariant &v, values) + valueStrings += v.toString(); + QCOMPARE(valueStrings, expectedValues); +} + +void TestLanguage::moduleScope() +{ + class IntPropertyFinder + { + const QVariantMap &m_properties; + public: + IntPropertyFinder(const QVariantMap &properties) + : m_properties(properties) + {} + + int intValue(const QString &name) + { + return PropertyFinder().propertyValue(m_properties, "scopemod", name).toInt(); + } + }; + + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("modulescope.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + IntPropertyFinder ipf(product->properties->value()); + QCOMPARE(ipf.intValue("a"), 2); // overridden in module instance + QCOMPARE(ipf.intValue("b"), 1); // genuine + QCOMPARE(ipf.intValue("c"), 3); // genuine, dependent on overridden value + QCOMPARE(ipf.intValue("d"), 2); // genuine, dependent on genuine value + QCOMPARE(ipf.intValue("e"), 1); // genuine + QCOMPARE(ipf.intValue("f"), 2); // overridden + QCOMPARE(ipf.intValue("g"), 156); // overridden, dependent on product properties + QCOMPARE(ipf.intValue("h"), 158); // overridden, base dependent on product properties + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + +} + +void TestLanguage::modules_data() +{ + QTest::addColumn<QStringList>("expectedModulesInProduct"); + QTest::addColumn<QString>("expectedProductProperty"); + QTest::newRow("init") << QStringList(); + QTest::newRow("no_modules") + << (QStringList() << "qbs") + << QString(); + QTest::newRow("qt_core") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core") + << QString("1.2.3"); + QTest::newRow("qt_gui") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core" << "dummyqt/gui") + << QString("guiProperty"); + QTest::newRow("qt_gui_network") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core" << "dummyqt/gui" + << "dummyqt/network") + << QString("guiProperty,networkProperty"); + QTest::newRow("dummy_twice") + << (QStringList() << "qbs" << "dummy") + << QString(); + QTest::newRow("cleanup") << QStringList(); +} + +void TestLanguage::modules() +{ + HANDLE_INIT_CLEANUP_DATATAGS("modules.qbs"); + QFETCH(QStringList, expectedModulesInProduct); + QFETCH(QString, expectedProductProperty); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QStringList modulesInProduct; + foreach (ResolvedModuleConstPtr m, product->modules) + modulesInProduct += m->name; + modulesInProduct.sort(); + expectedModulesInProduct.sort(); + QCOMPARE(modulesInProduct, expectedModulesInProduct); + QCOMPARE(product->properties->value().value("foo").toString(), expectedProductProperty); +} + +void TestLanguage::outerInGroup() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("outerInGroup.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.value("OuterInGroup"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 2); + GroupPtr group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, product->name); + QCOMPARE(group->files.count(), 1); + SourceArtifactConstPtr artifact = group->files.first(); + QVariant installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("Special Group")); + QCOMPARE(group->files.count(), 1); + artifact = group->files.first(); + installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere/else")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::pathProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("pathproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QString projectFileDir = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + QCOMPARE(cfg.value("projectFileDir").toString(), projectFileDir); + QStringList filesInProjectFileDir = QStringList() + << FileInfo::resolvePath(projectFileDir, "aboutdialog.h") + << FileInfo::resolvePath(projectFileDir, "aboutdialog.cpp"); + QCOMPARE(cfg.value("filesInProjectFileDir").toStringList(), filesInProjectFileDir); + QStringList includePaths = getConfigProperty(cfg, QStringList() << "modules" << "dummy" + << "includePaths").toStringList(); + QCOMPARE(includePaths, QStringList() << projectFileDir); + QCOMPARE(cfg.value("base_fileInProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QLatin1String("foo"))); + QCOMPARE(cfg.value("base_fileInBaseProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QLatin1String("subdir/bar"))); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::profileValuesAndOverriddenValues() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + QVariantMap buildConfig = parameters.buildConfiguration(); + buildConfig.insert("dummy.defines", "IN_PROFILE"); + buildConfig.insert("dummy.cFlags", "IN_PROFILE"); + buildConfig.insert("dummy.cxxFlags", "IN_PROFILE"); + parameters.setBuildConfiguration(buildConfig); + QVariantMap overriddenValues; + overriddenValues.insert("dummy.cFlags", "OVERRIDDEN"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("profilevaluesandoverriddenvalues.qbs")); + project = loader->loadProject(parameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + PropertyFinder pf; + QVariantList values; + values = pf.propertyValues(product->properties->value(), + "dummy", "cxxFlags", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("IN_PROFILE")); + values = pf.propertyValues(product->properties->value(), + "dummy", "defines", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("IN_FILE")); + values = pf.propertyValues(product->properties->value(), + "dummy", "cFlags", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("OVERRIDDEN")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::productConditions() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productconditions.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 4); + ResolvedProductPtr product; + product = products.value("product_no_condition"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_true_condition"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_condition_dependent_of_module"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_false_condition"); + QVERIFY(product); + QVERIFY(!product->enabled); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::productDirectories() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productdirectories.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product; + product = products.value("MyApp"); + QVERIFY(product); + const QVariantMap config = product->properties->value(); + QCOMPARE(config.value(QLatin1String("buildDirectory")).toString(), + buildDir(defaultParameters)); + QCOMPARE(config.value(QLatin1String("sourceDirectory")).toString(), testDataDir()); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::propertiesBlocks_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QStringList>("expectedValues"); + + QTest::newRow("init") << QString() << QStringList(); + QTest::newRow("property_overwrite") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("property_overwrite_no_outer") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("property_append_to_outer") << QString("dummy.defines") << (QStringList() << QString("ONE") << QString("TWO")); + + QTest::newRow("multiple_exclusive_properties") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("multiple_exclusive_properties_no_outer") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("multiple_exclusive_properties_append_to_outer") << QString("dummy.defines") << (QStringList() << QString("ONE") << QString("TWO")); + QTest::newRow("condition_refers_to_product_property") + << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("condition_refers_to_project_property") + << QString("dummy.defines") << QStringList("OVERWRITTEN"); + + QTest::newRow("ambiguous_properties") + << QString("dummy.defines") + << (QStringList() << QString("ONE") << QString("TWO")); + QTest::newRow("inheritance_overwrite_in_subitem") + << QString("dummy.defines") + << (QStringList() << QString("OVERWRITTEN_IN_SUBITEM")); + QTest::newRow("inheritance_retain_base1") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_retain_base2") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_retain_base3") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_condition_in_subitem1") + << QString("dummy.defines") + << (QStringList() << QString("SOMETHING") << QString("SUB")); + QTest::newRow("inheritance_condition_in_subitem2") + << QString("dummy.defines") + << (QStringList() << QString("SOMETHING")); + QTest::newRow("condition_references_id") + << QString("dummy.defines") + << (QStringList() << QString("OVERWRITTEN")); + QTest::newRow("cleanup") << QString() << QStringList(); +} + +void TestLanguage::propertiesBlocks() +{ + HANDLE_INIT_CLEANUP_DATATAGS("propertiesblocks.qbs"); + QFETCH(QString, propertyName); + QFETCH(QStringList, expectedValues); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QVariant v = productPropertyValue(product, propertyName); + QCOMPARE(v.toStringList(), expectedValues); +} + +void TestLanguage::fileTags_data() +{ + QTest::addColumn<int>("numberOfGroups"); + QTest::addColumn<QStringList>("expectedFileTags"); + + QTest::newRow("init") << 0 << QStringList(); + QTest::newRow("filetagger_project_scope") << 1 << (QStringList() << "cpp"); + QTest::newRow("filetagger_product_scope") << 1 << (QStringList() << "asm"); + QTest::newRow("filetagger_static_pattern") << 1 << (QStringList() << "yellow"); + QTest::newRow("unknown_file_tag") << 1 << (QStringList() << "unknown-file-tag"); + QTest::newRow("set_file_tag_via_group") << 2 << (QStringList() << "c++"); + QTest::newRow("override_file_tag_via_group") << 2 << (QStringList() << "c++"); + QTest::newRow("add_file_tag_via_group") << 2 << (QStringList() << "cpp" << "zzz"); + QTest::newRow("add_file_tag_via_group_and_file_ref") << 2 << (QStringList() << "cpp" << "zzz"); + QTest::newRow("cleanup") << 0 << QStringList(); +} + +void TestLanguage::fileTags() +{ + HANDLE_INIT_CLEANUP_DATATAGS("filetags.qbs"); + QFETCH(int, numberOfGroups); + QFETCH(QStringList, expectedFileTags); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product; + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + QVERIFY(product = products.value(productName)); + QCOMPARE(product->groups.count(), numberOfGroups); + GroupPtr group = product->groups.last(); + QVERIFY(group); + QCOMPARE(group->files.count(), 1); + SourceArtifactConstPtr sourceFile = group->files.first(); + QStringList fileTags = sourceFile->fileTags.toStringList(); + fileTags.sort(); + QCOMPARE(fileTags, expectedFileTags); +} + +void TestLanguage::wildcards_data() +{ + QTest::addColumn<bool>("useGroup"); + QTest::addColumn<QStringList>("filesToCreate"); + QTest::addColumn<QString>("projectFileSubDir"); + QTest::addColumn<QString>("prefix"); + QTest::addColumn<QStringList>("patterns"); + QTest::addColumn<QStringList>("excludePatterns"); + QTest::addColumn<QStringList>("expected"); + + const bool useGroup = true; + for (int i = 0; i <= 1; ++i) { + const bool useGroup = i; + const QByteArray dataTagSuffix = useGroup ? " group" : " nogroup"; + QTest::newRow(QByteArray("simple 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "foo.h" << "bar.h"); + QTest::newRow(QByteArray("simple 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "foo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("simple 3") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("exclude 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << (QStringList() << "bar*") + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("exclude 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*") + << (QStringList() << "*.qbs") + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("non-recursive") + dataTagSuffix) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/*") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp"); + QTest::newRow(QByteArray("absolute paths") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << m_wildcardsTestDirPath + "/?oo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("relative paths with dotdot") + dataTagSuffix) + << useGroup + << (QStringList() << "bar.h" << "bar.cpp") + << QString("TheLaughingLlama") + << QString() + << (QStringList() << "../bar.*") + << QStringList() + << (QStringList() << "bar.h" << "bar.cpp"); + } + QTest::newRow(QByteArray("recursive 1")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("recursive 2")) + << useGroup + << (QStringList() + << "d/1.h" << "b/d/1.h" << "b/c/d/1.h" + << "d/e/1.h" << "b/d/e/1.h" << "b/c/d/e/1.h" + << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h" + << "a/d/e/1.h" << "a/b/d/e/1.h" << "a/b/c/d/e/1.h" + << "a/d/1.cpp" << "a/b/d/1.cpp" << "a/b/c/d/1.h" + << "a/d/e/1.cpp" << "a/b/d/e/1.cpp" << "a/b/c/d/e/1.cpp") + << QString() + << QString() + << (QStringList() << "a/**/d/*.h") + << QStringList() + << (QStringList() << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h"); + QTest::newRow(QByteArray("recursive 3")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**/**/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("prefix")) + << useGroup + << (QStringList() << "subdir/foo.h" << "subdir/foo.cpp" << "subdir/bar.h" + << "subdir/bar.cpp") + << QString() + << QString("subdir/") + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "subdir/foo.h" << "subdir/bar.h"); +} + +void TestLanguage::wildcards() +{ + QFETCH(bool, useGroup); + QFETCH(QStringList, filesToCreate); + QFETCH(QString, projectFileSubDir); + QFETCH(QString, prefix); + QFETCH(QStringList, patterns); + QFETCH(QStringList, excludePatterns); + QFETCH(QStringList, expected); + + // create test directory + QDir::setCurrent(QDir::tempPath()); + { + QString errorMessage; + if (QFile::exists(m_wildcardsTestDirPath)) { + if (!removeDirectoryWithContents(m_wildcardsTestDirPath, &errorMessage)) { + qDebug() << errorMessage; + QVERIFY2(false, "removeDirectoryWithContents failed"); + } + } + QVERIFY(QDir().mkdir(m_wildcardsTestDirPath)); + } + + // create project file + const QString groupName = "Keks"; + QString dataTag = QString::fromLocal8Bit(QTest::currentDataTag()); + dataTag.replace(' ', '_'); + if (!projectFileSubDir.isEmpty()) { + if (!projectFileSubDir.startsWith('/')) + projectFileSubDir.prepend('/'); + if (projectFileSubDir.endsWith('/')) + projectFileSubDir.chop(1); + QVERIFY(QDir().mkpath(m_wildcardsTestDirPath + projectFileSubDir)); + } + const QString projectFilePath = m_wildcardsTestDirPath + projectFileSubDir + "/test_" + dataTag + + ".qbs"; + { + QFile projectFile(projectFilePath); + QVERIFY(projectFile.open(QIODevice::WriteOnly)); + QTextStream s(&projectFile); + s << "import qbs.base 1.0" << endl << endl + << "Application {" << endl + << " name: \"MyProduct\"" << endl; + if (useGroup) { + s << " Group {" << endl + << " name: " << toJSLiteral(groupName) << endl; + } + if (!prefix.isEmpty()) + s << " prefix: " << toJSLiteral(prefix) << endl; + if (!patterns.isEmpty()) + s << " files: " << toJSLiteral(patterns) << endl; + if (!excludePatterns.isEmpty()) + s << " excludeFiles: " << toJSLiteral(excludePatterns) << endl; + if (useGroup) + s << " }" << endl; + s << "}" << endl << endl; + } + + // create files + foreach (QString filePath, filesToCreate) { + filePath.prepend(m_wildcardsTestDirPath + '/'); + QFileInfo fi(filePath); + if (!QDir(fi.path()).exists()) + QVERIFY(QDir().mkpath(fi.path())); + QFile file(filePath); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + + // read the project + bool exceptionCaught = false; + ResolvedProductPtr product; + try { + defaultParameters.setProjectFilePath(projectFilePath); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + const QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + product = products.value("MyProduct"); + QVERIFY(product); + GroupPtr group; + if (useGroup) { + QCOMPARE(product->groups.count(), 2); + foreach (const GroupPtr &rg, product->groups) { + if (rg->name == groupName) { + group = rg; + break; + } + } + } else { + QCOMPARE(product->groups.count(), 1); + group = product->groups.first(); + } + QVERIFY(group); + QCOMPARE(group->files.count(), 0); + SourceWildCards::Ptr wildcards = group->wildcards; + QVERIFY(wildcards); + QStringList actualFilePaths; + foreach (const SourceArtifactConstPtr &artifact, wildcards->files) { + QString str = artifact->absoluteFilePath; + int idx = str.indexOf(m_wildcardsTestDirPath); + if (idx != -1) + str.remove(0, idx + m_wildcardsTestDirPath.count() + 1); + actualFilePaths << str; + } + actualFilePaths.sort(); + expected.sort(); + QCOMPARE(actualFilePaths, expected); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/tst_language.h b/src/lib/corelib/language/tst_language.h new file mode 100644 index 000000000..7e3ce5df5 --- /dev/null +++ b/src/lib/corelib/language/tst_language.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef TST_LANGUAGE_H +#define TST_LANGUAGE_H + +#include <language/forward_decls.h> +#include <language/loader.h> +#include <logging/ilogsink.h> +#include <tools/setupprojectparameters.h> +#include <tools/qbs_export.h> +#include <QtTest> + +namespace qbs { +namespace Internal { + +class QBS_EXPORT TestLanguage : public QObject +{ + Q_OBJECT +public: + TestLanguage(ILogSink *logSink); + ~TestLanguage(); + +private: + ILogSink *m_logSink; + Logger m_logger; + ScriptEngine *m_engine; + Loader *loader; + TopLevelProjectPtr project; + SetupProjectParameters defaultParameters; + const QString m_wildcardsTestDirPath; + + QHash<QString, ResolvedProductPtr> productsFromProject(ResolvedProjectPtr project); + ResolvedModuleConstPtr findModuleByName(ResolvedProductPtr product, const QString &name); + QVariant productPropertyValue(ResolvedProductPtr product, QString propertyName); + void handleInitCleanupDataTags(const char *projectFileName, bool *handled); + QString buildDir(const SetupProjectParameters ¶ms) const; + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void baseProperty(); + void buildConfigStringListSyntax(); + void builtinFunctionInSearchPathsProperty(); + void canonicalArchitecture(); + void conditionalDepends(); + void environmentVariable(); + void erroneousFiles_data(); + void erroneousFiles(); + void exports(); + void fileContextProperties(); + void getNativeSetting(); + void groupConditions_data(); + void groupConditions(); + void groupName(); + void homeDirectory(); + void identifierSearch_data(); + void identifierSearch(); + void idUsage(); + void invalidBindingInDisabledItem(); + void itemPrototype(); + void itemScope(); + void jsExtensions(); + void jsImportUsedInMultipleScopes_data(); + void jsImportUsedInMultipleScopes(); + void moduleProperties_data(); + void moduleProperties(); + void moduleScope(); + void modules_data(); + void modules(); + void outerInGroup(); + void pathProperties(); + void profileValuesAndOverriddenValues(); + void productConditions(); + void productDirectories(); + void propertiesBlocks_data(); + void propertiesBlocks(); + void fileTags_data(); + void fileTags(); + void wildcards_data(); + void wildcards(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // TST_LANGUAGE_H diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp new file mode 100644 index 000000000..e9b755f2e --- /dev/null +++ b/src/lib/corelib/language/value.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 "value.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +Value::Value(Type t) + : m_type(t) +{ +} + +Value::~Value() +{ +} + + +JSSourceValue::JSSourceValue() + : Value(JSSourceValueType) + , m_sourceUsesBase(false) + , m_sourceUsesOuter(false) + , m_hasFunctionForm(false) +{ +} + +JSSourceValuePtr JSSourceValue::create() +{ + return JSSourceValuePtr(new JSSourceValue); +} + +JSSourceValue::~JSSourceValue() +{ +} + + +ItemValue::ItemValue(Item *item) + : Value(ItemValueType) + , m_item(item) +{ +} + +ItemValuePtr ItemValue::create(Item *item) +{ + return ItemValuePtr(new ItemValue(item)); +} + +ItemValue::~ItemValue() +{ +} + +VariantValue::VariantValue(const QVariant &v) + : Value(VariantValueType) + , m_value(v) +{ +} + +VariantValuePtr VariantValue::create(const QVariant &v) +{ + return VariantValuePtr(new VariantValue(v)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h new file mode 100644 index 000000000..92313a98b --- /dev/null +++ b/src/lib/corelib/language/value.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QBS_VALUE_H +#define QBS_VALUE_H + +#include "filecontext.h" +#include "item.h" +#include <tools/codelocation.h> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class ValueHandler; + +class Value +{ +public: + enum Type + { + JSSourceValueType, + ItemValueType, + VariantValueType, + BuiltinValueType + }; + + Value(Type t); + virtual ~Value(); + + Type type() const { return m_type; } + virtual void apply(ValueHandler *) = 0; + virtual CodeLocation location() const { return CodeLocation(); } + +private: + Type m_type; +}; + +class ValueHandler +{ +public: + virtual void handle(JSSourceValue *value) = 0; + virtual void handle(ItemValue *value) = 0; + virtual void handle(VariantValue *value) = 0; + virtual void handle(BuiltinValue *value) = 0; +}; + +class JSSourceValue : public Value +{ + friend class ItemReaderASTVisitor; + JSSourceValue(); +public: + static JSSourceValuePtr create(); + ~JSSourceValue(); + + void apply(ValueHandler *handler) { handler->handle(this); } + + void setSourceCode(const QString &sourceCode) { m_sourceCode = sourceCode; } + const QString &sourceCode() const { return m_sourceCode; } + + void setLocation(const CodeLocation &location) { m_location = location; } + CodeLocation location() const { return m_location; } + + void setFile(const FileContextPtr &file) { m_file = file; } + const FileContextPtr &file() const { return m_file; } + + bool sourceUsesBase() const { return m_sourceUsesBase; } + bool sourceUsesOuter() const { return m_sourceUsesOuter; } + bool hasFunctionForm() const { return m_hasFunctionForm; } + + const JSSourceValuePtr &baseValue() const { return m_baseValue; } + void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; } + + struct Alternative + { + QString condition; + const Item *conditionScopeItem; + JSSourceValuePtr value; + }; + + const QList<Alternative> &alternatives() const { return m_alternatives; } + void setAlternatives(const QList<Alternative> &alternatives) { m_alternatives = alternatives; } + void addAlternative(const Alternative &alternative) { m_alternatives.append(alternative); } + +private: + QString m_sourceCode; + CodeLocation m_location; + FileContextPtr m_file; + bool m_sourceUsesBase; + bool m_sourceUsesOuter; + bool m_hasFunctionForm; + JSSourceValuePtr m_baseValue; + QList<Alternative> m_alternatives; +}; + +class Item; + +class ItemValue : public Value +{ + ItemValue(Item *item); +public: + static ItemValuePtr create(Item *item = 0); + ~ItemValue(); + + void apply(ValueHandler *handler) { handler->handle(this); } + Item *item() const; + void setItem(Item *ptr); + +private: + Item *m_item; +}; + +inline Item *ItemValue::item() const +{ + return m_item; +} + +inline void ItemValue::setItem(Item *ptr) +{ + m_item = ptr; +} + + +class VariantValue : public Value +{ + VariantValue(const QVariant &v); +public: + static VariantValuePtr create(const QVariant &v = QVariant()); + + void apply(ValueHandler *handler) { handler->handle(this); } + + void setValue(const QVariant &v) { m_value = v; } + const QVariant &value() const { return m_value; } + +private: + QVariant m_value; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_VALUE_H |