aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/language
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2014-01-09 17:50:40 +0100
committerJoerg Bornemann <joerg.bornemann@digia.com>2014-01-10 18:11:22 +0100
commit81af9acaa295a574c1cb5e6714725197dac7f530 (patch)
treecc8c94467f49a7d267e5249f624874feecc7eed4 /src/lib/corelib/language
parent2fe25eb3f20ffb4e58cb559f2bcb9950c963290a (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')
-rw-r--r--src/lib/corelib/language/artifactproperties.cpp65
-rw-r--r--src/lib/corelib/language/artifactproperties.h69
-rw-r--r--src/lib/corelib/language/asttools.cpp58
-rw-r--r--src/lib/corelib/language/asttools.h47
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp401
-rw-r--r--src/lib/corelib/language/builtindeclarations.h78
-rw-r--r--src/lib/corelib/language/builtinvalue.cpp47
-rw-r--r--src/lib/corelib/language/builtinvalue.h65
-rw-r--r--src/lib/corelib/language/evaluationdata.h71
-rw-r--r--src/lib/corelib/language/evaluator.cpp221
-rw-r--r--src/lib/corelib/language/evaluator.h88
-rw-r--r--src/lib/corelib/language/evaluatorscriptclass.cpp577
-rw-r--r--src/lib/corelib/language/evaluatorscriptclass.h98
-rw-r--r--src/lib/corelib/language/filecontext.cpp52
-rw-r--r--src/lib/corelib/language/filecontext.h87
-rw-r--r--src/lib/corelib/language/filetags.cpp106
-rw-r--r--src/lib/corelib/language/filetags.h79
-rw-r--r--src/lib/corelib/language/forward_decls.h120
-rw-r--r--src/lib/corelib/language/functiondeclaration.h61
-rw-r--r--src/lib/corelib/language/identifiersearch.cpp69
-rw-r--r--src/lib/corelib/language/identifiersearch.h59
-rw-r--r--src/lib/corelib/language/importversion.cpp77
-rw-r--r--src/lib/corelib/language/importversion.h58
-rw-r--r--src/lib/corelib/language/item.cpp158
-rw-r--r--src/lib/corelib/language/item.h282
-rw-r--r--src/lib/corelib/language/itemdeclaration.cpp59
-rw-r--r--src/lib/corelib/language/itemdeclaration.h67
-rw-r--r--src/lib/corelib/language/itemobserver.h50
-rw-r--r--src/lib/corelib/language/itempool.cpp54
-rw-r--r--src/lib/corelib/language/itempool.h60
-rw-r--r--src/lib/corelib/language/itemreader.cpp199
-rw-r--r--src/lib/corelib/language/itemreader.h110
-rw-r--r--src/lib/corelib/language/itemreaderastvisitor.cpp643
-rw-r--r--src/lib/corelib/language/itemreaderastvisitor.h93
-rw-r--r--src/lib/corelib/language/jsimports.h65
-rw-r--r--src/lib/corelib/language/language.cpp1124
-rw-r--r--src/lib/corelib/language/language.h464
-rw-r--r--src/lib/corelib/language/language.pri70
-rw-r--r--src/lib/corelib/language/loader.cpp122
-rw-r--r--src/lib/corelib/language/loader.h72
-rw-r--r--src/lib/corelib/language/moduleloader.cpp1143
-rw-r--r--src/lib/corelib/language/moduleloader.h213
-rw-r--r--src/lib/corelib/language/preparescriptobserver.cpp62
-rw-r--r--src/lib/corelib/language/preparescriptobserver.h57
-rw-r--r--src/lib/corelib/language/projectresolver.cpp999
-rw-r--r--src/lib/corelib/language/projectresolver.h142
-rw-r--r--src/lib/corelib/language/property.h83
-rw-r--r--src/lib/corelib/language/propertydeclaration.cpp77
-rw-r--r--src/lib/corelib/language/propertydeclaration.h84
-rw-r--r--src/lib/corelib/language/propertymapinternal.cpp103
-rw-r--r--src/lib/corelib/language/propertymapinternal.h64
-rw-r--r--src/lib/corelib/language/scriptengine.cpp314
-rw-r--r--src/lib/corelib/language/scriptengine.h139
-rw-r--r--src/lib/corelib/language/scriptpropertyobserver.h48
-rw-r--r--src/lib/corelib/language/testdata/Banana1
-rw-r--r--src/lib/corelib/language/testdata/aboutdialog.cpp0
-rw-r--r--src/lib/corelib/language/testdata/baseproperty.qbs7
-rw-r--r--src/lib/corelib/language/testdata/baseproperty_base.qbs4
-rw-r--r--src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs3
-rw-r--r--src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs8
-rw-r--r--src/lib/corelib/language/testdata/canonicalArchitecture.qbs3
-rw-r--r--src/lib/corelib/language/testdata/conditionaldepends.qbs67
-rw-r--r--src/lib/corelib/language/testdata/conditionaldepends_base.qbs10
-rw-r--r--src/lib/corelib/language/testdata/drawline.asm0
-rw-r--r--src/lib/corelib/language/testdata/environmentvariable.qbs3
-rw-r--r--src/lib/corelib/language/testdata/erroneous/importloop1.qbs5
-rw-r--r--src/lib/corelib/language/testdata/erroneous/importloop2.qbs5
-rw-r--r--src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/invalid_file.qbs5
-rw-r--r--src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs5
-rw-r--r--src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs3
-rw-r--r--src/lib/corelib/language/testdata/erroneous/main.cpp1
-rw-r--r--src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs4
-rw-r--r--src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs8
-rw-r--r--src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs7
-rw-r--r--src/lib/corelib/language/testdata/erroneous/references_cycle.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs4
-rw-r--r--src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs3
-rw-r--r--src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs8
-rw-r--r--src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs8
-rw-r--r--src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs8
-rw-r--r--src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs7
-rw-r--r--src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs6
-rw-r--r--src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs3
-rw-r--r--src/lib/corelib/language/testdata/erroneous/unknown_module.qbs3
-rw-r--r--src/lib/corelib/language/testdata/exports.qbs49
-rw-r--r--src/lib/corelib/language/testdata/exports_product.qbs7
-rw-r--r--src/lib/corelib/language/testdata/filecontextproperties.qbs5
-rw-r--r--src/lib/corelib/language/testdata/filetags.qbs73
-rw-r--r--src/lib/corelib/language/testdata/getNativeSetting.qbs23
-rw-r--r--src/lib/corelib/language/testdata/groupconditions.qbs53
-rw-r--r--src/lib/corelib/language/testdata/groupname.qbs20
-rw-r--r--src/lib/corelib/language/testdata/homeDirectory.qbs18
-rw-r--r--src/lib/corelib/language/testdata/idusage.qbs20
-rw-r--r--src/lib/corelib/language/testdata/idusagebase.qbs5
-rw-r--r--src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs16
-rw-r--r--src/lib/corelib/language/testdata/jsextensions.js64
-rw-r--r--src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js12
-rw-r--r--src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs7
-rw-r--r--src/lib/corelib/language/testdata/main.cpp0
-rw-r--r--src/lib/corelib/language/testdata/moduleproperties.qbs21
-rw-r--r--src/lib/corelib/language/testdata/modules.qbs34
-rw-r--r--src/lib/corelib/language/testdata/modules/dummy/dummy.qbs8
-rw-r--r--src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs4
-rw-r--r--src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs7
-rw-r--r--src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs14
-rw-r--r--src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs9
-rw-r--r--src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs9
-rw-r--r--src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs12
-rw-r--r--src/lib/corelib/language/testdata/modulescope.qbs14
-rw-r--r--src/lib/corelib/language/testdata/modulescope_base.qbs6
-rw-r--r--src/lib/corelib/language/testdata/narf0
-rw-r--r--src/lib/corelib/language/testdata/narf.zort0
-rw-r--r--src/lib/corelib/language/testdata/nativesettings.ini1
-rw-r--r--src/lib/corelib/language/testdata/outerInGroup.qbs14
-rw-r--r--src/lib/corelib/language/testdata/pathproperties.qbs9
-rw-r--r--src/lib/corelib/language/testdata/productconditions.qbs19
-rw-r--r--src/lib/corelib/language/testdata/productdirectories.qbs5
-rw-r--r--src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs19
-rw-r--r--src/lib/corelib/language/testdata/propertiesblocks.qbs150
-rw-r--r--src/lib/corelib/language/testdata/propertiesblocks_base.qbs11
-rw-r--r--src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs4
-rw-r--r--src/lib/corelib/language/testdata/zort0
-rw-r--r--src/lib/corelib/language/tst_language.cpp1439
-rw-r--r--src/lib/corelib/language/tst_language.h114
-rw-r--r--src/lib/corelib/language/value.cpp91
-rw-r--r--src/lib/corelib/language/value.h168
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 &regExp, 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 &parameters)
+{
+ 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 &parameters);
+ 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 &params) 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 &params) 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