diff options
author | Christian Kandeler <christian.kandeler@digia.com> | 2014-01-09 17:50:40 +0100 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@digia.com> | 2014-01-10 18:11:22 +0100 |
commit | 81af9acaa295a574c1cb5e6714725197dac7f530 (patch) | |
tree | cc8c94467f49a7d267e5249f624874feecc7eed4 /src/lib/corelib | |
parent | 2fe25eb3f20ffb4e58cb559f2bcb9950c963290a (diff) |
Move Qt profile setup into a dedicated library.
Otherwise all changes to the implementation will have to be duplicated
in IDEs.
Change-Id: I61e6d4fa1ee9b724eb5d9de9f233dc915a6c8bc3
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
Diffstat (limited to 'src/lib/corelib')
296 files changed, 49425 insertions, 0 deletions
diff --git a/src/lib/corelib/api/api.pri b/src/lib/corelib/api/api.pri new file mode 100644 index 000000000..8a0c93237 --- /dev/null +++ b/src/lib/corelib/api/api.pri @@ -0,0 +1,27 @@ +HEADERS += \ + $$PWD/changeset.h \ + $$PWD/internaljobs.h \ + $$PWD/projectdata.h \ + $$PWD/projectfileupdater.h \ + $$PWD/runenvironment.h \ + $$PWD/jobs.h \ + $$PWD/project.h \ + $$PWD/propertymap_p.h \ + $$PWD/projectdata_p.h \ + $$PWD/qmljsrewriter.h + +SOURCES += \ + $$PWD/changeset.cpp \ + $$PWD/internaljobs.cpp \ + $$PWD/projectfileupdater.cpp \ + $$PWD/runenvironment.cpp \ + $$PWD/projectdata.cpp \ + $$PWD/jobs.cpp \ + $$PWD/project.cpp \ + $$PWD/qmljsrewriter.cpp + +!qbs_no_dev_install { + api_headers.files = $$PWD/projectdata.h $$PWD/runenvironment.h $$PWD/jobs.h $$PWD/project.h + api_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/api + INSTALLS += api_headers +} diff --git a/src/lib/corelib/api/changeset.cpp b/src/lib/corelib/api/changeset.cpp new file mode 100644 index 000000000..bbde289a6 --- /dev/null +++ b/src/lib/corelib/api/changeset.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** 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 "changeset.h" + +#include <QTextCursor> + +namespace QbsQmlJS { + +ChangeSet::ChangeSet() + : m_string(0), m_cursor(0), m_error(false) +{ +} + +ChangeSet::ChangeSet(const QList<EditOp> &operations) + : m_string(0), m_cursor(0), m_operationList(operations), m_error(false) +{ +} + +static bool overlaps(int posA, int lengthA, int posB, int lengthB) { + if (lengthB > 0) { + return + // right edge of B contained in A + (posA < posB + lengthB && posA + lengthA >= posB + lengthB) + // left edge of B contained in A + || (posA <= posB && posA + lengthA > posB) + // A contained in B + || (posB < posA && posB + lengthB > posA + lengthA); + } else { + return (posB > posA && posB < posA + lengthA); + } +} + +bool ChangeSet::hasOverlap(int pos, int length) +{ + QListIterator<EditOp> i(m_operationList); + while (i.hasNext()) { + const EditOp &cmd = i.next(); + + switch (cmd.type) { + case EditOp::Replace: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + break; + + case EditOp::Move: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (cmd.pos2 > pos && cmd.pos2 < pos + length) + return true; + break; + + case EditOp::Insert: + if (cmd.pos1 > pos && cmd.pos1 < pos + length) + return true; + break; + + case EditOp::Remove: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + break; + + case EditOp::Flip: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (overlaps(pos, length, cmd.pos2, cmd.length2)) + return true; + break; + + case EditOp::Copy: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (cmd.pos2 > pos && cmd.pos2 < pos + length) + return true; + break; + + case EditOp::Unset: + break; + } + } + + return false; +} + +bool ChangeSet::isEmpty() const +{ + return m_operationList.isEmpty(); +} + +QList<ChangeSet::EditOp> ChangeSet::operationList() const +{ + return m_operationList; +} + +void ChangeSet::clear() +{ + m_string = 0; + m_cursor = 0; + m_operationList.clear(); + m_error = false; +} + +bool ChangeSet::replace_helper(int pos, int length, const QString &replacement) +{ + if (hasOverlap(pos, length)) + m_error = true; + + EditOp cmd(EditOp::Replace); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.text = replacement; + m_operationList += cmd; + + return !m_error; +} + +bool ChangeSet::move_helper(int pos, int length, int to) +{ + if (hasOverlap(pos, length) + || hasOverlap(to, 0) + || overlaps(pos, length, to, 0)) + m_error = true; + + EditOp cmd(EditOp::Move); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.pos2 = to; + m_operationList += cmd; + + return !m_error; +} + +bool ChangeSet::insert(int pos, const QString &text) +{ + Q_ASSERT(pos >= 0); + + if (hasOverlap(pos, 0)) + m_error = true; + + EditOp cmd(EditOp::Insert); + cmd.pos1 = pos; + cmd.text = text; + m_operationList += cmd; + + return !m_error; +} + +bool ChangeSet::replace(const Range &range, const QString &replacement) +{ return replace(range.start, range.end, replacement); } + +bool ChangeSet::remove(const Range &range) +{ return remove(range.start, range.end); } + +bool ChangeSet::move(const Range &range, int to) +{ return move(range.start, range.end, to); } + +bool ChangeSet::flip(const Range &range1, const Range &range2) +{ return flip(range1.start, range1.end, range2.start, range2.end); } + +bool ChangeSet::copy(const Range &range, int to) +{ return copy(range.start, range.end, to); } + +bool ChangeSet::replace(int start, int end, const QString &replacement) +{ return replace_helper(start, end - start, replacement); } + +bool ChangeSet::remove(int start, int end) +{ return remove_helper(start, end - start); } + +bool ChangeSet::move(int start, int end, int to) +{ return move_helper(start, end - start, to); } + +bool ChangeSet::flip(int start1, int end1, int start2, int end2) +{ return flip_helper(start1, end1 - start1, start2, end2 - start2); } + +bool ChangeSet::copy(int start, int end, int to) +{ return copy_helper(start, end - start, to); } + +bool ChangeSet::remove_helper(int pos, int length) +{ + if (hasOverlap(pos, length)) + m_error = true; + + EditOp cmd(EditOp::Remove); + cmd.pos1 = pos; + cmd.length1 = length; + m_operationList += cmd; + + return !m_error; +} + +bool ChangeSet::flip_helper(int pos1, int length1, int pos2, int length2) +{ + if (hasOverlap(pos1, length1) + || hasOverlap(pos2, length2) + || overlaps(pos1, length1, pos2, length2)) + m_error = true; + + EditOp cmd(EditOp::Flip); + cmd.pos1 = pos1; + cmd.length1 = length1; + cmd.pos2 = pos2; + cmd.length2 = length2; + m_operationList += cmd; + + return !m_error; +} + +bool ChangeSet::copy_helper(int pos, int length, int to) +{ + if (hasOverlap(pos, length) + || hasOverlap(to, 0) + || overlaps(pos, length, to, 0)) + m_error = true; + + EditOp cmd(EditOp::Copy); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.pos2 = to; + m_operationList += cmd; + + return !m_error; +} + +void ChangeSet::doReplace(const EditOp &replace_helper, QList<EditOp> *replaceList) +{ + Q_ASSERT(replace_helper.type == EditOp::Replace); + + { + QMutableListIterator<EditOp> i(*replaceList); + while (i.hasNext()) { + EditOp &c = i.next(); + if (replace_helper.pos1 <= c.pos1) + c.pos1 += replace_helper.text.size(); + if (replace_helper.pos1 < c.pos1) + c.pos1 -= replace_helper.length1; + } + } + + if (m_string) { + m_string->replace(replace_helper.pos1, replace_helper.length1, replace_helper.text); + } else if (m_cursor) { + m_cursor->setPosition(replace_helper.pos1); + m_cursor->setPosition(replace_helper.pos1 + replace_helper.length1, QTextCursor::KeepAnchor); + m_cursor->insertText(replace_helper.text); + } +} + +void ChangeSet::convertToReplace(const EditOp &op, QList<EditOp> *replaceList) +{ + EditOp replace1(EditOp::Replace); + EditOp replace2(EditOp::Replace); + + switch (op.type) { + case EditOp::Replace: + replaceList->append(op); + break; + + case EditOp::Move: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replaceList->append(replace1); + + replace2.pos1 = op.pos2; + replace2.text = textAt(op.pos1, op.length1); + replaceList->append(replace2); + break; + + case EditOp::Insert: + replace1.pos1 = op.pos1; + replace1.text = op.text; + replaceList->append(replace1); + break; + + case EditOp::Remove: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replaceList->append(replace1); + break; + + case EditOp::Flip: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replace1.text = textAt(op.pos2, op.length2); + replaceList->append(replace1); + + replace2.pos1 = op.pos2; + replace2.length1 = op.length2; + replace2.text = textAt(op.pos1, op.length1); + replaceList->append(replace2); + break; + + case EditOp::Copy: + replace1.pos1 = op.pos2; + replace1.text = textAt(op.pos1, op.length1); + replaceList->append(replace1); + break; + + case EditOp::Unset: + break; + } +} + +bool ChangeSet::hadErrors() +{ + return m_error; +} + +void ChangeSet::apply(QString *s) +{ + m_string = s; + apply_helper(); + m_string = 0; +} + +void ChangeSet::apply(QTextCursor *textCursor) +{ + m_cursor = textCursor; + apply_helper(); + m_cursor = 0; +} + +QString ChangeSet::textAt(int pos, int length) +{ + if (m_string) { + return m_string->mid(pos, length); + } else if (m_cursor) { + m_cursor->setPosition(pos); + m_cursor->setPosition(pos + length, QTextCursor::KeepAnchor); + return m_cursor->selectedText(); + } + return QString(); +} + +void ChangeSet::apply_helper() +{ + // convert all ops to replace + QList<EditOp> replaceList; + { + while (!m_operationList.isEmpty()) { + const EditOp cmd(m_operationList.first()); + m_operationList.removeFirst(); + convertToReplace(cmd, &replaceList); + } + } + + // execute replaces + if (m_cursor) + m_cursor->beginEditBlock(); + + while (!replaceList.isEmpty()) { + const EditOp cmd(replaceList.first()); + replaceList.removeFirst(); + doReplace(cmd, &replaceList); + } + + if (m_cursor) + m_cursor->endEditBlock(); +} + +} // namespace Internal { + diff --git a/src/lib/corelib/api/changeset.h b/src/lib/corelib/api/changeset.h new file mode 100644 index 000000000..b1674d392 --- /dev/null +++ b/src/lib/corelib/api/changeset.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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_CHANGESET_H +#define QBS_CHANGESET_H + +#include <QString> +#include <QList> + +QT_FORWARD_DECLARE_CLASS(QTextCursor) + +namespace QbsQmlJS { + +class ChangeSet +{ +public: + struct EditOp { + enum Type + { + Unset, + Replace, + Move, + Insert, + Remove, + Flip, + Copy + }; + + EditOp(): type(Unset), pos1(0), pos2(0), length1(0), length2(0) {} + EditOp(Type t): type(t), pos1(0), pos2(0), length1(0), length2(0) {} + + Type type; + int pos1; + int pos2; + int length1; + int length2; + QString text; + }; + + struct Range { + Range() + : start(0), end(0) {} + + Range(int start, int end) + : start(start), end(end) {} + + int start; + int end; + }; + +public: + ChangeSet(); + ChangeSet(const QList<EditOp> &operations); + + bool isEmpty() const; + + QList<EditOp> operationList() const; + + void clear(); + + bool replace(const Range &range, const QString &replacement); + bool remove(const Range &range); + bool move(const Range &range, int to); + bool flip(const Range &range1, const Range &range2); + bool copy(const Range &range, int to); + bool replace(int start, int end, const QString &replacement); + bool remove(int start, int end); + bool move(int start, int end, int to); + bool flip(int start1, int end1, int start2, int end2); + bool copy(int start, int end, int to); + bool insert(int pos, const QString &text); + + bool hadErrors(); + + void apply(QString *s); + void apply(QTextCursor *textCursor); + +private: + // length-based API. + bool replace_helper(int pos, int length, const QString &replacement); + bool move_helper(int pos, int length, int to); + bool remove_helper(int pos, int length); + bool flip_helper(int pos1, int length1, int pos2, int length2); + bool copy_helper(int pos, int length, int to); + + bool hasOverlap(int pos, int length); + QString textAt(int pos, int length); + + void doReplace(const EditOp &replace, QList<EditOp> *replaceList); + void convertToReplace(const EditOp &op, QList<EditOp> *replaceList); + + void apply_helper(); + +private: + QString *m_string; + QTextCursor *m_cursor; + + QList<EditOp> m_operationList; + bool m_error; +}; + +} // namespace QbsQmlJS + +#endif // Include guard. diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp new file mode 100644 index 000000000..a28bee72e --- /dev/null +++ b/src/lib/corelib/api/internaljobs.cpp @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** 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 "internaljobs.h" + +#include "jobs.h" + +#include <buildgraph/artifactcleaner.h> +#include <buildgraph/buildgraphloader.h> +#include <buildgraph/productbuilddata.h> +#include <buildgraph/projectbuilddata.h> +#include <buildgraph/executor.h> +#include <buildgraph/productinstaller.h> +#include <buildgraph/rulesevaluationcontext.h> +#include <language/language.h> +#include <language/loader.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/progressobserver.h> +#include <tools/preferences.h> +#include <tools/qbsassert.h> + +#include <QEventLoop> +#include <QScopedPointer> +#include <QTimer> + +namespace qbs { +namespace Internal { + +static void unlockBuildGraph(const TopLevelProjectPtr &project) +{ + QBS_ASSERT(project->locked, return); + project->locked = false; +} + +class JobObserver : public ProgressObserver +{ +public: + JobObserver(InternalJob *job) : m_canceled(false), m_job(job), m_timedLogger(0) { } + ~JobObserver() { delete m_timedLogger; } + + void cancel() { m_canceled = true; } + +private: + void initialize(const QString &task, int maximum) + { + QBS_ASSERT(!m_timedLogger, delete m_timedLogger); + m_timedLogger = new TimedActivityLogger(m_job->logger(), task, QString(), + m_job->timed() ? LoggerInfo : LoggerDebug, m_job->timed()); + m_value = 0; + m_maximum = maximum; + m_canceled = false; + emit m_job->newTaskStarted(task, maximum, m_job); + } + + void setMaximum(int maximum) + { + m_maximum = maximum; + emit m_job->totalEffortChanged(maximum, m_job); + } + + void setProgressValue(int value) + { + //QBS_ASSERT(value >= m_value, qDebug("old value = %d, new value = %d", m_value, value)); + //QBS_ASSERT(value <= m_maximum, qDebug("value = %d, maximum = %d", value, m_maximum)); + m_value = value; + if (value == m_maximum) { + delete m_timedLogger; + m_timedLogger = 0; + } + emit m_job->taskProgress(value, m_job); + } + + int progressValue() { return m_value; } + int maximum() const { return m_maximum; } + bool canceled() const { return m_canceled; } + + int m_value; + int m_maximum; + bool m_canceled; + InternalJob * const m_job; + TimedActivityLogger *m_timedLogger; +}; + + +InternalJob::InternalJob(const Logger &logger, QObject *parent) + : QObject(parent) + , m_observer(new JobObserver(this)) + , m_ownsObserver(true) + , m_logger(logger) + , m_timed(false) +{ +} + +InternalJob::~InternalJob() +{ + if (m_ownsObserver) + delete m_observer; +} + +void InternalJob::cancel() +{ + m_observer->cancel(); +} + +void InternalJob::shareObserverWith(InternalJob *otherJob) +{ + if (m_ownsObserver) { + delete m_observer; + m_ownsObserver = false; + } + m_observer = otherJob->m_observer; +} + +void InternalJob::storeBuildGraph(const TopLevelProjectConstPtr &project) +{ + try { + project->store(logger()); + } catch (const ErrorInfo &error) { + logger().printWarning(error); + } +} + + +/** + * Construct a new thread wrapper for a synchronous job. + * This object takes over ownership of the synchronous job. + */ +InternalJobThreadWrapper::InternalJobThreadWrapper(InternalJob *synchronousJob, QObject *parent) + : InternalJob(synchronousJob->logger(), parent) + , m_job(synchronousJob) + , m_running(false) +{ + synchronousJob->shareObserverWith(this); + m_job->moveToThread(&m_thread); + connect(m_job, SIGNAL(finished(Internal::InternalJob*)), SLOT(handleFinished())); + connect(m_job, SIGNAL(newTaskStarted(QString,int,Internal::InternalJob*)), + SIGNAL(newTaskStarted(QString,int,Internal::InternalJob*))); + connect(m_job, SIGNAL(taskProgress(int,Internal::InternalJob*)), + SIGNAL(taskProgress(int,Internal::InternalJob*))); + connect(m_job, SIGNAL(totalEffortChanged(int,Internal::InternalJob*)), + SIGNAL(totalEffortChanged(int,Internal::InternalJob*))); + m_job->connect(this, SIGNAL(startRequested()), SLOT(start())); +} + +InternalJobThreadWrapper::~InternalJobThreadWrapper() +{ + if (m_running) { + QEventLoop loop; + loop.connect(m_job, SIGNAL(finished(Internal::InternalJob*)), SLOT(quit())); + cancel(); + loop.exec(); + } + m_thread.quit(); + m_thread.wait(); + delete m_job; +} + +void InternalJobThreadWrapper::start() +{ + setTimed(m_job->timed()); + m_thread.start(); + m_running = true; + emit startRequested(); +} + +void InternalJobThreadWrapper::handleFinished() +{ + m_running = false; + setError(m_job->error()); + emit finished(this); +} + + +InternalSetupProjectJob::InternalSetupProjectJob(const Logger &logger) + : InternalJob(logger) +{ +} + +InternalSetupProjectJob::~InternalSetupProjectJob() +{ +} + +void InternalSetupProjectJob::init(const SetupProjectParameters ¶meters) +{ + m_parameters = parameters; + setTimed(parameters.logElapsedTime()); +} + +void InternalSetupProjectJob::reportError(const ErrorInfo &error) +{ + setError(error); + emit finished(this); +} + +TopLevelProjectPtr InternalSetupProjectJob::project() const +{ + return m_project; +} + +void InternalSetupProjectJob::start() +{ + try { + execute(); + } catch (const ErrorInfo &error) { + m_project.clear(); + setError(error); + } + emit finished(this); +} + +void InternalSetupProjectJob::execute() +{ + RulesEvaluationContextPtr evalContext(new RulesEvaluationContext(logger())); + evalContext->setObserver(observer()); + + switch (m_parameters.restoreBehavior()) { + case SetupProjectParameters::ResolveOnly: + resolveProjectFromScratch(evalContext->engine()); + resolveBuildDataFromScratch(evalContext); + setupPlatformEnvironment(); + break; + case SetupProjectParameters::RestoreOnly: + m_project = restoreProject(evalContext).loadedProject; + break; + case SetupProjectParameters::RestoreAndTrackChanges: { + const BuildGraphLoadResult loadResult = restoreProject(evalContext); + m_project = loadResult.newlyResolvedProject; + if (!m_project) + m_project = loadResult.loadedProject; + if (!m_project) { + resolveProjectFromScratch(evalContext->engine()); + resolveBuildDataFromScratch(evalContext); + } else { + QBS_CHECK(m_project->buildData); + } + setupPlatformEnvironment(); + break; + } + } + + if (!m_parameters.dryRun()) + storeBuildGraph(m_project); + + // The evalutation context cannot be re-used for building, which runs in a different thread. + m_project->buildData->evaluationContext.clear(); +} + +void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine) +{ + Loader loader(engine, logger()); + loader.setSearchPaths(m_parameters.searchPaths()); + loader.setProgressObserver(observer()); + m_project = loader.loadProject(m_parameters); + QBS_CHECK(m_project); +} + +void InternalSetupProjectJob::resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext) +{ + TimedActivityLogger resolveLogger(logger(), QLatin1String("Resolving build project")); + BuildDataResolver(logger()).resolveBuildData(m_project, evalContext); +} + +void InternalSetupProjectJob::setupPlatformEnvironment() +{ + const QVariantMap platformEnvironment + = m_parameters.buildConfiguration().value(QLatin1String("environment")).toMap(); + m_project->platformEnvironment = platformEnvironment; +} + +BuildGraphLoadResult InternalSetupProjectJob::restoreProject(const RulesEvaluationContextPtr &evalContext) +{ + BuildGraphLoader bgLoader(m_parameters.environment(), logger()); + const BuildGraphLoadResult loadResult = bgLoader.load(m_parameters, evalContext); + return loadResult; +} + +BuildGraphTouchingJob::BuildGraphTouchingJob(const Logger &logger, QObject *parent) + : InternalJob(logger, parent), m_dryRun(false) +{ +} + +BuildGraphTouchingJob::~BuildGraphTouchingJob() +{ +} + +void BuildGraphTouchingJob::setup(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, bool dryRun) +{ + m_project = project; + m_products = products; + m_dryRun = dryRun; +} + +void BuildGraphTouchingJob::storeBuildGraph() +{ + if (!m_dryRun && !error().isInternalError()) + InternalJob::storeBuildGraph(m_project); +} + +InternalBuildJob::InternalBuildJob(const Logger &logger, QObject *parent) + : BuildGraphTouchingJob(logger, parent), m_executor(0) +{ +} + +void InternalBuildJob::build(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const BuildOptions &buildOptions) +{ + setup(project, products, buildOptions.dryRun()); + setTimed(buildOptions.logElapsedTime()); + + m_executor = new Executor(logger()); + m_executor->setProject(project); + m_executor->setProducts(products); + m_executor->setBuildOptions(buildOptions); + m_executor->setProgressObserver(observer()); + + QThread * const executorThread = new QThread(this); + m_executor->moveToThread(executorThread); + connect(m_executor, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString))); + connect(m_executor, SIGNAL(reportProcessResult(qbs::ProcessResult)), + this, SIGNAL(reportProcessResult(qbs::ProcessResult))); + + connect(executorThread, SIGNAL(started()), m_executor, SLOT(build())); + connect(m_executor, SIGNAL(finished()), SLOT(handleFinished())); + connect(m_executor, SIGNAL(destroyed()), executorThread, SLOT(quit())); + connect(executorThread, SIGNAL(finished()), this, SLOT(emitFinished())); + executorThread->start(); +} + +void InternalBuildJob::handleFinished() +{ + setError(m_executor->error()); + project()->buildData->evaluationContext.clear(); + storeBuildGraph(); + m_executor->deleteLater(); +} + +void InternalBuildJob::emitFinished() +{ + unlockBuildGraph(project()); + emit finished(this); +} + +InternalCleanJob::InternalCleanJob(const Logger &logger, QObject *parent) + : BuildGraphTouchingJob(logger, parent) +{ +} + +void InternalCleanJob::init(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const CleanOptions &options) +{ + setup(project, products, options.dryRun()); + setTimed(options.logElapsedTime()); + m_options = options; +} + +void InternalCleanJob::start() +{ + try { + ArtifactCleaner cleaner(logger(), observer()); + cleaner.cleanup(project(), products(), m_options); + } catch (const ErrorInfo &error) { + setError(error); + } + storeBuildGraph(); + unlockBuildGraph(project()); + emit finished(this); +} + + +InternalInstallJob::InternalInstallJob(const Logger &logger) + : InternalJob(logger) +{ +} + +InternalInstallJob::~InternalInstallJob() +{ +} + +void InternalInstallJob::init(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const InstallOptions &options) +{ + m_project = project; + m_products = products; + m_options = options; + setTimed(options.logElapsedTime()); +} + +void InternalInstallJob::start() +{ + try { + ProductInstaller(m_project, m_products, m_options, observer(), logger()).install(); + } catch (const ErrorInfo &error) { + setError(error); + } + unlockBuildGraph(m_project); + emit finished(this); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/api/internaljobs.h b/src/lib/corelib/api/internaljobs.h new file mode 100644 index 000000000..ef112662f --- /dev/null +++ b/src/lib/corelib/api/internaljobs.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** 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_INTERNALJOBS_H +#define QBS_INTERNALJOBS_H + +#include <buildgraph/forward_decls.h> +#include <language/forward_decls.h> +#include <logging/logger.h> +#include <tools/buildoptions.h> +#include <tools/cleanoptions.h> +#include <tools/installoptions.h> +#include <tools/error.h> +#include <tools/setupprojectparameters.h> + +#include <QList> +#include <QObject> +#include <QThread> + +namespace qbs { +class ProcessResult; +class Settings; + +namespace Internal { +class BuildGraphLoadResult; +class Executor; +class JobObserver; +class ScriptEngine; + +class InternalJob : public QObject +{ + Q_OBJECT + friend class JobObserver; +public: + ~InternalJob(); + + void cancel(); + ErrorInfo error() const { return m_error; } + void setError(const ErrorInfo &error) { m_error = error; } + + Logger logger() const { return m_logger; } + bool timed() const { return m_timed; } + void shareObserverWith(InternalJob *otherJob); + +protected: + explicit InternalJob(const Logger &logger, QObject *parent = 0); + + JobObserver *observer() const { return m_observer; } + void setTimed(bool timed) { m_timed = timed; } + void storeBuildGraph(const TopLevelProjectConstPtr &project); + +signals: + void finished(Internal::InternalJob *job); + void newTaskStarted(const QString &description, int totalEffort, Internal::InternalJob *job); + void totalEffortChanged(int totalEffort, Internal::InternalJob *job); + void taskProgress(int value, Internal::InternalJob *job); + +private: + ErrorInfo m_error; + JobObserver *m_observer; + bool m_ownsObserver; + Logger m_logger; + bool m_timed; +}; + + +class InternalJobThreadWrapper : public InternalJob +{ + Q_OBJECT +public: + InternalJobThreadWrapper(InternalJob *synchronousJob, QObject *parent = 0); + ~InternalJobThreadWrapper(); + + void start(); + InternalJob *synchronousJob() const { return m_job; } + +signals: + void startRequested(); + +private slots: + void handleFinished(); + +private: + QThread m_thread; + InternalJob *m_job; + bool m_running; +}; + +class InternalSetupProjectJob : public InternalJob +{ + Q_OBJECT +public: + InternalSetupProjectJob(const Logger &logger); + ~InternalSetupProjectJob(); + + void init(const SetupProjectParameters ¶meters); + void reportError(const ErrorInfo &error); + + TopLevelProjectPtr project() const; + +private slots: + void start(); + +private: + void resolveProjectFromScratch(Internal::ScriptEngine *engine); + void resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext); + void setupPlatformEnvironment(); + BuildGraphLoadResult restoreProject(const RulesEvaluationContextPtr &evalContext); + void execute(); + + TopLevelProjectPtr m_project; + SetupProjectParameters m_parameters; +}; + + +class BuildGraphTouchingJob : public InternalJob +{ + Q_OBJECT +public: + const QList<ResolvedProductPtr> &products() const { return m_products; } + const TopLevelProjectPtr &project() const { return m_project; } + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + +protected: + BuildGraphTouchingJob(const Logger &logger, QObject *parent = 0); + ~BuildGraphTouchingJob(); + + void setup(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + bool dryRun); + void storeBuildGraph(); + +private: + TopLevelProjectPtr m_project; + QList<ResolvedProductPtr> m_products; + bool m_dryRun; +}; + + +class InternalBuildJob : public BuildGraphTouchingJob +{ + Q_OBJECT +public: + InternalBuildJob(const Logger &logger, QObject *parent = 0); + + void build(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const BuildOptions &buildOptions); + +private slots: + void handleFinished(); + void emitFinished(); + +private: + Executor *m_executor; +}; + + +class InternalCleanJob : public BuildGraphTouchingJob +{ + Q_OBJECT +public: + InternalCleanJob(const Logger &logger, QObject *parent = 0); + + void init(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const CleanOptions &options); + +private slots: + void start(); + +private: + CleanOptions m_options; +}; + + +class InternalInstallJob : public InternalJob +{ + Q_OBJECT +public: + InternalInstallJob(const Logger &logger); + ~InternalInstallJob(); + + void init(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const InstallOptions &options); + +private slots: + void start(); + +private: + TopLevelProjectPtr m_project; + QList<ResolvedProductPtr> m_products; + InstallOptions m_options; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_INTERNALJOBS_H diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp new file mode 100644 index 000000000..d7ac324cb --- /dev/null +++ b/src/lib/corelib/api/jobs.cpp @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** 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 "jobs.h" + +#include "internaljobs.h" +#include "project.h" +#include <language/language.h> +#include <tools/qbsassert.h> + +#include <QMetaObject> + +namespace qbs { +using namespace Internal; + +/*! + * \class AbstractJob + * \brief The \c AbstractJob class represents an operation relating to a \c Project. + * Concrete child classes of \c AbstractJob are created by factory functions in the \c Project + * class. The respective objects represent an operation that is started automatically + * and is considered "running" until the \c finished() signal has been emitted. Afterwards, + * callers can find out whether the operation was successful by calling \c hasError(). While + * the operation is going on, progress information is being provided via \c taskStarted() and + * \c taskProgress. + * Note that though a job is being started automatically by its factory function, you are guaranteed + * to recevieve all signals it emits if you connect to it right after getting the object from the + * creating function. + * \sa Project + */ + +/*! + * \enum AbstractJob::State + * This enum type specifies which states a job can be in. + * \value StateRunning The respective operation is ongoing. + * \value StateCanceling The job has been requested to cancel via \c AbstractJob::cancel(), + * but the \c AbstractJob::finished() signal has not been emitted yet. + * \value StateFinished The operation has finished and the \c AbstractJob::finished() signal + * has been emitted. + */ + + /*! + * \fn AbstractJob::State AbstractJob::state() const + * \brief Returns the current state of the operation. + */ + + /*! + * \fn bool AbstractJob::hasError() const + * \brief Returns true if the operation has finished with an error, otherwise returns false. + * This function should not be called before the \c finished() signal has been emitted. + */ + +/*! + * \fn void AbstractJob::taskStarted(const QString &description, int maximumProgressValue, qbs::AbstractJob *job) + * \brief Indicates that a new task has been started. + * The \a description parameter is a string intended for presentation to a user. + * The \a maximumProgressValue parameter indicates the maximum value to which subsequent values of + * \c taskProgress() will go. + * This signal is typically emitted exactly once for a job that finishes successfully. However, + * operations might emit it several times if they are made up of subtasks whose overall effort + * cannot be determined in advance. + * \sa AbstractJob::taskProgress() + */ + +/*! + * \fn void taskProgress(int newProgressValue, qbs::AbstractJob *job) + * \brief Indicates progress in executing the operation. + * The \a newProgressValue parameter represents the current progress. It is always greater than + * zero, strictly increasing and goes up to the \c maximumProgressValue argument of the last + * call to \c taskStarted(). + * \sa AbstractJob::taskStarted() + */ + + /*! + * \fn void finished(bool success, qbs::AbstractJob *job) + * \brief Indicates that the operation has finished. + * Check the \a success parameter to find out whether everything went fine or an error occurred. + */ + +AbstractJob::AbstractJob(InternalJob *internalJob, QObject *parent) + : QObject(parent), m_internalJob(internalJob) +{ + m_internalJob->setParent(this); + connect(m_internalJob, SIGNAL(newTaskStarted(QString,int,Internal::InternalJob*)), + SLOT(handleTaskStarted(QString,int)), Qt::QueuedConnection); + connect(m_internalJob, SIGNAL(totalEffortChanged(int,Internal::InternalJob*)), + SLOT(handleTotalEffortChanged(int))); + connect(m_internalJob, SIGNAL(taskProgress(int,Internal::InternalJob*)), + SLOT(handleTaskProgress(int)), Qt::QueuedConnection); + connect(m_internalJob, SIGNAL(finished(Internal::InternalJob *)), SLOT(handleFinished())); + m_state = StateRunning; +} + +bool AbstractJob::lockBuildGraph(const TopLevelProjectPtr &project) +{ + // The API is not thread-safe, so we don't need a mutex here, as the API requests come in + // synchronously. + if (project->locked) { + internalJob()->setError(tr("Cannot start a job while another one is in process.")); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(bool, false), + Q_ARG(qbs::AbstractJob *, this)); + return false; + } + project->locked = true; + return true; +} + +/*! + * \brief Destroys the object, canceling the operation if necessary. + */ +AbstractJob::~AbstractJob() +{ + m_internalJob->disconnect(this); + cancel(); +} + +/*! + * \brief Returns the error which caused this operation to fail, if it did fail. + */ +ErrorInfo AbstractJob::error() const +{ + return internalJob()->error(); +} + +/*! + * \brief Cancels this job. + * Note that the job might not finish immediately. If you need to make sure it has actually + * finished, wait for the \c finished() signal. + * \sa AbstractJob::finished(AbstractJob *); + */ +void AbstractJob::cancel() +{ + if (m_state != StateRunning) + return; + m_state = StateCanceling; + internalJob()->cancel(); +} + +void AbstractJob::handleTaskStarted(const QString &description, int maximumProgressValue) +{ + emit taskStarted(description, maximumProgressValue, this); +} + +void AbstractJob::handleTotalEffortChanged(int totalEffort) +{ + emit totalEffortChanged(totalEffort, this); +} + +void AbstractJob::handleTaskProgress(int newProgressValue) +{ + emit taskProgress(newProgressValue, this); +} + +void AbstractJob::handleFinished() +{ + QBS_ASSERT(m_state != StateFinished, return); + m_state = StateFinished; + emit finished(!error().hasError(), this); +} + + +/*! + * \class SetupProjectJob + * \brief The \c SetupProjectJob class represents an operation that reads a qbs project file and + * creates a \c Project object from it. + * Note that this job can emit the \c taskStarted() signal more than once. + * \sa AbstractJob::taskStarted() + */ + +SetupProjectJob::SetupProjectJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalJobThreadWrapper(new InternalSetupProjectJob(logger)), parent) +{ +} + +/*! + * \brief Returns the project resulting from this operation. + * Note that the result is undefined if the job did not finish successfully. + * \sa AbstractJob::hasError() + */ +Project SetupProjectJob::project() const +{ + const InternalJobThreadWrapper * const wrapper + = qobject_cast<InternalJobThreadWrapper *>(internalJob()); + const InternalSetupProjectJob * const job + = qobject_cast<InternalSetupProjectJob *>(wrapper->synchronousJob()); + return Project(job->project(), job->logger()); +} + +void SetupProjectJob::resolve(const SetupProjectParameters ¶meters) +{ + InternalJobThreadWrapper * const wrapper + = qobject_cast<InternalJobThreadWrapper *>(internalJob()); + InternalSetupProjectJob * const job + = qobject_cast<InternalSetupProjectJob *>(wrapper->synchronousJob()); + job->init(parameters); + wrapper->start(); +} + +void SetupProjectJob::reportError(const ErrorInfo &error) +{ + InternalJobThreadWrapper * const wrapper + = qobject_cast<InternalJobThreadWrapper *>(internalJob()); + InternalSetupProjectJob * const job + = qobject_cast<InternalSetupProjectJob *>(wrapper->synchronousJob()); + job->reportError(error); +} + +/*! + * \class ProcessResult + * \brief The \c ProcessResult class represents the result of one external program run by Qbs. + * + * The \c ProcessResult class represents all the information on one external program that was + * run by Qbs. It includes the command line used to start the program, the working directory + * as well as output and exit codes. + */ + +/*! + * \class BuildJob + * \brief The \c BuildJob class represents a build operation. + */ + +/*! + * \fn void BuildJob::reportCommandDescription(const QString &highlight, const QString &message) + * \brief Signals that a new command is being worked on. + * The \a highlight parameter is used to decide on the colors and font styles to be used to + * print the message. + * The \a message parameter is the localized message to print. + */ + +/*! + * \fn void BuildJob::reportProcessResult(const qbs::ProcessResult &result) + * \brief Signals that an external command has finished. + * The \a result parameter contains all details on the process that was run by Qbs. + */ + +BuildJob::BuildJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalBuildJob(logger), parent) +{ + InternalBuildJob *job = static_cast<InternalBuildJob *>(internalJob()); + connect(job, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString))); + connect(job, SIGNAL(reportProcessResult(qbs::ProcessResult)), + this, SIGNAL(reportProcessResult(qbs::ProcessResult))); +} + +void BuildJob::build(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const BuildOptions &options) +{ + if (!lockBuildGraph(project)) + return; + qobject_cast<InternalBuildJob *>(internalJob())->build(project, products, options); +} + + +/*! + * \class CleanJob + * \brief The \c CleanJob class represents an operation removing build artifacts. + */ + +CleanJob::CleanJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalJobThreadWrapper(new InternalCleanJob(logger)), parent) +{ +} + +void CleanJob::clean(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const qbs::CleanOptions &options) +{ + if (!lockBuildGraph(project)) + return; + InternalJobThreadWrapper * wrapper = qobject_cast<InternalJobThreadWrapper *>(internalJob()); + qobject_cast<InternalCleanJob *>(wrapper->synchronousJob())->init(project, products, options); + wrapper->start(); +} + +/*! + * \class InstallJob + * \brief The \c InstallJob class represents an operation installing files. + */ + +InstallJob::InstallJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalJobThreadWrapper(new InternalInstallJob(logger)), parent) +{ +} + +void InstallJob::install(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const InstallOptions &options) +{ + if (!lockBuildGraph(project)) + return; + InternalJobThreadWrapper *wrapper = qobject_cast<InternalJobThreadWrapper *>(internalJob()); + InternalInstallJob *installJob = qobject_cast<InternalInstallJob *>(wrapper->synchronousJob()); + installJob->init(project, products, options); + wrapper->start(); +} + +} // namespace qbs diff --git a/src/lib/corelib/api/jobs.h b/src/lib/corelib/api/jobs.h new file mode 100644 index 000000000..42c743876 --- /dev/null +++ b/src/lib/corelib/api/jobs.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** 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_JOBS_H +#define QBS_JOBS_H + +#include "../language/forward_decls.h" +#include "../tools/error.h" +#include "../tools/qbs_export.h" + +#include <QObject> +#include <QProcess> +#include <QVariantMap> + +namespace qbs { +class BuildOptions; +class CleanOptions; +class InstallOptions; +class ProcessResult; +class SetupProjectParameters; +namespace Internal { +class InternalJob; +class Logger; +class ProjectPrivate; +} // namespace Internal + +class Project; + +class QBS_EXPORT AbstractJob : public QObject +{ + Q_OBJECT +public: + ~AbstractJob(); + + enum State { StateRunning, StateCanceling, StateFinished }; + State state() const { return m_state; } + + ErrorInfo error() const; + + void cancel(); + +protected: + AbstractJob(Internal::InternalJob *internalJob, QObject *parent); + Internal::InternalJob *internalJob() const { return m_internalJob; } + + bool lockBuildGraph(const Internal::TopLevelProjectPtr &project); + +signals: + void taskStarted(const QString &description, int maximumProgressValue, qbs::AbstractJob *job); + void totalEffortChanged(int totalEffort, qbs::AbstractJob *job); + void taskProgress(int newProgressValue, qbs::AbstractJob *job); + void finished(bool success, qbs::AbstractJob *job); + +private slots: + void handleTaskStarted(const QString &description, int maximumProgressValue); + void handleTotalEffortChanged(int totalEffort); + void handleTaskProgress(int newProgressValue); + void handleFinished(); + +private: + Internal::InternalJob * const m_internalJob; + State m_state; +}; + + +class QBS_EXPORT SetupProjectJob : public AbstractJob +{ + Q_OBJECT + friend class Project; +public: + Project project() const; + +private: + SetupProjectJob(const Internal::Logger &logger, QObject *parent); + + void resolve(const SetupProjectParameters ¶meters); + void reportError(const ErrorInfo &error); +}; + +class QBS_EXPORT BuildJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + +private: + BuildJob(const Internal::Logger &logger, QObject *parent); + + void build(const Internal::TopLevelProjectPtr &project, + const QList<qbs::Internal::ResolvedProductPtr> &products, + const BuildOptions &options); +}; + + +class QBS_EXPORT CleanJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; + +private: + CleanJob(const Internal::Logger &logger, QObject *parent); + + void clean(const Internal::TopLevelProjectPtr &project, + const QList<Internal::ResolvedProductPtr> &products, const CleanOptions &options); +}; + +class QBS_EXPORT InstallJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; +private: + InstallJob(const Internal::Logger &logger, QObject *parent); + + void install(const Internal::TopLevelProjectPtr &project, + const QList<Internal::ResolvedProductPtr> &products, const InstallOptions &options); +}; + +} // namespace qbs + +#endif // QBS_JOBS_H diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp new file mode 100644 index 000000000..6826884e6 --- /dev/null +++ b/src/lib/corelib/api/project.cpp @@ -0,0 +1,1027 @@ +/**************************************************************************** +** +** 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 "project.h" + +#include "internaljobs.h" +#include "jobs.h" +#include "projectdata.h" +#include "projectdata_p.h" +#include "projectfileupdater.h" +#include "propertymap_p.h" +#include "runenvironment.h" +#include <buildgraph/artifact.h> +#include <buildgraph/buildgraph.h> +#include <buildgraph/productbuilddata.h> +#include <buildgraph/productinstaller.h> +#include <buildgraph/projectbuilddata.h> +#include <buildgraph/rulesevaluationcontext.h> +#include <buildgraph/timestampsupdater.h> +#include <buildgraph/transformer.h> +#include <language/language.h> +#include <language/projectresolver.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/cleanoptions.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/installoptions.h> +#include <tools/preferences.h> +#include <tools/processresult.h> +#include <tools/propertyfinder.h> +#include <tools/scannerpluginmanager.h> +#include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QMutex> +#include <QMutexLocker> +#include <QSharedData> + +namespace qbs { +namespace Internal { + +static bool pluginsLoaded = false; +static QMutex pluginsLoadedMutex; + +static void loadPlugins(const QStringList &_pluginPaths, const Logger &logger) +{ + QMutexLocker locker(&pluginsLoadedMutex); + if (pluginsLoaded) + return; + + QStringList pluginPaths; + foreach (const QString &pluginPath, _pluginPaths) { + if (!FileInfo::exists(pluginPath)) { + logger.qbsWarning() << Tr::tr("Plugin path '%1' does not exist.") + .arg(QDir::toNativeSeparators(pluginPath)); + } else { + pluginPaths << pluginPath; + } + } + ScannerPluginManager::instance()->loadPlugins(pluginPaths, logger); + + qRegisterMetaType<ErrorInfo>("qbs::ErrorInfo"); + qRegisterMetaType<ProcessResult>("qbs::ProcessResult"); + qRegisterMetaType<InternalJob *>("Internal::InternalJob *"); + pluginsLoaded = true; +} + + +class ProjectPrivate : public QSharedData +{ +public: + ProjectPrivate(const TopLevelProjectPtr &internalProject, const Logger &logger) + : internalProject(internalProject), logger(logger), m_projectDataRetrieved(false) + { + } + + ProjectData projectData(); + BuildJob *buildProducts(const QList<ResolvedProductPtr> &products, const BuildOptions &options, + bool needsDepencencyResolving, + QObject *jobOwner); + CleanJob *cleanProducts(const QList<ResolvedProductPtr> &products, const CleanOptions &options, + QObject *jobOwner); + InstallJob *installProducts(const QList<ResolvedProductPtr> &products, + const InstallOptions &options, bool needsDepencencyResolving, + QObject *jobOwner); + QList<ResolvedProductPtr> internalProducts(const QList<ProductData> &products) const; + QList<ResolvedProductPtr> allEnabledInternalProducts() const; + ResolvedProductPtr internalProduct(const ProductData &product) const; + ProductData findProductData(const QString &productName) const; + GroupData findGroupData(const ProductData &product, const QString &groupName) const; + + GroupData createGroupDataFromGroup(const GroupPtr &resolvedGroup); + + struct GroupUpdateContext { + ResolvedProductPtr resolvedProduct; + GroupPtr resolvedGroup; + ProductData currentProduct; + GroupData currentGroup; + }; + + struct FileListUpdateContext { + GroupUpdateContext groupContext; + QStringList absoluteFilePaths; + QStringList relativeFilePaths; + }; + + GroupUpdateContext getGroupContext(const ProductData &product, const GroupData &group); + FileListUpdateContext getFileListContext(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + + void addGroup(const ProductData &product, const QString &groupName); + void addFiles(const ProductData &product, const GroupData &group, const QStringList &filePaths); + void removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + void removeGroup(const ProductData &product, const GroupData &group); + void removeFilesFromBuildGraph(const ResolvedProductConstPtr &product, + const QList<SourceArtifactPtr> &files); + void updateInternalCodeLocations(const ResolvedProjectPtr &project, + const CodeLocation &changeLocation, int lineOffset); + void updateExternalCodeLocations(const ProjectData &project, + const CodeLocation &changeLocation, int lineOffset); + void prepareChangeToProject(); + + const TopLevelProjectPtr internalProject; + Logger logger; + +private: + void retrieveProjectData(ProjectData &projectData, + const ResolvedProjectConstPtr &internalProject); + + ProjectData m_projectData; + bool m_projectDataRetrieved; +}; + +ProjectData ProjectPrivate::projectData() +{ + if (!m_projectDataRetrieved) { + retrieveProjectData(m_projectData, internalProject); + m_projectData.d->buildDir = internalProject->buildDirectory; + m_projectDataRetrieved = true; + } + return m_projectData; +} + +static void addDependencies(QList<ResolvedProductPtr> &products) +{ + for (int i = 0; i < products.count(); ++i) { + const ResolvedProductPtr &product = products.at(i); + foreach (const ResolvedProductPtr &dependency, product->dependencies) { + if (!products.contains(dependency)) + products << dependency; + } + } +} + +BuildJob *ProjectPrivate::buildProducts(const QList<ResolvedProductPtr> &products, + const BuildOptions &options, bool needsDepencencyResolving, + QObject *jobOwner) +{ + QList<ResolvedProductPtr> productsToBuild = products; + if (needsDepencencyResolving) + addDependencies(productsToBuild); + + BuildJob * const job = new BuildJob(logger, jobOwner); + job->build(internalProject, productsToBuild, options); + return job; +} + +CleanJob *ProjectPrivate::cleanProducts(const QList<ResolvedProductPtr> &products, + const CleanOptions &options, QObject *jobOwner) +{ + CleanJob * const job = new CleanJob(logger, jobOwner); + job->clean(internalProject, products, options); + return job; +} + +InstallJob *ProjectPrivate::installProducts(const QList<ResolvedProductPtr> &products, + const InstallOptions &options, bool needsDepencencyResolving, QObject *jobOwner) +{ + QList<ResolvedProductPtr> productsToInstall = products; + if (needsDepencencyResolving) + addDependencies(productsToInstall); + InstallJob * const job = new InstallJob(logger, jobOwner); + job->install(internalProject, productsToInstall, options); + return job; +} + +QList<ResolvedProductPtr> ProjectPrivate::internalProducts(const QList<ProductData> &products) const +{ + QList<ResolvedProductPtr> internalProducts; + foreach (const ProductData &product, products) { + if (product.isEnabled()) + internalProducts << internalProduct(product); + } + return internalProducts; +} + +static QList<ResolvedProductPtr> enabledInternalProducts(const ResolvedProjectConstPtr &project) +{ + QList<ResolvedProductPtr> products; + foreach (const ResolvedProductPtr &p, project->products) { + if (p->enabled) + products << p; + } + foreach (const ResolvedProjectConstPtr &subProject, project->subProjects) + products << enabledInternalProducts(subProject); + return products; +} + +QList<ResolvedProductPtr> ProjectPrivate::allEnabledInternalProducts() const +{ + return enabledInternalProducts(internalProject); +} + +static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPtr &project, + const ProductData &product) +{ + foreach (const ResolvedProductPtr &resolvedProduct, project->products) { + if (product.name() == resolvedProduct->name) + return resolvedProduct; + } + foreach (const ResolvedProjectConstPtr &subProject, project->subProjects) { + const ResolvedProductPtr &p = internalProductForProject(subProject, product); + if (p) + return p; + } + return ResolvedProductPtr(); +} + +ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) const +{ + return internalProductForProject(internalProject, product); +} + +ProductData ProjectPrivate::findProductData(const QString &productName) const +{ + foreach (const ProductData &p, m_projectData.allProducts()) { + if (p.name() == productName) + return p; + } + return ProductData(); +} + +GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const +{ + foreach (const GroupData &g, product.groups()) { + if (g.name() == groupName) + return g; + } + return GroupData(); +} + +GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup) +{ + GroupData group; + group.d->name = resolvedGroup->name; + group.d->location = resolvedGroup->location; + foreach (const SourceArtifactConstPtr &sa, resolvedGroup->files) + group.d->filePaths << sa->absoluteFilePath; + if (resolvedGroup->wildcards) { + foreach (const SourceArtifactConstPtr &sa, resolvedGroup->wildcards->files) + group.d->expandedWildcards << sa->absoluteFilePath; + } + qSort(group.d->filePaths); + qSort(group.d->expandedWildcards); + group.d->properties.d->m_map = resolvedGroup->properties; + group.d->isEnabled = resolvedGroup->enabled; + group.d->isValid = true; + return group; +} + +void ProjectPrivate::addGroup(const ProductData &product, const QString &groupName) +{ + if (groupName.isEmpty()) + throw ErrorInfo(Tr::tr("Group has an empty name.")); + if (!product.isValid()) + throw ErrorInfo(Tr::tr("Product is invalid.")); + const ResolvedProductPtr resolvedProduct = internalProduct(product); + if (!resolvedProduct) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + + // Guard against calls with outdated product data. + const ProductData currentProduct= findProductData(product.name()); + QBS_CHECK(currentProduct.isValid()); + + foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) { + if (resolvedGroup->name == groupName) { + throw ErrorInfo(Tr::tr("Group '%1' already exists in product '%2'.") + .arg(groupName, product.name()), resolvedGroup->location); + } + } + + ProjectFileGroupInserter groupInserter(currentProduct, groupName); + groupInserter.apply(); + + m_projectData.d.detach(); // The data we already gave out must stay as it is. + + updateInternalCodeLocations(internalProject, groupInserter.itemPosition(), + groupInserter.lineOffset()); + updateExternalCodeLocations(m_projectData, groupInserter.itemPosition(), + groupInserter.lineOffset()); + + GroupPtr resolvedGroup = ResolvedGroup::create(); + resolvedGroup->location = groupInserter.itemPosition(); + resolvedGroup->enabled = true; + resolvedGroup->name = groupName; + resolvedGroup->properties = resolvedProduct->properties; + resolvedGroup->overrideTags = false; + resolvedProduct->groups << resolvedGroup; + foreach (const ProductData &newProduct, m_projectData.allProducts()) { + if (newProduct.name() == product.name()) { + newProduct.d->groups << createGroupDataFromGroup(resolvedGroup); + qSort(newProduct.d->groups); + break; + } + } +} + +ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const ProductData &product, + const GroupData &group) +{ + GroupUpdateContext context; + if (!product.isValid()) + throw ErrorInfo(Tr::tr("Product is invalid.")); + context.resolvedProduct = internalProduct(product); + if (!context.resolvedProduct) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + + context.currentProduct = findProductData(product.name()); + QBS_CHECK(context.currentProduct.isValid()); + + const QString groupName = group.isValid() ? group.name() : product.name(); + foreach (const GroupPtr &g, context.resolvedProduct->groups) { + if (g->name == groupName) { + context.resolvedGroup = g; + break; + } + } + if (!context.resolvedGroup) + throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName)); + context.currentGroup = findGroupData(context.currentProduct, groupName); + QBS_CHECK(context.currentGroup.isValid()); + return context; +} + +ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const ProductData &product, + const GroupData &group, const QStringList &filePaths) +{ + FileListUpdateContext filesContext; + GroupUpdateContext &groupContext = filesContext.groupContext; + groupContext = getGroupContext(product, group); + + if (filePaths.isEmpty()) + throw ErrorInfo(Tr::tr("No files supplied.")); + + if (!groupContext.resolvedGroup->prefix.isEmpty() && + !groupContext.resolvedGroup->prefix.endsWith(QLatin1Char('/'))) { + throw ErrorInfo(Tr::tr("Group has non-directory prefix.")); + } + QString baseDirPath = QFileInfo(product.location().fileName()).dir().absolutePath() + + QLatin1Char('/') + groupContext.resolvedGroup->prefix; + QDir baseDir(baseDirPath); + foreach (const QString &filePath, filePaths) { + const QString absPath = QDir::cleanPath(FileInfo::resolvePath(baseDirPath, filePath)); + if (filesContext.absoluteFilePaths.contains(absPath)) + throw ErrorInfo(Tr::tr("File '%1' appears more than once.").arg(absPath)); + if (!FileInfo(absPath).exists()) + throw ErrorInfo(Tr::tr("File '%1' does not exist.").arg(absPath)); + filesContext.absoluteFilePaths << absPath; + filesContext.relativeFilePaths << baseDir.relativeFilePath(absPath); + } + + return filesContext; +} + +void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + FileListUpdateContext filesContext = getFileListContext(product, group, filePaths); + GroupUpdateContext &groupContext = filesContext.groupContext; + + // We do not check for entries in other groups, because such doublettes might be legitimate + // due to conditions. + foreach (const QString &filePath, filesContext.absoluteFilePaths) { + foreach (const SourceArtifactConstPtr &sa, groupContext.resolvedGroup->files) { + if (sa->absoluteFilePath == filePath) { + throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") + .arg(filePath, group.name())); + } + } + } + + ProjectFileFilesAdder adder(groupContext.currentProduct, + group.isValid() ? groupContext.currentGroup : GroupData(), filesContext.relativeFilePaths); + adder.apply(); + + m_projectData.d.detach(); + updateInternalCodeLocations(internalProject, adder.itemPosition(), adder.lineOffset()); + updateExternalCodeLocations(m_projectData, adder.itemPosition(), adder.lineOffset()); + + QList<SourceArtifactPtr> addedSourceArtifacts; + foreach (const QString &file, filesContext.absoluteFilePaths) { + const SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->absoluteFilePath = file; + artifact->properties = groupContext.resolvedGroup->properties; + artifact->fileTags = groupContext.resolvedGroup->fileTags; + artifact->overrideFileTags = groupContext.resolvedGroup->overrideTags; + ProjectResolver::applyFileTaggers(artifact, groupContext.resolvedProduct, logger); + addedSourceArtifacts << artifact; + groupContext.resolvedGroup->files << artifact; + } + if (groupContext.resolvedProduct->enabled) { + ArtifactsPerFileTagMap artifactsPerFileTag; + foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) { + Artifact * const artifact = createArtifact(groupContext.resolvedProduct, sa, logger); + foreach (const FileTag &ft, artifact->fileTags) + artifactsPerFileTag[ft] += artifact; + } + RulesEvaluationContextPtr &evalContext + = groupContext.resolvedProduct->topLevelProject()->buildData->evaluationContext; + evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger)); + RulesApplicator(groupContext.resolvedProduct, artifactsPerFileTag, logger).applyAllRules(); + addTargetArtifacts(groupContext.resolvedProduct, artifactsPerFileTag, logger); + evalContext.clear(); + } + doSanityChecks(internalProject, logger); + groupContext.currentGroup.d->filePaths << filesContext.absoluteFilePaths; + qSort(groupContext.currentGroup.d->filePaths); +} + +void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + FileListUpdateContext filesContext = getFileListContext(product, group, filePaths); + GroupUpdateContext &groupContext = filesContext.groupContext; + + QStringList filesNotFound = filesContext.absoluteFilePaths; + QList<SourceArtifactPtr> sourceArtifacts; + foreach (const SourceArtifactPtr &sa, groupContext.resolvedGroup->files) { + if (filesNotFound.removeOne(sa->absoluteFilePath)) + sourceArtifacts << sa; + } + if (!filesNotFound.isEmpty()) { + throw ErrorInfo(Tr::tr("The following files are not known to qbs: %1") + .arg(filesNotFound.join(QLatin1String(", ")))); + } + + ProjectFileFilesRemover remover(groupContext.currentProduct, + group.isValid() ? groupContext.currentGroup + : GroupData(), filesContext.relativeFilePaths); + remover.apply(); + + removeFilesFromBuildGraph(groupContext.resolvedProduct, sourceArtifacts); + foreach (const SourceArtifactPtr &sa, sourceArtifacts) { + const bool removed = groupContext.resolvedGroup->files.removeOne(sa); + QBS_CHECK(removed); + } + doSanityChecks(internalProject, logger); + + m_projectData.d.detach(); + updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); + updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); + foreach (const QString &filePath, filesContext.absoluteFilePaths) { + const bool removed = groupContext.currentGroup.d->filePaths.removeOne(filePath); + QBS_CHECK(removed); + } +} + +void ProjectPrivate::removeGroup(const ProductData &product, const GroupData &group) +{ + GroupUpdateContext context = getGroupContext(product, group); + + ProjectFileGroupRemover remover(context.currentProduct, context.currentGroup); + remover.apply(); + + removeFilesFromBuildGraph(context.resolvedProduct, context.resolvedGroup->allFiles()); + bool removed = context.resolvedProduct->groups.removeOne(context.resolvedGroup); + QBS_CHECK(removed); + doSanityChecks(internalProject, logger); + + m_projectData.d.detach(); + updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); + updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); + removed = context.currentProduct.d->groups.removeOne(context.currentGroup); + QBS_CHECK(removed); +} + +void ProjectPrivate::removeFilesFromBuildGraph(const ResolvedProductConstPtr &product, + const QList<SourceArtifactPtr> &files) +{ + if (!product->enabled) + return; + QBS_CHECK(internalProject->buildData); + foreach (const SourceArtifactPtr &sa, files) { + Artifact * const artifact = lookupArtifact(product, sa->absoluteFilePath); + QBS_CHECK(artifact); + internalProject->buildData->removeArtifactAndExclusiveDependents(artifact, logger); + } + RulesEvaluationContextPtr &evalContext + = internalProject->buildData->evaluationContext; + evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger)); + internalProject->buildData->updateNodesThatMustGetNewTransformer(logger); + evalContext.clear(); +} + +static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation &changeLocation, + int lineOffset) +{ + if (location.fileName() == changeLocation.fileName() + && location.line() >= changeLocation.line()) { + location = CodeLocation(location.fileName(), location.line() + lineOffset, + location.column()); + } +} + +void ProjectPrivate::updateInternalCodeLocations(const ResolvedProjectPtr &project, + const CodeLocation &changeLocation, int lineOffset) +{ + if (lineOffset == 0) + return; + updateLocationIfNecessary(project->location, changeLocation, lineOffset); + foreach (const ResolvedProjectPtr &subProject, project->subProjects) + updateInternalCodeLocations(subProject, changeLocation, lineOffset); + foreach (const ResolvedProductPtr &product, project->products) { + updateLocationIfNecessary(product->location, changeLocation, lineOffset); + foreach (const GroupPtr &group, product->groups) + updateLocationIfNecessary(group->location, changeLocation, lineOffset); + foreach (const RulePtr &rule, product->rules) { + updateLocationIfNecessary(rule->script->location, changeLocation, lineOffset); + foreach (const RuleArtifactPtr &artifact, rule->artifacts) { + for (int i = 0; i < artifact->bindings.count(); ++i) { + updateLocationIfNecessary(artifact->bindings[i].location, changeLocation, + lineOffset); + } + } + } + foreach (const ResolvedTransformerConstPtr &transformer, product->transformers) + updateLocationIfNecessary(transformer->transform->location, changeLocation, lineOffset); + foreach (const ResolvedModuleConstPtr &module, product->modules) { + updateLocationIfNecessary(module->setupBuildEnvironmentScript->location, + changeLocation, lineOffset); + updateLocationIfNecessary(module->setupRunEnvironmentScript->location, + changeLocation, lineOffset); + } + } +} + +void ProjectPrivate::updateExternalCodeLocations(const ProjectData &project, + const CodeLocation &changeLocation, int lineOffset) +{ + if (lineOffset == 0) + return; + updateLocationIfNecessary(project.d->location, changeLocation, lineOffset); + foreach (const ProjectData &subProject, project.subProjects()) + updateExternalCodeLocations(subProject, changeLocation, lineOffset); + foreach (const ProductData &product, project.products()) { + updateLocationIfNecessary(product.d->location, changeLocation, lineOffset); + foreach (const GroupData &group, product.groups()) + updateLocationIfNecessary(group.d->location, changeLocation, lineOffset); + } +} + +void ProjectPrivate::prepareChangeToProject() +{ + if (internalProject->locked) + throw ErrorInfo(Tr::tr("A job is currently in process.")); + if (!m_projectDataRetrieved) + retrieveProjectData(m_projectData, internalProject); +} + +void ProjectPrivate::retrieveProjectData(ProjectData &projectData, + const ResolvedProjectConstPtr &internalProject) +{ + projectData.d->name = internalProject->name; + projectData.d->location = internalProject->location; + projectData.d->enabled = internalProject->enabled; + foreach (const ResolvedProductConstPtr &resolvedProduct, internalProject->products) { + ProductData product; + product.d->name = resolvedProduct->name; + product.d->location = resolvedProduct->location; + product.d->isEnabled = resolvedProduct->enabled; + foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) + product.d->groups << createGroupDataFromGroup(resolvedGroup); + if (resolvedProduct->enabled) { + QBS_CHECK(resolvedProduct->buildData); + foreach (const Artifact * const a, resolvedProduct->buildData->targetArtifacts) { + TargetArtifact ta; + ta.d->filePath = a->filePath(); + ta.d->fileTags = a->fileTags.toStringList(); + ta.d->properties.d->m_map = a->properties; + ta.d->isValid = true; + product.d->targetArtifacts << ta; + } + } + qSort(product.d->groups); + qSort(product.d->targetArtifacts); + product.d->isValid = true; + projectData.d->products << product; + } + foreach (const ResolvedProjectConstPtr &internalSubProject, internalProject->subProjects) { + ProjectData subProject; + retrieveProjectData(subProject, internalSubProject); + projectData.d->subProjects << subProject; + } + projectData.d->isValid = true; + qSort(projectData.d->products); + qSort(projectData.d->subProjects); + m_projectDataRetrieved = true; +} + +} // namespace Internal + +using namespace Internal; + + /*! + * \class Project + * \brief The \c Project class provides services related to a qbs project. + */ + +Project::Project(const TopLevelProjectPtr &internalProject, const Logger &logger) + : d(new ProjectPrivate(internalProject, logger)) +{ +} + +Project::Project(const Project &other) : d(other.d) +{ +} + +Project::~Project() +{ +} + +/*! + * \brief Returns true if and only if this object was retrieved from a successful \c SetupProjectJob. + * \sa SetupProjectJob + */ +bool Project::isValid() const +{ + return d && d->internalProject; +} + +Project &Project::operator=(const Project &other) +{ + d = other.d; + return *this; +} + +/*! + * \brief Sets up a \c Project from a source file, possibly re-using previously stored information. + * The function will finish immediately, returning a \c SetupProjectJob which can be used to + * track the results of the operation. + * \note The qbs plugins will only be loaded once. As a result, the value of + * \c parameters.pluginPaths will only have an effect the first time this function is called. + * Similarly, the value of \c parameters.searchPaths will not have an effect if + * a stored build graph is available. + */ +SetupProjectJob *Project::setupProject(const SetupProjectParameters ¶meters, + ILogSink *logSink, QObject *jobOwner) +{ + Logger logger(logSink); + SetupProjectJob * const job = new SetupProjectJob(logger, jobOwner); + try { + loadPlugins(parameters.pluginPaths(), logger); + job->resolve(parameters); + } catch (const ErrorInfo &error) { + // Throwing from here would complicate the API, so let's report the error the same way + // as all others, via AbstractJob::error(). + job->reportError(error); + } + return job; +} + +Project::Project() +{ +} + + +/*! + * \brief Retrieves information for this project. + * Call this function if you need insight into the project structure, e.g. because you want to know + * which products or files are in it. + */ +ProjectData Project::projectData() const +{ + return d->projectData(); +} + +/*! + * \brief Returns the file path of the executable associated with the given product. + * If the product is not an application, an empty string is returned. + * The \a installOptions parameter is used to look up the executable in case it is installable; + * otherwise the parameter is ignored and the returned path will point to where the file is built. + */ +QString Project::targetExecutable(const ProductData &product, + const InstallOptions &installOptions) const +{ + if (!product.isEnabled()) + return QString(); + foreach (const TargetArtifact &ta, product.targetArtifacts()) { + if (ta.isExecutable()) { + const QList<InstallableFile> &installables + = installableFilesForProduct(product, installOptions); + foreach (const InstallableFile &file, installables) { + if (file.sourceFilePath() == ta.filePath()) + return file.targetFilePath(); + } + return ta.filePath(); + } + } + return QString(); +} + +RunEnvironment Project::getRunEnvironment(const ProductData &product, + const QProcessEnvironment &environment, Settings *settings) const +{ + QBS_CHECK(product.isEnabled()); + const ResolvedProductPtr resolvedProduct = d->internalProduct(product); + return RunEnvironment(resolvedProduct, environment, settings, d->logger); +} + +/*! + * \brief Causes all products of this project to be built, if necessary. + * The function will finish immediately, returning a \c BuildJob identifiying the operation. + */ +BuildJob *Project::buildAllProducts(const BuildOptions &options, QObject *jobOwner) const +{ + return d->buildProducts(d->allEnabledInternalProducts(), options, false, jobOwner); +} + +/*! + * \brief Causes the specified list of products to be built. + * Use this function if you only want to build some products, not the whole project. If any of + * the products in \a products depend on other products, those will also be built. + * The function will finish immediately, returning a \c BuildJob identifiying the operation. + */ +BuildJob *Project::buildSomeProducts(const QList<ProductData> &products, + const BuildOptions &options, QObject *jobOwner) const +{ + return d->buildProducts(d->internalProducts(products), options, true, jobOwner); +} + +/*! + * \brief Convenience function for \c buildSomeProducts(). + * \sa Project::buildSomeProducts(). + */ +BuildJob *Project::buildOneProduct(const ProductData &product, const BuildOptions &options, + QObject *jobOwner) const +{ + return buildSomeProducts(QList<ProductData>() << product, options, jobOwner); +} + +/*! + * \brief Removes the build artifacts of all products in the project. + * The function will finish immediately, returning a \c CleanJob identifiying this operation. + * \sa Project::cleanSomeProducts() + */ +CleanJob *Project::cleanAllProducts(const CleanOptions &options, QObject *jobOwner) const +{ + return d->cleanProducts(d->allEnabledInternalProducts(), options, jobOwner); +} + +/*! + * \brief Removes the build artifacts of the given products. + * The function will finish immediately, returning a \c CleanJob identifiying this operation. + */ +CleanJob *Project::cleanSomeProducts(const QList<ProductData> &products, + const CleanOptions &options, QObject *jobOwner) const +{ + return d->cleanProducts(d->internalProducts(products), options, jobOwner); +} + +/*! + * \brief Convenience function for \c cleanSomeProducts(). + * \sa Project::cleanSomeProducts(). + */ +CleanJob *Project::cleanOneProduct(const ProductData &product, const CleanOptions &options, + QObject *jobOwner) const +{ + return cleanSomeProducts(QList<ProductData>() << product, options, jobOwner); +} + +/*! + * \brief Installs the installable files of all products in the project. + * The function will finish immediately, returning an \c InstallJob identifiying this operation. + */ +InstallJob *Project::installAllProducts(const InstallOptions &options, QObject *jobOwner) const +{ + return d->installProducts(d->allEnabledInternalProducts(), options, false, jobOwner); +} + +/*! + * \brief Installs the installable files of the given products. + * The function will finish immediately, returning an \c InstallJob identifiying this operation. + */ +InstallJob *Project::installSomeProducts(const QList<ProductData> &products, + const InstallOptions &options, QObject *jobOwner) const +{ + return d->installProducts(d->internalProducts(products), options, true, jobOwner); +} + +/*! + * \brief Convenience function for \c installSomeProducts(). + * \sa Project::installSomeProducts(). + */ +InstallJob *Project::installOneProduct(const ProductData &product, const InstallOptions &options, + QObject *jobOwner) const +{ + return installSomeProducts(QList<ProductData>() << product, options, jobOwner); +} + +/*! + * \brief All files in the product for which "qbs.install" is true. + * This includes source files as well as generated files. + */ +QList<InstallableFile> Project::installableFilesForProduct(const ProductData &product, + const InstallOptions &options) const +{ + QList<InstallableFile> installableFiles; + const ResolvedProductConstPtr internalProduct = d->internalProduct(product); + if (!internalProduct) + return installableFiles; + InstallOptions mutableOptions = options; + foreach (const GroupConstPtr &group, internalProduct->groups) { + foreach (const SourceArtifactConstPtr &artifact, group->allFiles()) { + InstallableFile f; + const QString &targetFilePath = ProductInstaller::targetFilePath(internalProduct->topLevelProject(), + artifact->absoluteFilePath, artifact->properties, mutableOptions, + &f.d->targetDirectory); + if (targetFilePath.isEmpty()) + continue; + f.d->sourceFilePath = artifact->absoluteFilePath; + f.d->fileTags = artifact->fileTags.toStringList(); + f.d->isValid = true; + installableFiles << f; + } + } + if (internalProduct->enabled) { + QBS_CHECK(internalProduct->buildData); + foreach (const Artifact * const artifact, internalProduct->buildData->artifacts) { + if (artifact->artifactType == Artifact::SourceFile) + continue; + InstallableFile f; + const QString &targetFilePath = ProductInstaller::targetFilePath(internalProduct->topLevelProject(), + artifact->filePath(), artifact->properties, mutableOptions, + &f.d->targetDirectory); + if (targetFilePath.isEmpty()) + continue; + f.d->sourceFilePath = artifact->filePath(); + f.d->fileTags = artifact->fileTags.toStringList(); + f.d->isValid = true; + installableFiles << f; + } + } + qSort(installableFiles); + return installableFiles; +} + +/*! + * \brief All files in the project for which "qbs.install" is true. + * This includes all sub-projects. + * \sa Project::installableFilesForProduct() + */ +QList<InstallableFile> Project::installableFilesForProject(const ProjectData &project, + const InstallOptions &options) const +{ + QList<InstallableFile> installableFiles; + foreach (const ProductData &p, project.allProducts()) + installableFiles << installableFilesForProduct(p, options); + qSort(installableFiles); + return installableFiles; +} + +/*! + * \brief Updates the timestamps of all build artifacts in the given products. + * Afterwards, the build graph will have the same state as if a successful build had been done. + */ +void Project::updateTimestamps(const QList<ProductData> &products) +{ + TimestampsUpdater().updateTimestamps(d->internalProject, d->internalProducts(products), + d->logger); +} + +/*! + * \brief Finds files generated from the given file in the given product. + * The function returns files generated from the given file and the given product. To do so it will + * traverse the graph of generated files and the files generated from those files. + * + * If an empty list of tags is given, then all directly and indirectly generated files will be + * returned. If there are tags, then processing will stop once matching files were found. + */ +QStringList Project::generatedFiles(const ProductData &product, const QString &file, + const QStringList &tags) const +{ + const ResolvedProductConstPtr internalProduct = d->internalProduct(product); + return internalProduct->generatedFiles(file, FileTags::fromStringList(tags)); +} + +QVariantMap Project::projectConfiguration() const +{ + return d->internalProject->buildConfiguration(); +} + +QHash<QString, QString> Project::usedEnvironment() const +{ + return d->internalProject->usedEnvironment; +} + +QSet<QString> Project::buildSystemFiles() const +{ + return d->internalProject->buildSystemFiles; +} + +/*! + * \brief Adds a new empty group to the given product. + * Returns an \c ErrorInfo object for which \c hasError() is false in case of a success + * and true otherwise. In the latter case, the object will have a sensible description. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::addGroup(const ProductData &product, const QString &groupName) +{ + try { + d->prepareChangeToProject(); + d->addGroup(product, groupName); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure adding group '%1' to product '%2'.") + .arg(groupName, product.name())); + return errorInfo; + } +} + +/*! + * \brief Adds the given files to the given product. + * If \c group is a default-constructed object, the files will be added to the product's + * "files" property, otherwise to the one of \c group. + * The file paths can be absolute or relative to the location of \c product (including a possible + * prefix in the group). The project file will always contain relative paths. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + try { + d->prepareChangeToProject(); + d->addFiles(product, group, filePaths); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure adding files to product.")); + return errorInfo; + } +} + +/*! + * \brief Removes the given files from the given product. + * If \c group is a default-constructed object, the files will be removed from the product's + * "files" property, otherwise from the one of \c group. + * The file paths can be absolute or relative to the location of \c product (including a possible + * prefix in the group). + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + try { + d->prepareChangeToProject(); + d->removeFiles(product, group, filePaths); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure removing files from product '%1'.").arg(product.name())); + return errorInfo; + } +} + +/*! + * \brief Removes the given group from the given product. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::removeGroup(const ProductData &product, const GroupData &group) +{ + try { + d->prepareChangeToProject(); + d->removeGroup(product, group); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure removing group '%1' from product '%2'.") + .arg(group.name(), product.name())); + return errorInfo; + } +} + +} // namespace qbs diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h new file mode 100644 index 000000000..0118977c5 --- /dev/null +++ b/src/lib/corelib/api/project.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** 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_PROJECT_H +#define QBS_PROJECT_H + +#include "../language/forward_decls.h" +#include "../tools/qbs_export.h" + +#include <QExplicitlySharedDataPointer> +#include <QHash> +#include <QList> +#include <QSet> +#include <QStringList> +#include <QVariantMap> + +QT_BEGIN_NAMESPACE +class QObject; +class QProcessEnvironment; +QT_END_NAMESPACE + +namespace qbs { +class BuildJob; +class BuildOptions; +class CleanJob; +class CleanOptions; +class ErrorInfo; +class GroupData; +class ILogSink; +class InstallableFile; +class InstallJob; +class InstallOptions; +class ProductData; +class ProjectData; +class RunEnvironment; +class Settings; +class SetupProjectJob; +class SetupProjectParameters; + +namespace Internal { +class Logger; +class ProjectPrivate; +} // namespace Internal; + +class QBS_EXPORT Project +{ + friend class SetupProjectJob; + friend uint qHash(const Project &p); +public: + static SetupProjectJob *setupProject(const SetupProjectParameters ¶meters, + ILogSink *logSink, QObject *jobOwner); + + Project(); + Project(const Project &other); + Project &operator=(const Project &other); + ~Project(); + + bool isValid() const; + ProjectData projectData() const; + QString targetExecutable(const ProductData &product, + const InstallOptions &installoptions) const; + RunEnvironment getRunEnvironment(const ProductData &product, + const QProcessEnvironment &environment, Settings *settings) const; + + BuildJob *buildAllProducts(const BuildOptions &options, QObject *jobOwner = 0) const; + BuildJob *buildSomeProducts(const QList<ProductData> &products, const BuildOptions &options, + QObject *jobOwner = 0) const; + BuildJob *buildOneProduct(const ProductData &product, const BuildOptions &options, + QObject *jobOwner = 0) const; + + CleanJob *cleanAllProducts(const CleanOptions &options, QObject *jobOwner = 0) const; + CleanJob *cleanSomeProducts(const QList<ProductData> &products, const CleanOptions &options, + QObject *jobOwner = 0) const; + CleanJob *cleanOneProduct(const ProductData &product, const CleanOptions &options, + QObject *jobOwner = 0) const; + + InstallJob *installAllProducts(const InstallOptions &options, QObject *jobOwner = 0) const; + InstallJob *installSomeProducts(const QList<ProductData> &products, + const InstallOptions &options, QObject *jobOwner = 0) const; + InstallJob *installOneProduct(const ProductData &product, const InstallOptions &options, + QObject *jobOwner = 0) const; + + QList<InstallableFile> installableFilesForProduct(const ProductData &product, + const InstallOptions &options) const; + QList<InstallableFile> installableFilesForProject(const ProjectData &project, + const InstallOptions &options) const; + + void updateTimestamps(const QList<ProductData> &products); + + bool operator==(const Project &other) const { return d.data() == other.d.data(); } + + QStringList generatedFiles(const ProductData &product, const QString &file, + const QStringList &tags = QStringList()) const; + + QVariantMap projectConfiguration() const; + QHash<QString, QString> usedEnvironment() const; + + QSet<QString> buildSystemFiles() const; + + ErrorInfo addGroup(const ProductData &product, const QString &groupName); + ErrorInfo addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + ErrorInfo removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + ErrorInfo removeGroup(const ProductData &product, const GroupData &group); + +private: + Project(const Internal::TopLevelProjectPtr &internalProject, const Internal::Logger &logger); + + QExplicitlySharedDataPointer<Internal::ProjectPrivate> d; +}; + +inline bool operator!=(const Project &p1, const Project &p2) { return !(p1 == p2); } +inline uint qHash(const Project &p) { return QT_PREPEND_NAMESPACE(qHash)(p.d.data()); } + +} // namespace qbs + +#endif // QBS_PROJECT_H diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp new file mode 100644 index 000000000..a5cc6093e --- /dev/null +++ b/src/lib/corelib/api/projectdata.cpp @@ -0,0 +1,674 @@ +/**************************************************************************** +** +** 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 "projectdata.h" + +#include "projectdata_p.h" +#include "propertymap_p.h" +#include <language/propertymapinternal.h> +#include <tools/fileinfo.h> +#include <tools/propertyfinder.h> +#include <tools/qbsassert.h> +#include <tools/scripttools.h> + +namespace qbs { + +/*! + * \class GroupData + * \brief The \c GroupData class corresponds to the Group item in a qbs source file. + */ + +GroupData::GroupData() : d(new Internal::GroupDataPrivate) +{ +} + +GroupData::GroupData(const GroupData &other) : d(other.d) +{ +} + +GroupData &GroupData::operator=(const GroupData &other) +{ + d = other.d; + return *this; +} + +GroupData::~GroupData() +{ +} + +/*! + * \brief Returns true if and only if the Group holds data that was initialized by Qbs. + */ +bool GroupData::isValid() const +{ + return d->isValid; +} + +/*! + * \brief The location at which the group is defined in the respective source file. + */ +CodeLocation GroupData::location() const +{ + return d->location; +} + +/*! + * \brief The name of the group. + */ +QString GroupData::name() const +{ + return d->name; +} + +/*! + * \brief The files listed in the group item's "files" binding. + * \note These do not include expanded wildcards. + * \sa GroupData::expandedWildcards + */ +QStringList GroupData::filePaths() const +{ + return d->filePaths; +} + +/*! + * \brief The list of files resulting from expanding all wildcard patterns in the group. + */ +QStringList GroupData::expandedWildcards() const +{ + return d->expandedWildcards; +} + +/*! + * \brief The set of properties valid in this group. + * Typically, most of them are inherited from the respective \c Product. + */ +PropertyMap GroupData::properties() const +{ + return d->properties; +} + +/*! + * \brief Returns true if this group is enabled in Qbs + * This method returns the "condition" property of the \c Group definition. If the group is enabled + * then the files in this group will be processed, provided the product it belongs to is also + * enabled. + * + * Note that a group can be enabled, even if the product it belongs to is not. In this case + * the files in the group will not be processed. + * \sa ProductData::isEnabled() + */ +bool GroupData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->isEnabled; +} + +/*! + * \fn QStringList GroupData::allFilePaths() const + * \brief All files in this group, regardless of how whether they were given explicitly + * or via wildcards. + * \sa GroupData::filePaths + * \sa GroupData::expandedWildcards + */ +QStringList GroupData::allFilePaths() const +{ + return d->filePaths + d->expandedWildcards; +} + +bool operator!=(const GroupData &lhs, const GroupData &rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const GroupData &lhs, const GroupData &rhs) +{ + return lhs.name() == rhs.name() + && lhs.location() == rhs.location() + && lhs.expandedWildcards() == rhs.expandedWildcards() + && lhs.filePaths() == rhs.filePaths() + && lhs.properties() == rhs.properties() + && lhs.isEnabled() == rhs.isEnabled(); +} + +bool operator<(const GroupData &lhs, const GroupData &rhs) +{ + return lhs.name() < rhs.name(); +} + + +/*! + * \class TargetArtifact + * \brief The \c TargetArtifact class describes a top-level build result of a product. + * For instance, the target artifact of a product with type "application" is an executable file. + */ + +TargetArtifact::TargetArtifact() : d(new Internal::TargetArtifactPrivate) +{ +} + +TargetArtifact::TargetArtifact(const TargetArtifact &other) : d(other.d) +{ +} + +TargetArtifact &TargetArtifact::operator=(const TargetArtifact &other) +{ + d = other.d; + return *this; +} + +TargetArtifact::~TargetArtifact() +{ +} + +/*! + * \brief Returns true if and only if this object holds data that was initialized by Qbs. + */ +bool TargetArtifact::isValid() const +{ + return d->isValid; +} + +/*! + * \brief The full path of this file. + */ +QString TargetArtifact::filePath() const +{ + return d->filePath; +} + +/*! + * \brief The tags of this file. + * Typically, this list will contain just one element. + */ +QStringList TargetArtifact::fileTags() const +{ + return d->fileTags; +} + +/*! + * \brief True if and only if this file is executable, + * either natively or through an interpreter or shell. + */ +bool TargetArtifact::isExecutable() const +{ + return d->fileTags.contains(QLatin1String("application")) + || d->fileTags.contains(QLatin1String("applicationbundle")) + || d->fileTags.contains(QLatin1String("msi")); +} + +/*! + * \brief The properties of this file. + */ +PropertyMap TargetArtifact::properties() const +{ + return d->properties; +} + +bool operator==(const TargetArtifact &ta1, const TargetArtifact &ta2) +{ + return ta1.filePath() == ta2.filePath() + && ta1.fileTags() == ta2.fileTags() + && ta1.properties() == ta2.properties(); +} + +bool operator!=(const TargetArtifact &ta1, const TargetArtifact &ta2) +{ + return !(ta1 == ta2); +} + +bool operator<(const TargetArtifact &ta1, const TargetArtifact &ta2) +{ + return ta1.filePath() < ta2.filePath(); +} + + +/*! + * \class InstallableFile + * \brief Describes a file that is marked for installation. + */ + +InstallableFile::InstallableFile() : d(new Internal::InstallableFilePrivate) +{ +} + +InstallableFile::InstallableFile(const InstallableFile &other) : d(other.d) +{ +} + +InstallableFile &InstallableFile::operator=(const InstallableFile &other) +{ + d = other.d; + return *this; +} + +InstallableFile::~InstallableFile() +{ +} + +/*! + * \brief Returns true if and only if this object holds data that was initialized by Qbs. + */ +bool InstallableFile::isValid() const +{ + return d->isValid; +} + +/*! + * \brief The location of the file from which it will be copied to \c targetFilePath() + * on installation. + */ +QString InstallableFile::sourceFilePath() const +{ + return d->sourceFilePath; +} + +/*! + * \brief The directory that this file will be copied into on installation. + */ +QString InstallableFile::targetDirectory() const +{ + return d->targetDirectory; +} + +/*! + * \brief The file path that this file will be copied to on installation. + * This is a convenience function. + * \sa InstallableFile::targetDirectory() + */ +QString InstallableFile::targetFilePath() const +{ + return d->targetDirectory + QLatin1Char('/') + Internal::FileInfo::fileName(d->sourceFilePath); +} + +/*! + * \brief The file's tags. + */ +QStringList InstallableFile::fileTags() const +{ + return d->fileTags; +} + +/*! + * \brief True if and only if the file is an executable. + */ +bool InstallableFile::isExecutable() const +{ + return d->fileTags.contains(QLatin1String("application")) + || d->fileTags.contains(QLatin1String("applicationbundle")); +} + +bool operator==(const InstallableFile &file1, const InstallableFile &file2) +{ + return file1.sourceFilePath() == file2.sourceFilePath() + && file1.targetFilePath() == file2.targetFilePath() + && file1.fileTags() == file2.fileTags(); +} + +bool operator!=(const InstallableFile &file1, const InstallableFile &file2) +{ + return !(file1 == file2); +} + +bool operator<(const InstallableFile &file1, const InstallableFile &file2) +{ + return file1.sourceFilePath() < file2.sourceFilePath(); +} + +/*! + * \class ProductData + * \brief The \c ProductData class corresponds to the Product item in a qbs source file. + */ + +ProductData::ProductData() : d(new Internal::ProductDataPrivate) +{ +} + +ProductData::ProductData(const ProductData &other) : d(other.d) +{ +} + +ProductData &ProductData::operator=(const ProductData &other) +{ + d = other.d; + return *this; +} + +ProductData::~ProductData() +{ +} + +/*! + * \brief Returns true if and only if the Product holds data that was initialized by Qbs. + */ +bool ProductData::isValid() const +{ + return d->isValid; +} + +/*! + * \brief The name of the product as given in the qbs source file. + */ +QString ProductData::name() const +{ + return d->name; +} + +/*! + * \brief The location at which the product is defined in the source file. + */ +CodeLocation ProductData::location() const +{ + return d->location; +} + +/*! + * \brief This product's target artifacts. + */ +QList<TargetArtifact> ProductData::targetArtifacts() const +{ + return d->targetArtifacts; +} + +/*! + * \brief The list of \c GroupData in this product. + */ +QList<GroupData> ProductData::groups() const +{ + return d->groups; +} + +/*! + * \brief Returns true if this Product is enabled in Qbs. + * This method returns the \c condition property of the \c Product definition. If a product is + * enabled, then it will be built in the current configuration. + * \sa GroupData::isEnabled() + */ +bool ProductData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->isEnabled; +} + +bool operator==(const ProductData &lhs, const ProductData &rhs) +{ + return lhs.name() == rhs.name() + && lhs.location() == rhs.location() + && lhs.groups() == rhs.groups() + && lhs.targetArtifacts() == rhs.targetArtifacts() + && lhs.isEnabled() == rhs.isEnabled(); +} + +bool operator!=(const ProductData &lhs, const ProductData &rhs) +{ + return !(lhs == rhs); +} + +bool operator<(const ProductData &lhs, const ProductData &rhs) +{ + return lhs.name() < rhs.name(); +} + +/*! + * \class ProjectData + * \brief The \c ProjectData class corresponds to the \c Project item in a qbs source file. + */ + +/*! + * \fn QList<ProductData> ProjectData::products() const + * \brief The products in this project. + */ + +ProjectData::ProjectData() : d(new Internal::ProjectDataPrivate) +{ +} + +ProjectData::ProjectData(const ProjectData &other) : d(other.d) +{ +} + +ProjectData &ProjectData::operator =(const ProjectData &other) +{ + d = other.d; + return *this; +} + +ProjectData::~ProjectData() +{ +} + +/*! + * \brief Returns true if and only if the Project holds data that was initialized by Qbs. + */ +bool ProjectData::isValid() const +{ + return d->isValid; +} + +/*! + * \brief The name of this project. + */ +QString ProjectData::name() const +{ + return d->name; +} + +/*! + * \brief The location at which the project is defined in a qbs source file. + */ +CodeLocation ProjectData::location() const +{ + return d->location; +} + +/*! + * \brief Whether the project is enabled. + * \note Disabled projects never have any products or sub-projects. + */ +bool ProjectData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->enabled; +} + +/*! + * \brief The base directory under which the build artifacts of this project will be created. + * This is only valid for the top-level project. + */ +QString ProjectData::buildDirectory() const +{ + return d->buildDir; +} + +/*! + * The products in this project. + * \note This also includes disabled products. + */ +QList<ProductData> ProjectData::products() const +{ + return d->products; +} + +/*! + * The sub-projects of this project. + */ +QList<ProjectData> ProjectData::subProjects() const +{ + return d->subProjects; +} + +/*! + * All products in this projects and its direct and indirect sub-projects. + */ +QList<ProductData> ProjectData::allProducts() const +{ + QList<ProductData> productList = products(); + foreach (const ProjectData &pd, subProjects()) + productList << pd.allProducts(); + return productList; +} + +bool operator==(const ProjectData &lhs, const ProjectData &rhs) +{ + return lhs.location() == rhs.location() + && lhs.subProjects() == rhs.subProjects() + && lhs.products() == rhs.products(); +} + +bool operator!=(const ProjectData &lhs, const ProjectData &rhs) +{ + return !(lhs == rhs); +} + +bool operator<(const ProjectData &lhs, const ProjectData &rhs) +{ + return lhs.name() < rhs.name(); +} + +/*! + * \class PropertyMap + * \brief The \c PropertyMap class represents the properties of a group or a product. + */ + +PropertyMap::PropertyMap() + : d(new Internal::PropertyMapPrivate) +{ + static Internal::PropertyMapPtr defaultInternalMap = Internal::PropertyMapInternal::create(); + d->m_map = defaultInternalMap; +} + +PropertyMap::PropertyMap(const PropertyMap &other) + : d(new Internal::PropertyMapPrivate(*other.d)) +{ +} + +PropertyMap::~PropertyMap() +{ + delete d; +} + +PropertyMap &PropertyMap::operator =(const PropertyMap &other) +{ + delete d; + d = new Internal::PropertyMapPrivate(*other.d); + return *this; +} + +/*! + * \brief Returns the names of all properties. + */ +QStringList PropertyMap::allProperties() const +{ + QStringList properties; + for (QVariantMap::ConstIterator it = d->m_map->value().constBegin(); + it != d->m_map->value().constEnd(); ++it) { + if (!it.value().canConvert<QVariantMap>()) + properties << it.key(); + } + return properties; +} + +/*! + * \brief Returns the value of the given property of a product or group. + */ +QVariant PropertyMap::getProperty(const QString &name) const +{ + return d->m_map->value().value(name); +} + +/*! + * \brief Returns the values of the given module property. + * This function is intended for properties of list type, such as "cpp.includes". + * The values will be gathered both directly from the product/group as well as from the + * product's module dependencies. + */ +QVariantList PropertyMap::getModuleProperties(const QString &moduleName, + const QString &propertyName) const +{ + return Internal::PropertyFinder().propertyValues(d->m_map->value(), moduleName, propertyName); +} + +/*! + * \brief Convenience function for \c PropertyMap::getModuleProperties. + */ +QStringList PropertyMap::getModulePropertiesAsStringList(const QString &moduleName, + const QString &propertyName) const +{ + const QVariantList &vl = getModuleProperties(moduleName, propertyName); + QStringList sl; + foreach (const QVariant &v, vl) { + QBS_ASSERT(v.canConvert<QString>(), continue); + sl << v.toString(); + } + return sl; +} + +/*! + * \brief Returns the value of the given module property. + * This function is intended for properties of "integral" type, such as "qbs.targetOS". + * The property will be looked up first at the product or group itself. If it is not found there, + * the module dependencies are searched in undefined order. + */ +QVariant PropertyMap::getModuleProperty(const QString &moduleName, + const QString &propertyName) const +{ + return Internal::PropertyFinder().propertyValue(d->m_map->value(), moduleName, propertyName); +} + +static QString mapToString(const QVariantMap &map, const QString &prefix) +{ + QStringList keys(map.keys()); + qSort(keys); + QString stringRep; + foreach (const QString &key, keys) { + const QVariant &val = map.value(key); + if (val.type() == QVariant::Map) { + stringRep += mapToString(val.value<QVariantMap>(), prefix + key + QLatin1Char('.')); + } else { + stringRep += QString::fromLocal8Bit("%1%2: %3\n") + .arg(prefix, key, Internal::toJSLiteral(val)); + } + } + return stringRep; +} + +QString PropertyMap::toString() const +{ + return mapToString(d->m_map->value(), QString()); +} + +bool operator==(const PropertyMap &pm1, const PropertyMap &pm2) +{ + return pm1.d->m_map->value() == pm2.d->m_map->value(); +} + +bool operator!=(const PropertyMap &pm1, const PropertyMap &pm2) +{ + return !(pm1.d->m_map->value() == pm2.d->m_map->value()); +} + +} // namespace qbs diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h new file mode 100644 index 000000000..ba0016d0c --- /dev/null +++ b/src/lib/corelib/api/projectdata.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** 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_PROJECTDATA_H +#define QBS_PROJECTDATA_H + +#include "../tools/codelocation.h" +#include "../tools/qbs_export.h" + +#include <QExplicitlySharedDataPointer> +#include <QList> +#include <QPair> +#include <QString> +#include <QStringList> +#include <QVariantMap> + +namespace qbs { +namespace Internal { +class GroupDataPrivate; +class InstallableFilePrivate; +class ProductDataPrivate; +class ProjectPrivate; +class ProjectDataPrivate; +class PropertyMapPrivate; +class TargetArtifactPrivate; +} // namespace Internal + +class PropertyMap; + +bool operator==(const PropertyMap &pm1, const PropertyMap &pm2); +bool operator!=(const PropertyMap &pm1, const PropertyMap &pm2); + +class QBS_EXPORT PropertyMap +{ + friend class Internal::ProjectPrivate; + friend bool operator==(const PropertyMap &, const PropertyMap &); + friend bool operator!=(const PropertyMap &, const PropertyMap &); + +public: + PropertyMap(); + PropertyMap(const PropertyMap &other); + ~PropertyMap(); + + PropertyMap &operator =(const PropertyMap &other); + + QStringList allProperties() const; + QVariant getProperty(const QString &name) const; + + QVariantList getModuleProperties(const QString &moduleName, const QString &propertyName) const; + QStringList getModulePropertiesAsStringList(const QString &moduleName, + const QString &propertyName) const; + QVariant getModuleProperty(const QString &moduleName, const QString &propertyName) const; + + // For debugging. + QString toString() const; + +private: + Internal::PropertyMapPrivate *d; +}; + +class QBS_EXPORT GroupData +{ + friend class Internal::ProjectPrivate; +public: + GroupData(); + GroupData(const GroupData &other); + GroupData &operator=(const GroupData &other); + ~GroupData(); + + bool isValid() const; + + CodeLocation location() const; + QString name() const; + QStringList filePaths() const; + QStringList expandedWildcards() const; + PropertyMap properties() const; + bool isEnabled() const; + QStringList allFilePaths() const; + +private: + QExplicitlySharedDataPointer<Internal::GroupDataPrivate> d; +}; + +QBS_EXPORT bool operator==(const GroupData &lhs, const GroupData &rhs); +QBS_EXPORT bool operator!=(const GroupData &lhs, const GroupData &rhs); +QBS_EXPORT bool operator<(const GroupData &lhs, const GroupData &rhs); + +class QBS_EXPORT TargetArtifact +{ + friend class Internal::ProjectPrivate; +public: + TargetArtifact(); + TargetArtifact(const TargetArtifact &other); + TargetArtifact &operator=(const TargetArtifact &other); + ~TargetArtifact(); + + bool isValid() const; + + QString filePath() const; + QStringList fileTags() const; + bool isExecutable() const; + PropertyMap properties() const; + +private: + QExplicitlySharedDataPointer<Internal::TargetArtifactPrivate> d; +}; + +QBS_EXPORT bool operator==(const TargetArtifact &ta1, const TargetArtifact &ta2); +QBS_EXPORT bool operator!=(const TargetArtifact &ta1, const TargetArtifact &ta2); +QBS_EXPORT bool operator<(const TargetArtifact &ta1, const TargetArtifact &ta2); + +class QBS_EXPORT InstallableFile +{ + friend class Project; +public: + InstallableFile(); + InstallableFile(const InstallableFile &other); + InstallableFile &operator=(const InstallableFile &other); + ~InstallableFile(); + + bool isValid() const; + + QString sourceFilePath() const; + QString targetDirectory() const; + QString targetFilePath() const; + QStringList fileTags() const; + bool isExecutable() const; + +private: + QExplicitlySharedDataPointer<Internal::InstallableFilePrivate> d; +}; + +QBS_EXPORT bool operator==(const InstallableFile &file1, const InstallableFile &file2); +QBS_EXPORT bool operator!=(const InstallableFile &file1, const InstallableFile &file2); +QBS_EXPORT bool operator<(const InstallableFile &file1, const InstallableFile &file2); + + +class QBS_EXPORT ProductData +{ + friend class Internal::ProjectPrivate; +public: + ProductData(); + ProductData(const ProductData &other); + ProductData &operator=(const ProductData &other); + ~ProductData(); + + bool isValid() const; + + QString name() const; + CodeLocation location() const; + QList<TargetArtifact> targetArtifacts() const; + QList<GroupData> groups() const; + bool isEnabled() const; + +private: + QExplicitlySharedDataPointer<Internal::ProductDataPrivate> d; +}; + +QBS_EXPORT bool operator==(const ProductData &lhs, const ProductData &rhs); +QBS_EXPORT bool operator!=(const ProductData &lhs, const ProductData &rhs); +QBS_EXPORT bool operator<(const ProductData &lhs, const ProductData &rhs); + +class QBS_EXPORT ProjectData +{ + friend class Internal::ProjectPrivate; +public: + ProjectData(); + ProjectData(const ProjectData &other); + ProjectData &operator=(const ProjectData &other); + ~ProjectData(); + + bool isValid() const; + + QString name() const; + CodeLocation location() const; + bool isEnabled() const; + QString buildDirectory() const; + QList<ProductData> products() const; + QList<ProjectData> subProjects() const; + QList<ProductData> allProducts() const; + +private: + QExplicitlySharedDataPointer<Internal::ProjectDataPrivate> d; +}; + +QBS_EXPORT bool operator==(const ProjectData &lhs, const ProjectData &rhs); +QBS_EXPORT bool operator!=(const ProjectData &lhs, const ProjectData &rhs); +QBS_EXPORT bool operator<(const ProjectData &lhs, const ProjectData &rhs); + +} // namespace qbs + +#endif // QBS_PROJECTDATA_H diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h new file mode 100644 index 000000000..2eda4d932 --- /dev/null +++ b/src/lib/corelib/api/projectdata_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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_PROJECTDATA_P_H +#define QBS_PROJECTDATA_P_H + +#include "projectdata.h" + +#include <QSharedData> + +namespace qbs { +namespace Internal { + +class GroupDataPrivate : public QSharedData +{ +public: + GroupDataPrivate() : isValid(false) + { } + + QString name; + CodeLocation location; + QStringList filePaths; + QStringList expandedWildcards; + PropertyMap properties; + bool isEnabled; + bool isValid; +}; + +class TargetArtifactPrivate : public QSharedData +{ +public: + TargetArtifactPrivate() : isValid(false) {} + + QString filePath; + QStringList fileTags; + PropertyMap properties; + bool isValid; +}; + +class InstallableFilePrivate: public QSharedData +{ +public: + InstallableFilePrivate() : isValid(false) {} + + QString sourceFilePath; + QString targetDirectory; + QStringList fileTags; + bool isValid; +}; + +class ProductDataPrivate : public QSharedData +{ +public: + ProductDataPrivate() : isValid(false) + { } + + QString name; + CodeLocation location; + QList<GroupData> groups; + QList<TargetArtifact> targetArtifacts; + bool isEnabled; + bool isValid; +}; + +class ProjectDataPrivate : public QSharedData +{ +public: + ProjectDataPrivate() : isValid(false) + { } + + QString name; + CodeLocation location; + bool enabled; + bool isValid; + QList<ProductData> products; + QList<ProjectData> subProjects; + QString buildDir; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp new file mode 100644 index 000000000..e7d879b28 --- /dev/null +++ b/src/lib/corelib/api/projectfileupdater.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** 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 "projectfileupdater.h" + +#include "projectdata.h" +#include "qmljsrewriter.h" + +#include <language/asttools.h> +#include <logging/translator.h> +#include <parser/qmljsast_p.h> +#include <parser/qmljsastvisitor_p.h> +#include <parser/qmljsengine_p.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/qbsassert.h> + +#include <QFile> + +using namespace QbsQmlJS; +using namespace AST; + +namespace qbs { +namespace Internal { + +class ItemFinder : public Visitor +{ +public: + ItemFinder(const CodeLocation &cl) : m_cl(cl), m_item(0) { } + + UiObjectDefinition *item() const { return m_item; } + +private: + bool visit(UiObjectDefinition *ast) + { + if (toCodeLocation(m_cl.fileName(), ast->firstSourceLocation()) == m_cl) { + m_item = ast; + return false; + } + return true; + } + + const CodeLocation m_cl; + UiObjectDefinition *m_item; +}; + +class FilesBindingFinder : public Visitor +{ +public: + FilesBindingFinder(const UiObjectDefinition *startItem) + : m_startItem(startItem), m_binding(0) + { + } + + UiScriptBinding *binding() const { return m_binding; } + +private: + bool visit(UiObjectDefinition *ast) + { + // We start with the direct parent of the binding, so do not descend into any + // other item. + return ast == m_startItem; + } + + bool visit(UiScriptBinding *ast) + { + if (ast->qualifiedId->name.toString() != QLatin1String("files")) + return true; + m_binding = ast; + return false; + } + + const UiObjectDefinition * const m_startItem; + UiScriptBinding *m_binding; +}; + + +ProjectFileUpdater::ProjectFileUpdater(const QString &projectFile) : m_projectFile(projectFile) +{ +} + +void ProjectFileUpdater::apply() +{ + QFile file(m_projectFile); + if (!file.open(QFile::ReadOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for reading: %2") + .arg(m_projectFile, file.errorString())); + } + QString content = QString::fromLocal8Bit(file.readAll()); + file.close(); + Engine engine; + Lexer lexer(&engine); + lexer.setCode(content, 1); + Parser parser(&engine); + if (!parser.parse()) { + QList<DiagnosticMessage> parserMessages = parser.diagnosticMessages(); + if (!parserMessages.isEmpty()) { + ErrorInfo errorInfo; + errorInfo.append(Tr::tr("Failure parsing project file.")); + foreach (const DiagnosticMessage &msg, parserMessages) + errorInfo.append(msg.message, toCodeLocation(file.fileName(), msg.loc)); + throw errorInfo; + } + } + + doApply(content, parser.ast()); + + if (!file.open(QFile::WriteOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for writing: %2") + .arg(m_projectFile, file.errorString())); + } + file.resize(0); + file.write(content.toLocal8Bit()); +} + + +ProjectFileGroupInserter::ProjectFileGroupInserter(const ProductData &product, + const QString &groupName) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_groupName(groupName) +{ +} + +void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) +{ + ItemFinder itemFinder(m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the product item."), + CodeLocation(projectFile())); + } + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + QString groupItemString; + const int productItemIndentation + = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + const int groupItemIndentation = productItemIndentation + 4; + const QString groupItemIndentationString = QString(groupItemIndentation, QLatin1Char(' ')); + groupItemString += groupItemIndentationString + QLatin1String("Group {\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("name: \"") + m_groupName + QLatin1String("\"\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("files: []\n"); + groupItemString += groupItemIndentationString + QLatin1Char('}'); + rewriter.addObject(itemFinder.item()->initializer, groupItemString); + + int lineOffset = 3 + 1; // Our text + a leading newline that is always added by the rewriter. + const QList<ChangeSet::EditOp> &editOps = changeSet.operationList(); + QBS_CHECK(editOps.count() == 1); + const ChangeSet::EditOp &insertOp = editOps.first(); + setLineOffset(lineOffset); + + int insertionLine = fileContent.left(insertOp.pos1).count(QLatin1Char('\n')); + for (int i = 0; i < insertOp.text.count() && insertOp.text.at(i) == QLatin1Char('\n'); ++i) + ++insertionLine; // To account for newlines prepended by the rewriter. + ++insertionLine; // To account for zero-based indexing. + setItemPosition(CodeLocation(projectFile(), insertionLine, + groupItemIndentation + 1)); + changeSet.apply(&fileContent); +} + +static QString getNodeRepresentation(const QString &fileContent, const Node *node) +{ + const quint32 start = node->firstSourceLocation().offset; + const quint32 end = node->lastSourceLocation().end(); + return fileContent.mid(start, end - start); +} + +static const ChangeSet::EditOp &getEditOp(const ChangeSet &changeSet) +{ + const QList<ChangeSet::EditOp> &editOps = changeSet.operationList(); + QBS_CHECK(editOps.count() == 1); + return editOps.first(); +} + +static int getLineOffsetForChangedBinding(const ChangeSet &changeSet, const QString &oldRhs) +{ + return getEditOp(changeSet).text.count(QLatin1Char('\n')) - oldRhs.count(QLatin1Char('\n')); +} + +static int getBindingLine(const ChangeSet &changeSet, const QString &fileContent) +{ + return fileContent.left(getEditOp(changeSet).pos1 + 1).count(QLatin1Char('\n')) + 1; +} + + +ProjectFileFilesAdder::ProjectFileFilesAdder(const ProductData &product, const GroupData &group, + const QStringList &files) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_group(group) + , m_files(files) +{ +} + +void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast) +{ + // Find the item containing the "files" binding. + ItemFinder itemFinder(m_group.isValid() ? m_group.location() : m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the item."), + CodeLocation(projectFile())); + } + + const int itemIndentation + = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + const int bindingIndentation = itemIndentation + 4; + const int arrayElemIndentation = bindingIndentation + 4; + QString newFilesString; + foreach (const QString &relFilePath, m_files) { + newFilesString += QString(arrayElemIndentation, QLatin1Char(' ')); + newFilesString += QLatin1Char('"'); + newFilesString += relFilePath; + newFilesString += QLatin1Char('"'); + newFilesString += QLatin1String(",\n"); + } + newFilesString.chop(2); // Trailing comma and newline. + + // Now get the binding itself. + FilesBindingFinder bindingFinder(itemFinder.item()); + itemFinder.item()->accept(&bindingFinder); + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + + UiScriptBinding * const filesBinding = bindingFinder.binding(); + if (filesBinding) { + if (filesBinding->statement->kind != Node::Kind_ExpressionStatement) + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex.")); // TODO: rename, add new and concat. + const ExpressionStatement * const exprStatement + = static_cast<ExpressionStatement *>(filesBinding->statement); + switch (exprStatement->expression->kind) { + case Node::Kind_ArrayLiteral: { + QString filesString = QLatin1String("[\n"); + const ElementList *elem + = static_cast<ArrayLiteral *>(exprStatement->expression)->elements; + while (elem) { + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += getNodeRepresentation(fileContent, elem->expression); + filesString += QLatin1String(",\n"); + elem = elem->next; + } + filesString += newFilesString; + filesString += QLatin1Char('\n'); + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + break; + } + case Node::Kind_StringLiteral: { + const QString existingElement + = static_cast<StringLiteral *>(exprStatement->expression)->value.toString(); + QString filesString = QLatin1String("[\n"); + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += QLatin1Char('"') + existingElement + QLatin1Char('"'); + filesString += QLatin1String(",\n"); + filesString += newFilesString; + filesString += QLatin1Char('\n'); + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + break; + } + default: { + // Note that we can often do better than simply concatenating: For instance, + // in the case where the existing list is of the form ["a", "b"].concat(myProperty), + // we could keep on parsing until we find the array literal and then merge it with + // the new files, preventing cascading concat() calls. + // But this is not essential and can be implemented when we have some downtime. + const QString rhsRepr = getNodeRepresentation(fileContent, exprStatement->expression); + QString filesString = QLatin1String("[\n"); + filesString += newFilesString; + filesString += QLatin1Char('\n'); + filesString += QString(bindingIndentation, QLatin1Char(' ')); + + // It cannot be the other way around, since the existing right-hand side could + // have string type. + filesString += QString::fromLatin1("].concat(%1)").arg(rhsRepr); + + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + } + } + } else { // Can happen for the product itself, for which the "files" binding is not mandatory. + newFilesString.prepend(QLatin1String("[\n")); + newFilesString += QLatin1Char('\n'); + newFilesString += QString(bindingIndentation, QLatin1Char(' ')); + newFilesString += QLatin1Char(']'); + const QString bindingString = QString(bindingIndentation, QLatin1Char(' ')) + + QLatin1String("files"); + rewriter.addBinding(itemFinder.item()->initializer, bindingString, newFilesString, + Rewriter::ScriptBinding); + } + + setLineOffset(getLineOffsetForChangedBinding(changeSet, getNodeRepresentation(fileContent, + filesBinding->statement))); + const int insertionLine = getBindingLine(changeSet, fileContent) + 1; + const int insertionColumn = (filesBinding ? arrayElemIndentation : bindingIndentation) + 1; + setItemPosition(CodeLocation(projectFile(), insertionLine, insertionColumn)); + changeSet.apply(&fileContent); +} + +ProjectFileFilesRemover::ProjectFileFilesRemover(const ProductData &product, const GroupData &group, + const QStringList &files) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_group(group) + , m_files(files) +{ +} + +void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast) +{ + // Find the item containing the "files" binding. + ItemFinder itemFinder(m_group.isValid() ? m_group.location() : m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the item."), + CodeLocation(projectFile())); + } + + // Now get the binding itself. + FilesBindingFinder bindingFinder(itemFinder.item()); + itemFinder.item()->accept(&bindingFinder); + if (!bindingFinder.binding()) { + throw ErrorInfo(Tr::tr("Could not find the 'files' binding in the project file."), + m_product.location()); + } + + if (bindingFinder.binding()->statement->kind != Node::Kind_ExpressionStatement) + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex.")); + const CodeLocation bindingLocation + = toCodeLocation(projectFile(), bindingFinder.binding()->firstSourceLocation()); + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + + const int itemIndentation + = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + const int bindingIndentation = itemIndentation + 4; + const int arrayElemIndentation = bindingIndentation + 4; + + const ExpressionStatement * const exprStatement + = static_cast<ExpressionStatement *>(bindingFinder.binding()->statement); + switch (exprStatement->expression->kind) { + case Node::Kind_ArrayLiteral: { + QStringList filesToRemove = m_files; + QStringList newFilesList; + const ElementList *elem = static_cast<ArrayLiteral *>(exprStatement->expression)->elements; + while (elem) { + if (elem->expression->kind != Node::Kind_StringLiteral) { + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."), + bindingLocation); + } + const QString existingFile + = static_cast<StringLiteral *>(elem->expression)->value.toString(); + if (!filesToRemove.removeOne(existingFile)) + newFilesList << existingFile; + elem = elem->next; + } + if (!filesToRemove.isEmpty()) { + throw ErrorInfo(Tr::tr("The following files were not found in the 'files' list: %1") + .arg(filesToRemove.join(QLatin1String(", "))), bindingLocation); + } + QString filesString = QLatin1String("[\n"); + foreach (const QString &file, newFilesList) { + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += QString::fromLocal8Bit("\"%1\",\n").arg(file); + } + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + break; + } + case Node::Kind_StringLiteral: { + if (m_files.count() != 1) { + throw ErrorInfo(Tr::tr("Was requested to remove %1 files, but there is only " + "one in the list.").arg(m_files.count()), bindingLocation); + } + const QString existingFile + = static_cast<StringLiteral *>(exprStatement->expression)->value.toString(); + if (existingFile != m_files.first()) { + throw ErrorInfo(Tr::tr("File '1' could not be found in the 'files' list."), + bindingLocation); + } + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + QLatin1String("[]"), Rewriter::ScriptBinding); + break; + } + default: + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."), + bindingLocation); + } + + setLineOffset(getLineOffsetForChangedBinding(changeSet, + getNodeRepresentation(fileContent, exprStatement->expression))); + const int bindingLine = getBindingLine(changeSet, fileContent); + const int bindingColumn = (bindingFinder.binding() + ? arrayElemIndentation : bindingIndentation) + 1; + setItemPosition(CodeLocation(projectFile(), bindingLine, bindingColumn)); + changeSet.apply(&fileContent); +} + + +ProjectFileGroupRemover::ProjectFileGroupRemover(const ProductData &product, const GroupData &group) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_group(group) +{ +} + +void ProjectFileGroupRemover::doApply(QString &fileContent, UiProgram *ast) +{ + ItemFinder productFinder(m_product.location()); + ast->accept(&productFinder); + if (!productFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the product item."), + CodeLocation(projectFile())); + } + + ItemFinder groupFinder(m_group.location()); + productFinder.item()->accept(&groupFinder); + if (!groupFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the group item."), + m_product.location()); + } + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + rewriter.removeObjectMember(groupFinder.item(), productFinder.item()); + + setItemPosition(m_group.location()); + const QList<ChangeSet::EditOp> &editOps = changeSet.operationList(); + QBS_CHECK(editOps.count() == 1); + const ChangeSet::EditOp &op = editOps.first(); + const QString removedText = fileContent.mid(op.pos1, op.length1); + setLineOffset(-removedText.count(QLatin1Char('\n'))); + + changeSet.apply(&fileContent); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/api/projectfileupdater.h b/src/lib/corelib/api/projectfileupdater.h new file mode 100644 index 000000000..ba125dc41 --- /dev/null +++ b/src/lib/corelib/api/projectfileupdater.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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_PROJECTFILEUPDATER_H +#define QBS_PROJECTFILEUPDATER_H + +#include "projectdata.h" + +#include <tools/error.h> +#include <tools/codelocation.h> + +#include <QStringList> + +namespace QbsQmlJS { namespace AST { class UiProgram; } } + +namespace qbs { +namespace Internal { + +class ProjectFileUpdater +{ +public: + void apply(); + + CodeLocation itemPosition() const { return m_itemPosition; } + int lineOffset() const { return m_lineOffset; } + +protected: + ProjectFileUpdater(const QString &projectFile); + + QString projectFile() const { return m_projectFile; } + + void setLineOffset(int offset) { m_lineOffset = offset; } + void setItemPosition(const CodeLocation &cl) { m_itemPosition = cl; } + +private: + virtual void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) = 0; + + const QString m_projectFile; + CodeLocation m_itemPosition; + int m_lineOffset; +}; + + +class ProjectFileGroupInserter : public ProjectFileUpdater +{ +public: + ProjectFileGroupInserter(const ProductData &product, const QString &groupName); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast); + + const ProductData m_product; + const QString m_groupName; +}; + + +class ProjectFileFilesAdder : public ProjectFileUpdater +{ +public: + ProjectFileFilesAdder(const ProductData &product, const GroupData &group, + const QStringList &files); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast); + + const ProductData m_product; + const GroupData m_group; + const QStringList m_files; +}; + +class ProjectFileFilesRemover : public ProjectFileUpdater +{ +public: + ProjectFileFilesRemover(const ProductData &product, const GroupData &group, + const QStringList &files); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast); + + const ProductData m_product; + const GroupData m_group; + const QStringList m_files; +}; + +class ProjectFileGroupRemover : public ProjectFileUpdater +{ +public: + ProjectFileGroupRemover(const ProductData &product, const GroupData &group); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast); + + const ProductData m_product; + const GroupData m_group; +}; + +} // namespace Internal +} // namespace qbs + + +#endif // Include guard. diff --git a/src/lib/corelib/api/propertymap_p.h b/src/lib/corelib/api/propertymap_p.h new file mode 100644 index 000000000..e57e323cc --- /dev/null +++ b/src/lib/corelib/api/propertymap_p.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_PROPERTYMAP_P_H +#define QBS_PROPERTYMAP_P_H + +#include <language/language.h> + +namespace qbs { +namespace Internal { + +class PropertyMapPrivate +{ +public: + PropertyMapPtr m_map; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYMAP_P_H diff --git a/src/lib/corelib/api/qmljsrewriter.cpp b/src/lib/corelib/api/qmljsrewriter.cpp new file mode 100644 index 000000000..ecb59e778 --- /dev/null +++ b/src/lib/corelib/api/qmljsrewriter.cpp @@ -0,0 +1,718 @@ +/**************************************************************************** +** +** 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 "qmljsrewriter.h" + +#include <parser/qmljsast_p.h> + +#include <QTextBlock> +#include <QTextCursor> +#include <QTextDocument> + +namespace QbsQmlJS { +using namespace AST; + +static QString toString(UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + + +Rewriter::Rewriter(const QString &originalText, + ChangeSet *changeSet, + const QStringList &propertyOrder) + : m_originalText(originalText) + , m_changeSet(changeSet) + , m_propertyOrder(propertyOrder) +{ + Q_ASSERT(changeSet); +} + +Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType) +{ + UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, + propertyName, + m_propertyOrder); + return addBinding(ast, propertyName, propertyValue, bindingType, insertAfter); +} + +Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType, + UiObjectMemberList *insertAfter) +{ + SourceLocation endOfPreviousMember; + SourceLocation startOfNextMember; + + if (insertAfter == 0 || insertAfter->member == 0) { + // insert as first member + endOfPreviousMember = ast->lbraceToken; + + if (ast->members && ast->members->member) + startOfNextMember = ast->members->member->firstSourceLocation(); + else + startOfNextMember = ast->rbraceToken; + } else { + endOfPreviousMember = insertAfter->member->lastSourceLocation(); + + if (insertAfter->next && insertAfter->next->member) + startOfNextMember = insertAfter->next->member->firstSourceLocation(); + else + startOfNextMember = ast->rbraceToken; + } + const bool isOneLiner = endOfPreviousMember.startLine == startOfNextMember.startLine; + bool needsPreceedingSemicolon = false; + bool needsTrailingSemicolon = false; + + if (isOneLiner) { + if (insertAfter == 0) { // we're inserting after an lbrace + if (ast->members) { // we're inserting before a member (and not the rbrace) + needsTrailingSemicolon = bindingType == ScriptBinding; + } + } else { // we're inserting after a member, not after the lbrace + if (endOfPreviousMember.isValid()) { // there already is a semicolon after the previous member + if (insertAfter->next && insertAfter->next->member) { // and the after us there is a member, not an rbrace, so: + needsTrailingSemicolon = bindingType == ScriptBinding; + } + } else { // there is no semicolon after the previous member (probably because there is an rbrace after us/it, so: + needsPreceedingSemicolon = true; + } + } + } + + QString newPropertyTemplate; + switch (bindingType) { + case ArrayBinding: + newPropertyTemplate = QLatin1String("%1: [\n%2\n]"); + break; + + case ObjectBinding: + newPropertyTemplate = QLatin1String("%1: %2"); + break; + + case ScriptBinding: + newPropertyTemplate = QLatin1String("%1: %2"); + break; + + default: + Q_ASSERT(!"unknown property type"); + } + + if (isOneLiner) { + if (needsPreceedingSemicolon) + newPropertyTemplate.prepend(QLatin1Char(';')); + newPropertyTemplate.prepend(QLatin1Char(' ')); + if (needsTrailingSemicolon) + newPropertyTemplate.append(QLatin1Char(';')); + } else { + newPropertyTemplate.prepend(QLatin1Char('\n')); + } + + m_changeSet->insert(endOfPreviousMember.end(), + newPropertyTemplate.arg(propertyName, propertyValue)); + + return Range(endOfPreviousMember.end(), endOfPreviousMember.end()); +} + +UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members, + const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString()); + + UiObjectMemberList *lastObjectDef = 0; + UiObjectMemberList *lastNonObjectDef = 0; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast<UiObjectDefinition*>(member)) + lastObjectDef = iter; + else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast<UiPublicMember*>(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *members, + const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString()); + + UiArrayMemberList *lastObjectDef = 0; + UiArrayMemberList *lastNonObjectDef = 0; + + for (UiArrayMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast<UiObjectDefinition*>(member)) + lastObjectDef = iter; + else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast<UiPublicMember*>(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members, + const QString &propertyName, + const QStringList &propertyOrder) +{ + if (!members) + return 0; // empty members + + QHash<QString, UiObjectMemberList *> orderedMembers; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + + if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + orderedMembers[toString(arrayBinding->qualifiedId)] = iter; + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + orderedMembers[toString(objectBinding->qualifiedId)] = iter; + else if (cast<UiObjectDefinition*>(member)) + orderedMembers[QString::null] = iter; + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + orderedMembers[toString(scriptBinding->qualifiedId)] = iter; + else if (cast<UiPublicMember*>(member)) + orderedMembers[QLatin1String("property")] = iter; + } + + int idx = propertyOrder.indexOf(propertyName); + if (idx == -1) + idx = propertyOrder.indexOf(QString()); + if (idx == -1) + idx = propertyOrder.size() - 1; + + for (; idx > 0; --idx) { + const QString prop = propertyOrder.at(idx - 1); + UiObjectMemberList *candidate = orderedMembers.value(prop, 0); + if (candidate != 0) + return candidate; + } + + return 0; +} + +void Rewriter::changeBinding(UiObjectInitializer *ast, + const QString &propertyName, + const QString &newValue, + BindingType binding) +{ + QString prefix, suffix; + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx != -1) { + prefix = propertyName.left(dotIdx); + suffix = propertyName.mid(dotIdx + 1); + } + + for (UiObjectMemberList *members = ast->members; members; members = members->next) { + UiObjectMember *member = members->member; + + // for non-grouped properties: + if (isMatchingPropertyMember(propertyName, member)) { + switch (binding) { + case ArrayBinding: + insertIntoArray(cast<UiArrayBinding*>(member), newValue); + break; + + case ObjectBinding: + replaceMemberValue(member, newValue, false); + break; + + case ScriptBinding: + replaceMemberValue(member, newValue, nextMemberOnSameLine(members)); + break; + + default: + Q_ASSERT(!"Unhandled QmlRefactoring::PropertyType"); + } + + break; + // for grouped properties: + } else if (!prefix.isEmpty()) { + if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) { + if (toString(def->qualifiedTypeNameId) == prefix) + changeBinding(def->initializer, suffix, newValue, binding); + } + } + } +} + +void Rewriter::replaceMemberValue(UiObjectMember *propertyMember, + const QString &newValue, + bool needsSemicolon) +{ + QString replacement = newValue; + int startOffset = -1; + int endOffset = -1; + if (UiObjectBinding *objectBinding = AST::cast<UiObjectBinding *>(propertyMember)) { + startOffset = objectBinding->qualifiedTypeNameId->identifierToken.offset; + endOffset = objectBinding->initializer->rbraceToken.end(); + } else if (UiScriptBinding *scriptBinding = AST::cast<UiScriptBinding *>(propertyMember)) { + startOffset = scriptBinding->statement->firstSourceLocation().offset; + endOffset = scriptBinding->statement->lastSourceLocation().end(); + } else if (UiArrayBinding *arrayBinding = AST::cast<UiArrayBinding *>(propertyMember)) { + startOffset = arrayBinding->lbracketToken.offset; + endOffset = arrayBinding->rbracketToken.end(); + } else if (UiPublicMember *publicMember = AST::cast<UiPublicMember*>(propertyMember)) { + if (publicMember->statement) { + startOffset = publicMember->statement->firstSourceLocation().offset; + if (publicMember->semicolonToken.isValid()) + endOffset = publicMember->semicolonToken.end(); + else + endOffset = publicMember->statement->lastSourceLocation().offset; + } else { + startOffset = publicMember->lastSourceLocation().end(); + endOffset = startOffset; + if (publicMember->semicolonToken.isValid()) + startOffset = publicMember->semicolonToken.offset; + replacement.prepend(QLatin1String(": ")); + } + } else { + return; + } + + if (needsSemicolon) + replacement += QLatin1Char(';'); + + m_changeSet->replace(startOffset, endOffset, replacement); +} + +bool Rewriter::isMatchingPropertyMember(const QString &propertyName, + UiObjectMember *member) +{ + if (UiPublicMember *publicMember = cast<UiPublicMember*>(member)) + return publicMember->name == propertyName; + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + return toString(objectBinding->qualifiedId) == propertyName; + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + return toString(scriptBinding->qualifiedId) == propertyName; + else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + return toString(arrayBinding->qualifiedId) == propertyName; + else + return false; +} + +bool Rewriter::nextMemberOnSameLine(UiObjectMemberList *members) +{ + if (members && members->next && members->next->member) + return members->next->member->firstSourceLocation().startLine == members->member->lastSourceLocation().startLine; + else + return false; +} + +void Rewriter::insertIntoArray(UiArrayBinding *ast, const QString &newValue) +{ + if (!ast) + return; + + UiObjectMember *lastMember = 0; + for (UiArrayMemberList *iter = ast->members; iter; iter = iter->next) { + lastMember = iter->member; + } + + if (!lastMember) + return; + + const int insertionPoint = lastMember->lastSourceLocation().end(); + m_changeSet->insert(insertionPoint, QLatin1String(",\n") + newValue); +} + +void Rewriter::removeBindingByName(UiObjectInitializer *ast, const QString &propertyName) +{ + QString prefix; + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx != -1) + prefix = propertyName.left(dotIdx); + + for (UiObjectMemberList *it = ast->members; it; it = it->next) { + UiObjectMember *member = it->member; + + // run full name match (for ungrouped properties): + if (isMatchingPropertyMember(propertyName, member)) { + removeMember(member); + // check for grouped properties: + } else if (!prefix.isEmpty()) { + if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) { + if (toString(def->qualifiedTypeNameId) == prefix) + removeGroupedProperty(def, propertyName); + } + } + } +} + +void Rewriter::removeGroupedProperty(UiObjectDefinition *ast, + const QString &propertyName) +{ + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx == -1) + return; + + const QString propName = propertyName.mid(dotIdx + 1); + + UiObjectMember *wanted = 0; + unsigned memberCount = 0; + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + ++memberCount; + UiObjectMember *member = it->member; + + if (!wanted && isMatchingPropertyMember(propName, member)) + wanted = member; + } + + if (!wanted) + return; + if (memberCount == 1) + removeMember(ast); + else + removeMember(wanted); +} + +void Rewriter::removeMember(UiObjectMember *member) +{ + int start = member->firstSourceLocation().offset; + int end = member->lastSourceLocation().end(); + + includeSurroundingWhitespace(m_originalText, start, end); + + m_changeSet->remove(start, end); +} + +bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, int &end) +{ + bool includeStartingWhitespace = true; + bool paragraphFound = false; + bool paragraphSkipped = false; + + if (end >= 0) { + QChar c = source.at(end); + + while (c.isSpace()) { + ++end; + if (c.unicode() == 10) { + paragraphFound = true; + paragraphSkipped = true; + break; + } else if (end == source.length()) { + break; + } + + c = source.at(end); + } + + includeStartingWhitespace = paragraphFound; + } + + paragraphFound = false; + if (includeStartingWhitespace) { + while (start > 0) { + const QChar c = source.at(start - 1); + + if (c.unicode() == 10) { + paragraphFound = true; + break; + } + if (!c.isSpace()) + break; + + --start; + } + } + if (!paragraphFound && paragraphSkipped) //keep the line ending + --end; + + return paragraphFound; +} + +void Rewriter::includeLeadingEmptyLine(const QString &source, int &start) +{ + QTextDocument doc(source); + + if (start == 0) + return; + + if (doc.characterAt(start - 1) != QChar::ParagraphSeparator) + return; + + QTextCursor tc(&doc); + tc.setPosition(start); + const int blockNr = tc.blockNumber(); + if (blockNr == 0) + return; + + const QTextBlock prevBlock = tc.block().previous(); + const QString trimmedPrevBlockText = prevBlock.text().trimmed(); + if (!trimmedPrevBlockText.isEmpty()) + return; + + start = prevBlock.position(); +} + +void Rewriter::includeEmptyGroupedProperty(UiObjectDefinition *groupedProperty, UiObjectMember *memberToBeRemoved, int &start, int &end) +{ + if (groupedProperty->qualifiedTypeNameId && !groupedProperty->qualifiedTypeNameId->name.isEmpty() + && groupedProperty->qualifiedTypeNameId->name.at(0).isLower()) { + // grouped property + UiObjectMemberList *memberIter = groupedProperty->initializer->members; + while (memberIter) { + if (memberIter->member != memberToBeRemoved) + return; + memberIter = memberIter->next; + } + start = groupedProperty->firstSourceLocation().begin(); + end = groupedProperty->lastSourceLocation().end(); + } +} + +#if 0 +UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString::null); + + UiObjectMemberList *lastObjectDef = 0; + UiObjectMemberList *lastNonObjectDef = 0; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast<UiObjectDefinition*>(member)) + lastObjectDef = iter; + else if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast<UiPublicMember*>(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder) +{ + if (!members) + return 0; // empty members + + QHash<QString, UiObjectMemberList *> orderedMembers; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + + if (UiArrayBinding *arrayBinding = cast<UiArrayBinding*>(member)) + orderedMembers[toString(arrayBinding->qualifiedId)] = iter; + else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(member)) + orderedMembers[toString(objectBinding->qualifiedId)] = iter; + else if (cast<UiObjectDefinition*>(member)) + orderedMembers[QString::null] = iter; + else if (UiScriptBinding *scriptBinding = cast<UiScriptBinding*>(member)) + orderedMembers[toString(scriptBinding->qualifiedId)] = iter; + else if (cast<UiPublicMember*>(member)) + orderedMembers[QLatin1String("property")] = iter; + } + + int idx = propertyOrder.indexOf(propertyName); + if (idx == -1) + idx = propertyOrder.indexOf(QString()); + if (idx == -1) + idx = propertyOrder.size() - 1; + + for (; idx > 0; --idx) { + const QString prop = propertyOrder.at(idx - 1); + UiObjectMemberList *candidate = orderedMembers.value(prop, 0); + if (candidate != 0) + return candidate; + } + + return 0; +} + +#endif + +void Rewriter::appendToArrayBinding(UiArrayBinding *arrayBinding, + const QString &content) +{ + UiObjectMember *lastMember = 0; + for (UiArrayMemberList *iter = arrayBinding->members; iter; iter = iter->next) + if (iter->member) + lastMember = iter->member; + + if (!lastMember) + return; // an array binding cannot be empty, so there will (or should) always be a last member. + + const int insertionPoint = lastMember->lastSourceLocation().end(); + + m_changeSet->insert(insertionPoint, QLatin1String(",\n") + content); +} + +Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content) +{ + UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); + return addObject(ast, content, insertAfter); +} + +Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content, UiObjectMemberList *insertAfter) +{ + int insertionPoint; + QString textToInsert; + if (insertAfter && insertAfter->member) { + insertionPoint = insertAfter->member->lastSourceLocation().end(); + textToInsert += QLatin1String("\n"); + } else { + insertionPoint = ast->lbraceToken.end(); + } + + textToInsert += content; + m_changeSet->insert(insertionPoint, QLatin1String("\n") + textToInsert); + + return Range(insertionPoint, insertionPoint); +} + +Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content) +{ + UiArrayMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); + return addObject(ast, content, insertAfter); +} + +Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content, UiArrayMemberList *insertAfter) +{ + int insertionPoint; + QString textToInsert; + if (insertAfter && insertAfter->member) { + insertionPoint = insertAfter->member->lastSourceLocation().end(); + textToInsert = QLatin1String(",\n") + content; + } else { + insertionPoint = ast->lbracketToken.end(); + textToInsert += QLatin1String("\n") + content + QLatin1Char(','); + } + + m_changeSet->insert(insertionPoint, textToInsert); + + return Range(insertionPoint, insertionPoint); +} + +void Rewriter::removeObjectMember(UiObjectMember *member, UiObjectMember *parent) +{ + int start = member->firstSourceLocation().offset; + int end = member->lastSourceLocation().end(); + + if (UiArrayBinding *parentArray = cast<UiArrayBinding *>(parent)) { + extendToLeadingOrTrailingComma(parentArray, member, start, end); + } else { + if (UiObjectDefinition *parentObjectDefinition = cast<UiObjectDefinition *>(parent)) + includeEmptyGroupedProperty(parentObjectDefinition, member, start, end); + includeSurroundingWhitespace(m_originalText, start, end); + } + + includeLeadingEmptyLine(m_originalText, start); + m_changeSet->remove(start, end); +} + +void Rewriter::extendToLeadingOrTrailingComma(UiArrayBinding *parentArray, + UiObjectMember *member, + int &start, + int &end) const +{ + UiArrayMemberList *currentMember = 0; + for (UiArrayMemberList *it = parentArray->members; it; it = it->next) { + if (it->member == member) { + currentMember = it; + break; + } + } + + if (!currentMember) + return; + + if (currentMember->commaToken.isValid()) { + // leading comma + start = currentMember->commaToken.offset; + if (includeSurroundingWhitespace(m_originalText, start, end)) + --end; + } else if (currentMember->next && currentMember->next->commaToken.isValid()) { + // trailing comma + end = currentMember->next->commaToken.end(); + includeSurroundingWhitespace(m_originalText, start, end); + } else { + // array with 1 element, so remove the complete binding + start = parentArray->firstSourceLocation().offset; + end = parentArray->lastSourceLocation().end(); + includeSurroundingWhitespace(m_originalText, start, end); + } +} + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/api/qmljsrewriter.h b/src/lib/corelib/api/qmljsrewriter.h new file mode 100644 index 000000000..09a638ab0 --- /dev/null +++ b/src/lib/corelib/api/qmljsrewriter.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 QMLJSREWRITER_H +#define QMLJSREWRITER_H + +#include "changeset.h" + +#include <parser/qmljsastfwd_p.h> + +#include <QStringList> + +namespace QbsQmlJS { + +class Rewriter +{ +public: + enum BindingType { + ScriptBinding, + ObjectBinding, + ArrayBinding + }; + + typedef ChangeSet::Range Range; + +public: + Rewriter(const QString &originalText, + ChangeSet *changeSet, + const QStringList &propertyOrder); + + Range addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType); + + Range addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType, + AST::UiObjectMemberList *insertAfter); + + void changeBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &newValue, + BindingType binding); + + void removeBindingByName(AST::UiObjectInitializer *ast, const QString &propertyName); + + void appendToArrayBinding(AST::UiArrayBinding *arrayBinding, + const QString &content); + + Range addObject(AST::UiObjectInitializer *ast, const QString &content); + Range addObject(AST::UiObjectInitializer *ast, const QString &content, AST::UiObjectMemberList *insertAfter); + Range addObject(AST::UiArrayBinding *ast, const QString &content); + Range addObject(AST::UiArrayBinding *ast, const QString &content, AST::UiArrayMemberList *insertAfter); + + void removeObjectMember(AST::UiObjectMember *member, AST::UiObjectMember *parent); + + static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QStringList &propertyOrder); + static AST::UiArrayMemberList *searchMemberToInsertAfter(AST::UiArrayMemberList *members, const QStringList &propertyOrder); + static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder); + + static bool includeSurroundingWhitespace(const QString &source, int &start, int &end); + static void includeLeadingEmptyLine(const QString &source, int &start); + static void includeEmptyGroupedProperty(AST::UiObjectDefinition *groupedProperty, AST::UiObjectMember *memberToBeRemoved, int &start, int &end); + +private: + void replaceMemberValue(AST::UiObjectMember *propertyMember, + const QString &newValue, + bool needsSemicolon); + static bool isMatchingPropertyMember(const QString &propertyName, + AST::UiObjectMember *member); + static bool nextMemberOnSameLine(AST::UiObjectMemberList *members); + + void insertIntoArray(AST::UiArrayBinding* ast, const QString &newValue); + + void removeMember(AST::UiObjectMember *member); + void removeGroupedProperty(AST::UiObjectDefinition *ast, + const QString &propertyName); + + void extendToLeadingOrTrailingComma(AST::UiArrayBinding *parentArray, + AST::UiObjectMember *member, + int &start, + int &end) const; + +private: + QString m_originalText; + ChangeSet *m_changeSet; + const QStringList m_propertyOrder; +}; + +} // namespace QbsQmlJS + +#endif // QMLJSREWRITER_H diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp new file mode 100644 index 000000000..43b5c5605 --- /dev/null +++ b/src/lib/corelib/api/runenvironment.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** 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 "runenvironment.h" + +#include <api/projectdata.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/hostosinfo.h> +#include <tools/preferences.h> +#include <tools/propertyfinder.h> + +#include <QDir> +#include <QProcess> +#include <QProcessEnvironment> +#include <QScopedPointer> +#include <QTemporaryFile> +#include <QVariantMap> + +#include <stdlib.h> + +namespace qbs { +using namespace Internal; + +class RunEnvironment::RunEnvironmentPrivate +{ +public: + RunEnvironmentPrivate(const ResolvedProductPtr &product, + const QProcessEnvironment &environment, Settings *settings, const Logger &logger) + : engine(logger) + , resolvedProduct(product) + , environment(environment) + , settings(settings) + , logger(logger) + { + } + + ScriptEngine engine; + const ResolvedProductPtr resolvedProduct; + const QProcessEnvironment environment; + Settings * const settings; + Logger logger; +}; + +RunEnvironment::RunEnvironment(const ResolvedProductPtr &product, + const QProcessEnvironment &environment, Settings *settings, const Logger &logger) + : d(new RunEnvironmentPrivate(product, environment, settings, logger)) +{ +} + +RunEnvironment::~RunEnvironment() +{ + delete d; +} + +int RunEnvironment::runShell() +{ + d->resolvedProduct->setupBuildEnvironment(&d->engine, d->environment); + + const QString productId = d->resolvedProduct->name; + d->logger.qbsInfo() << Tr::tr("Starting shell for target '%1'.").arg(productId); + const QProcessEnvironment environment = d->resolvedProduct->buildEnvironment; +#if defined(Q_OS_LINUX) + clearenv(); +#endif + foreach (const QString &key, environment.keys()) + qputenv(key.toLocal8Bit().constData(), environment.value(key).toLocal8Bit()); + QString command; + QScopedPointer<QTemporaryFile> envFile; + if (HostOsInfo::isWindowsHost()) { + command = environment.value(QLatin1String("COMSPEC")); + if (command.isEmpty()) + command = QLatin1String("cmd"); + const QString prompt = environment.value(QLatin1String("PROMPT")); + command += QLatin1String(" /k prompt [qbs] ") + prompt; + } else { + const QVariantMap qbsProps = d->resolvedProduct->topLevelProject()->buildConfiguration() + .value(QLatin1String("qbs")).toMap(); + const QString profileName = qbsProps.value(QLatin1String("profile")).toString(); + command = Preferences(d->settings, profileName).shell(); + if (command.isEmpty()) + command = environment.value(QLatin1String("SHELL"), QLatin1String("/bin/sh")); + + // Yes, we have to use this prcoedure. PS1 is not inherited from the environment. + const QString prompt = QLatin1String("qbs ") + productId + QLatin1String(" $ "); + envFile.reset(new QTemporaryFile); + if (envFile->open()) { + if (command.endsWith(QLatin1String("bash"))) + command += " --posix"; // Teach bash some manners. + const QString promptLine = QLatin1String("PS1='") + prompt + QLatin1String("'\n"); + envFile->write(promptLine.toLocal8Bit()); + envFile->close(); + qputenv("ENV", envFile->fileName().toLocal8Bit()); + } else { + d->logger.qbsWarning() << Tr::tr("Setting custom shell prompt failed."); + } + } + + // We cannot use QProcess, since it does not do stdin forwarding. + return system(command.toLocal8Bit().constData()); +} + +int RunEnvironment::runTarget(const QString &targetBin, const QStringList &arguments) +{ + const QStringList targetOS = PropertyFinder().propertyValue( + d->resolvedProduct->properties->value(), + QLatin1String("qbs"), + QLatin1String("targetOS")).toStringList(); + + QString targetExecutable = targetBin; + QStringList targetArguments = arguments; + const QString completeSuffix = QFileInfo(targetBin).completeSuffix(); + + if (targetOS.contains(QLatin1String("windows"))) { + if (completeSuffix == QLatin1String("msi")) { + targetExecutable = QLatin1String("msiexec"); + targetArguments.prepend(QDir::toNativeSeparators(targetBin)); + targetArguments.prepend(QLatin1String("/package")); + } + + // Run Windows executables through Wine when not on Windows + if (!HostOsInfo::isWindowsHost()) { + targetArguments.prepend(targetExecutable); + targetExecutable = QLatin1String("wine"); + } + } + + // Only check if the target is executable if we're not running it through another + // known application such as msiexec or wine, as we can't check in this case anyways + if (targetBin == targetExecutable && !QFileInfo(targetExecutable).isExecutable()) { + d->logger.qbsLog(LoggerError) << Tr::tr("File '%1' is not an executable.") + .arg(QDir::toNativeSeparators(targetExecutable)); + return EXIT_FAILURE; + } + + d->resolvedProduct->setupRunEnvironment(&d->engine, d->environment); + + d->logger.qbsInfo() << Tr::tr("Starting target '%1'.").arg(QDir::toNativeSeparators(targetBin)); + QProcess process; + process.setProcessEnvironment(d->resolvedProduct->runEnvironment); + process.setProcessChannelMode(QProcess::ForwardedChannels); + process.start(targetExecutable, targetArguments); + process.waitForFinished(-1); + return process.exitCode(); +} + +} // namespace qbs diff --git a/src/lib/corelib/api/runenvironment.h b/src/lib/corelib/api/runenvironment.h new file mode 100644 index 000000000..18ca65aae --- /dev/null +++ b/src/lib/corelib/api/runenvironment.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_RUNENVIRONMENT_H +#define QBS_RUNENVIRONMENT_H + +#include <language/forward_decls.h> +#include <tools/qbs_export.h> + +#include <QStringList> + +QT_BEGIN_NAMESPACE +class QProcessEnvironment; +QT_END_NAMESPACE + +namespace qbs { +class Settings; + +namespace Internal { +class Logger; +class ResolvedProduct; +} // namespace Internal + +class QBS_EXPORT RunEnvironment +{ + friend class Project; +public: + ~RunEnvironment(); + + // These can throw an Error + int runShell(); + int runTarget(const QString &targetBin, const QStringList &arguments); + +private: + RunEnvironment(const Internal::ResolvedProductPtr &product, + const QProcessEnvironment &environment, Settings *settings, + const Internal::Logger &logger); + + class RunEnvironmentPrivate; + RunEnvironmentPrivate * const d; +}; + +} // namespace qbs + +#endif // QBS_RUNENVIRONMENT_H diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp new file mode 100644 index 000000000..fa419a734 --- /dev/null +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 "abstractcommandexecutor.h" + +#include "command.h" + +#include <logging/translator.h> +#include <tools/error.h> + +namespace qbs { +namespace Internal { + +AbstractCommandExecutor::AbstractCommandExecutor(const Logger &logger, QObject *parent) + : QObject(parent) + , m_command(0) + , m_transformer(0) + , m_mainThreadScriptEngine(0) + , m_dryRun(false) + , m_logger(logger) +{ +} + +void AbstractCommandExecutor::start(Transformer *transformer, const AbstractCommand *cmd) +{ + m_transformer = transformer; + m_command = cmd; + if (!m_command->isSilent()) { + if (m_command->description().isEmpty()) { + m_logger.printWarning(ErrorInfo(Tr::tr("Command is not marked silent, but has no " + "description."), m_command->codeLocation())); + } else { + emit reportCommandDescription(m_command->highlight(), m_command->description()); + } + } + doStart(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.h b/src/lib/corelib/buildgraph/abstractcommandexecutor.h new file mode 100644 index 000000000..0abb5c137 --- /dev/null +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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_ABSTRACTCOMMANDEXECUTOR_H +#define QBS_ABSTRACTCOMMANDEXECUTOR_H + +#include <logging/logger.h> + +#include <QObject> + +namespace qbs { +class ErrorInfo; + +namespace Internal { +class AbstractCommand; +class ScriptEngine; +class Transformer; + +class AbstractCommandExecutor : public QObject +{ + Q_OBJECT +public: + explicit AbstractCommandExecutor(const Internal::Logger &logger, QObject *parent = 0); + + void setMainThreadScriptEngine(ScriptEngine *engine) { m_mainThreadScriptEngine = engine; } + void setDryRunEnabled(bool enabled) { m_dryRun = enabled; } + + virtual void waitForFinished() = 0; + +public slots: + void start(Transformer *transformer, const AbstractCommand *cmd); + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void error(const qbs::ErrorInfo &err); + void finished(); + +protected: + const AbstractCommand *command() const { return m_command; } + Transformer *transformer() const { return m_transformer; } + ScriptEngine *scriptEngine() const { return m_mainThreadScriptEngine; } + bool dryRun() const { return m_dryRun; } + Internal::Logger logger() const { return m_logger; } + +private: + virtual void doStart() = 0; + +private: + const AbstractCommand *m_command; + Transformer *m_transformer; + ScriptEngine *m_mainThreadScriptEngine; + bool m_dryRun; + Internal::Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ABSTRACTCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/artifact.cpp b/src/lib/corelib/buildgraph/artifact.cpp new file mode 100644 index 000000000..73a102827 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifact.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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 "artifact.h" + +#include "transformer.h" + +#include <language/propertymapinternal.h> +#include <tools/fileinfo.h> +#include <tools/persistence.h> + +QT_BEGIN_NAMESPACE + +static QDataStream &operator >>(QDataStream &s, qbs::Internal::Artifact::ArtifactType &t) +{ + int i; + s >> i; + t = static_cast<qbs::Internal::Artifact::ArtifactType>(i); + return s; +} + +static QDataStream &operator <<(QDataStream &s, const qbs::Internal::Artifact::ArtifactType &t) +{ + return s << (int)t; +} + +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +Artifact::Artifact() +{ + initialize(); +} + +Artifact::~Artifact() +{ +} + +void Artifact::initialize() +{ + artifactType = Unknown; + buildState = Untouched; + inputsScanned = false; + timestampRetrieved = false; + alwaysUpdated = true; +} + +void Artifact::load(PersistentPool &pool) +{ + FileResourceBase::load(pool); + pool.loadContainer(children); + + // restore parents of the loaded children + for (ArtifactList::const_iterator it = children.constBegin(); it != children.constEnd(); ++it) + (*it)->parents.insert(this); + + pool.loadContainer(childrenAddedByScanner); + pool.loadContainer(fileDependencies); + properties = pool.idLoadS<PropertyMapInternal>(); + transformer = pool.idLoadS<Transformer>(); + unsigned char c; + pool.stream() + >> fileTags + >> artifactType + >> autoMocTimestamp + >> c; + alwaysUpdated = c; +} + +void Artifact::store(PersistentPool &pool) const +{ + FileResourceBase::store(pool); + // Do not store parents to avoid recursion. + pool.storeContainer(children); + pool.storeContainer(childrenAddedByScanner); + pool.storeContainer(fileDependencies); + pool.store(properties); + pool.store(transformer); + pool.stream() + << fileTags + << artifactType + << autoMocTimestamp + << static_cast<unsigned char>(alwaysUpdated); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifact.h b/src/lib/corelib/buildgraph/artifact.h new file mode 100644 index 000000000..b2e9b0703 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifact.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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_ARTIFACT_H +#define QBS_ARTIFACT_H + +#include "artifactlist.h" +#include "filedependency.h" +#include "forward_decls.h" +#include <language/filetags.h> +#include <language/forward_decls.h> +#include <tools/filetime.h> +#include <tools/persistentobject.h> +#include <tools/weakpointer.h> + +#include <QSet> +#include <QString> + +namespace qbs { +namespace Internal { +class Logger; + +/** + * The Artifact class + * + * Let artifact P be the parent of artifact C. Thus C is child of P. + * C produces P using the transformer P.transformer. + * + * + */ +class Artifact : public FileResourceBase +{ +public: + Artifact(); + ~Artifact(); + + ArtifactList parents; + ArtifactList children; + ArtifactList childrenAddedByScanner; + QSet<FileDependency *> fileDependencies; + FileTags fileTags; + WeakPointer<ResolvedProduct> product; + TransformerPtr transformer; + PropertyMapPtr properties; + + enum ArtifactType + { + Unknown = 1, + SourceFile = 2, + Generated = 4 + }; + + enum BuildState + { + Untouched = 0, + Buildable, + Building, + Built + }; + + ArtifactType artifactType; + FileTime autoMocTimestamp; + BuildState buildState; // Do not serialize. Will be refreshed for every build. + bool inputsScanned : 1; // Do not serialize. Will be refreshed for every build. + bool timestampRetrieved : 1; // Do not serialize. Will be refreshed for every build. + bool alwaysUpdated : 1; + + void initialize(); + +private: + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +// debugging helper +inline QString toString(Artifact::ArtifactType t) +{ + switch (t) { + case Artifact::SourceFile: + return QLatin1String("SourceFile"); + case Artifact::Generated: + return QLatin1String("Generated"); + case Artifact::Unknown: + default: + return QLatin1String("Unknown"); + } +} + +// debugging helper +inline QString toString(Artifact::BuildState s) +{ + switch (s) { + case Artifact::Untouched: + return QLatin1String("Untouched"); + case Artifact::Buildable: + return QLatin1String("Buildable"); + case Artifact::Building: + return QLatin1String("Building"); + case Artifact::Built: + return QLatin1String("Built"); + default: + return QLatin1String("Unknown"); + } +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACT_H diff --git a/src/lib/corelib/buildgraph/artifactcleaner.cpp b/src/lib/corelib/buildgraph/artifactcleaner.cpp new file mode 100644 index 000000000..98fed728e --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactcleaner.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** 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 "artifactcleaner.h" + +#include "artifact.h" +#include "artifactvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "transformer.h" + +#include <language/language.h> +#include <logging/translator.h> +#include <tools/cleanoptions.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> + +#include <QCoreApplication> +#include <QDir> +#include <QDirIterator> +#include <QFileInfo> +#include <QSet> +#include <QString> + +namespace qbs { +namespace Internal { + +static void printRemovalMessage(const QString &path, bool dryRun, const Logger &logger) +{ + if (dryRun) + logger.qbsInfo() << Tr::tr("Would remove '%1'.").arg(path); + else + logger.qbsDebug() << "Removing '" << path << "'."; +} + +static void invalidateArtifactTimestamp(Artifact *artifact) +{ + if (artifact->timestamp().isValid()) { + artifact->clearTimestamp(); + artifact->product->topLevelProject()->buildData->isDirty = true; + } +} + +static void removeArtifactFromDisk(Artifact *artifact, bool dryRun, const Logger &logger) +{ + QFileInfo fileInfo(artifact->filePath()); + if (!fileInfo.exists()) { + if (!dryRun) + invalidateArtifactTimestamp(artifact); + return; + } + printRemovalMessage(fileInfo.filePath(), dryRun, logger); + if (dryRun) + return; + invalidateArtifactTimestamp(artifact); + QString errorMessage; + if (!removeFileRecursion(fileInfo, &errorMessage)) + throw ErrorInfo(errorMessage); +} + +class CleanupVisitor : public ArtifactVisitor +{ +public: + CleanupVisitor(const CleanOptions &options, const Logger &logger) + : ArtifactVisitor(Artifact::Generated) + , m_options(options) + , m_logger(logger) + , m_hasError(false) + { + } + + void visitProduct(const ResolvedProductConstPtr &product) + { + m_product = product; + ArtifactVisitor::visitProduct(product); + } + + const QSet<QString> &directories() const { return m_directories; } + bool hasError() const { return m_hasError; } + +private: + void doVisit(Artifact *artifact) + { + if (artifact->product != m_product) + return; + if (m_options.cleanType() == CleanOptions::CleanupTemporaries) { + QBS_CHECK(artifact->transformer); + foreach (Artifact * const sibling, artifact->transformer->outputs) { + if (artifact->product->buildData->targetArtifacts.contains(sibling)) + return; + } + } + try { + removeArtifactFromDisk(artifact, m_options.dryRun(), m_logger); + } catch (const ErrorInfo &error) { + if (!m_options.keepGoing()) + throw; + m_logger.printWarning(error); + m_hasError = true; + } + m_directories << artifact->dirPath(); + } + + const CleanOptions m_options; + Logger m_logger; + bool m_hasError; + ResolvedProductConstPtr m_product; + QSet<QString> m_directories; +}; + +ArtifactCleaner::ArtifactCleaner(const Logger &logger, ProgressObserver *observer) + : m_logger(logger), m_observer(observer) +{ +} + +void ArtifactCleaner::cleanup(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const CleanOptions &options) +{ + m_hasError = false; + + const QString configString = Tr::tr(" for configuration %1").arg(project->id()); + m_observer->initialize(Tr::tr("Cleaning up%1").arg(configString), products.count() + 1); + + QSet<QString> directories; + foreach (const ResolvedProductPtr &product, products) { + CleanupVisitor visitor(options, m_logger); + visitor.visitProduct(product); + directories.unite(visitor.directories()); + if (visitor.hasError()) + m_hasError = true; + m_observer->incrementProgressValue(); + } + + // Directories created during the build are not artifacts (TODO: should they be?), + // so we have to clean them up manually. + QList<QString> dirList = directories.toList(); + for (int i = 0; i < dirList.count(); ++i) { + const QString &dir = dirList.at(i); + if (dir.startsWith(project->buildDirectory) && FileInfo(dir).exists()) + removeEmptyDirectories(dir, options); + if (dir != project->buildDirectory) { + const QString parentDir = QDir::cleanPath(dir + "/.."); + if (parentDir != project->buildDirectory && !dirList.contains(parentDir)) + dirList << parentDir; + } + } + m_observer->incrementProgressValue(); + + if (m_hasError) + throw ErrorInfo(Tr::tr("Failed to remove some files.")); + m_observer->setFinished(); +} + +void ArtifactCleaner::removeEmptyDirectories(const QString &rootDir, const CleanOptions &options, + bool *isEmpty) +{ + bool subTreeIsEmpty = true; + QDirIterator it(rootDir, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + while (it.hasNext()) { + it.next(); + if (!it.fileInfo().isSymLink() && it.fileInfo().isDir()) + removeEmptyDirectories(it.filePath(), options, &subTreeIsEmpty); + else + subTreeIsEmpty = false; + } + if (subTreeIsEmpty) { + printRemovalMessage(rootDir, options.dryRun(), m_logger); + if (!QDir::root().rmdir(rootDir)) { + ErrorInfo error(Tr::tr("Failure to remove empty directory '%1'.").arg(rootDir)); + if (!options.keepGoing()) + throw error; + m_logger.printWarning(error); + m_hasError = true; + subTreeIsEmpty = false; + } + } + if (!subTreeIsEmpty && isEmpty) + *isEmpty = false; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactcleaner.h b/src/lib/corelib/buildgraph/artifactcleaner.h new file mode 100644 index 000000000..760af3412 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactcleaner.h @@ -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. +** +****************************************************************************/ +#ifndef QBS_ARTIFACTCLEANER_H +#define QBS_ARTIFACTCLEANER_H + +#include <QList> + +#include <language/forward_decls.h> +#include <logging/logger.h> + +namespace qbs { +class CleanOptions; + +namespace Internal { +class ProgressObserver; + +class ArtifactCleaner +{ +public: + ArtifactCleaner(const Logger &logger, ProgressObserver *observer); + void cleanup(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const CleanOptions &options); + +private: + void removeEmptyDirectories(const QString &rootDir, const CleanOptions &options, + bool *isEmpty = 0); + + Logger m_logger; + bool m_hasError; + ProgressObserver *m_observer; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTCLEANER_H diff --git a/src/lib/corelib/buildgraph/artifactlist.cpp b/src/lib/corelib/buildgraph/artifactlist.cpp new file mode 100644 index 000000000..b435e3e60 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactlist.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 "artifactlist.h" + +namespace qbs { +namespace Internal { + +ArtifactList::ArtifactList() +{} + +ArtifactList::ArtifactList(const ArtifactList &other) + : m_data(other.m_data) +{} + +ArtifactList &ArtifactList::unite(const ArtifactList &other) +{ + std::set<Artifact *>::const_iterator it = other.m_data.begin(); + for (; it != other.m_data.end(); ++it) + m_data.insert(*it); + return *this; +} + +void ArtifactList::remove(Artifact *artifact) +{ + iterator it = m_data.find(artifact); + if (it != m_data.end()) + m_data.erase(it); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactlist.h b/src/lib/corelib/buildgraph/artifactlist.h new file mode 100644 index 000000000..bbe2771cb --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactlist.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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_ARTIFACTLIST_H +#define QBS_ARTIFACTLIST_H + +#include <set> +#include <cstddef> + +namespace qbs { +namespace Internal { + +class Artifact; + +/** + * List that holds a bunch of build graph artifacts. + * This is faster than QSet when iterating over the container. + */ +class ArtifactList +{ +public: + ArtifactList(); + ArtifactList(const ArtifactList &other); + + ArtifactList &unite(const ArtifactList &other); + + typedef std::set<Artifact *>::const_iterator const_iterator; + typedef std::set<Artifact *>::iterator iterator; + typedef Artifact * value_type; + + iterator begin() { return m_data.begin(); } + iterator end() { return m_data.end(); } + const_iterator begin() const { return m_data.begin(); } + const_iterator end() const { return m_data.end(); } + const_iterator constBegin() const { return m_data.begin(); } + const_iterator constEnd() const { return m_data.end(); } + + void insert(Artifact *artifact) + { + m_data.insert(artifact); + } + + void operator +=(Artifact *artifact) + { + insert(artifact); + } + + void remove(Artifact *artifact); + + bool contains(Artifact *artifact) const + { + return m_data.find(artifact) != m_data.end(); + } + + void clear() + { + m_data.clear(); + } + + bool isEmpty() const + { + return m_data.empty(); + } + + int count() const + { + return (int)m_data.size(); + } + + void reserve(int) + { + // no-op + } + + bool operator==(const ArtifactList &other) const { return m_data == other.m_data; } + bool operator!=(const ArtifactList &other) const { return !(*this == other); } + + +private: + std::set<Artifact *> m_data; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTLIST_H diff --git a/src/lib/corelib/buildgraph/artifactvisitor.cpp b/src/lib/corelib/buildgraph/artifactvisitor.cpp new file mode 100644 index 000000000..3b5203e26 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactvisitor.cpp @@ -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. +** +****************************************************************************/ +#include "artifactvisitor.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include <language/language.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +ArtifactVisitor::ArtifactVisitor(int artifactType) : m_artifactType(artifactType) +{ +} + +void ArtifactVisitor::visitArtifact(Artifact *artifact) +{ + QBS_CHECK(artifact); + if (m_artifactType & artifact->artifactType) + doVisit(artifact); +} + +void ArtifactVisitor::visitProduct(const ResolvedProductConstPtr &product) +{ + if (!product->buildData) + return; + foreach (Artifact * const artifact, product->buildData->artifacts) + visitArtifact(artifact); +} + +void ArtifactVisitor::visitProject(const ResolvedProjectConstPtr &project) +{ + foreach (const ResolvedProductConstPtr &product, project->products) + visitProduct(product); + foreach (const ResolvedProjectConstPtr &subProject, project->subProjects) + visitProject(subProject); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactvisitor.h b/src/lib/corelib/buildgraph/artifactvisitor.h new file mode 100644 index 000000000..0c113ea1b --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactvisitor.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_ARTIFACTVISITOR_H +#define QBS_ARTIFACTVISITOR_H + +#include "forward_decls.h" + +#include <language/forward_decls.h> + +#include <QList> +#include <QSet> + +namespace qbs { +namespace Internal { + +class ArtifactVisitor +{ +public: + ArtifactVisitor(int artifactType); + + virtual void visitArtifact(Artifact *artifact); + virtual void visitProduct(const ResolvedProductConstPtr &product); + virtual void visitProject(const ResolvedProjectConstPtr &project); + +private: + virtual void doVisit(Artifact *artifact) = 0; + + const int m_artifactType; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTVISITOR_H diff --git a/src/lib/corelib/buildgraph/automoc.cpp b/src/lib/corelib/buildgraph/automoc.cpp new file mode 100644 index 000000000..5dcb6ec9f --- /dev/null +++ b/src/lib/corelib/buildgraph/automoc.cpp @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** 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 "automoc.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "buildgraph.h" +#include "rulesapplicator.h" +#include "scanresultcache.h" +#include <buildgraph/artifact.h> +#include <buildgraph/transformer.h> +#include <language/language.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/scannerpluginmanager.h> + +namespace qbs { +namespace Internal { + +AutoMoc::AutoMoc(const Logger &logger, QObject *parent) + : QObject(parent) + , m_scanResultCache(0) + , m_logger(logger) +{ +} + +void AutoMoc::setScanResultCache(ScanResultCache *scanResultCache) +{ + m_scanResultCache = scanResultCache; +} + +void AutoMoc::apply(const ResolvedProductPtr &product) +{ + if (cppScanners().isEmpty() || hppScanners().isEmpty()) + throw ErrorInfo("C++ scanner cannot be loaded."); + + Artifact *pluginMetaDataFile = 0; + Artifact *pchFile = 0; + QList<QPair<Artifact *, FileType> > artifactsToMoc; + QSet<QString> includedMocCppFiles; + const FileTime currentTime = FileTime::currentTime(); + ArtifactList::const_iterator it = product->buildData->artifacts.begin(); + for (; it != product->buildData->artifacts.end(); ++it) { + Artifact *artifact = *it; + if (!pchFile || !pluginMetaDataFile) { + foreach (const FileTag &fileTag, artifact->fileTags) { + if (fileTag == "cpp_pch") + pchFile = artifact; + else if (fileTag == "qt_plugin_metadata") + pluginMetaDataFile = artifact; + } + } + + if (!pluginMetaDataFile && artifact->fileTags.contains("qt_plugin_metadata")) { + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[AUTOMOC] found Qt plugin metadata file " + << artifact->filePath(); + } + pluginMetaDataFile = artifact; + } + if (artifact->artifactType != Artifact::SourceFile) + continue; + if (artifact->timestamp() < artifact->autoMocTimestamp) + continue; + artifact->autoMocTimestamp = currentTime; + const FileType fileType = AutoMoc::fileType(artifact); + if (fileType == UnknownFileType) + continue; + FileTag mocFileTag; + bool alreadyMocced = isVictimOfMoc(artifact, fileType, mocFileTag); + bool hasQObjectMacro; + scan(artifact, fileType, hasQObjectMacro, includedMocCppFiles); + if (hasQObjectMacro && !alreadyMocced) + artifactsToMoc += qMakePair(artifact, fileType); + else if (!hasQObjectMacro && alreadyMocced) + unmoc(artifact, mocFileTag); + } + + Artifact *pluginHeaderFile = 0; + ArtifactsPerFileTagMap artifactsPerFileTag; + for (int i = artifactsToMoc.count(); --i >= 0;) { + const QPair<Artifact *, FileType> &p = artifactsToMoc.at(i); + Artifact * const artifact = p.first; + FileType fileType = p.second; + foreach (const FileTag &fileTag, artifact->fileTags) { + if (fileTag == "moc_hpp") { + const QString mocFileName = generateMocFileName(artifact, fileType); + if (includedMocCppFiles.contains(mocFileName)) { + FileTag newFileTag = "moc_hpp_inc"; + artifact->fileTags -= fileTag; + artifact->fileTags += newFileTag; + artifactsPerFileTag[newFileTag].insert(artifact); + continue; + } + } else if (fileTag == "moc_plugin_hpp") { + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[AUTOMOC] found Qt plugin header file " + << artifact->filePath(); + } + FileTag newFileTag = "moc_hpp"; + artifact->fileTags -= fileTag; + artifact->fileTags += newFileTag; + artifactsPerFileTag[newFileTag].insert(artifact); + pluginHeaderFile = artifact; + } + artifactsPerFileTag[fileTag].insert(artifact); + } + } + + if (pchFile) + artifactsPerFileTag["cpp_pch"] += pchFile; + if (!artifactsPerFileTag.isEmpty()) { + emit reportCommandDescription(QLatin1String("automoc"), + Tr::tr("Applying moc rules for '%1'.") + .arg(product->name)); + RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules(); + } + if (pluginHeaderFile && pluginMetaDataFile) { + // Make every artifact that is dependent of the header file also + // dependent of the plugin metadata file. + foreach (Artifact *outputOfHeader, pluginHeaderFile->parents) + loggedConnect(outputOfHeader, pluginMetaDataFile, m_logger); + } + + product->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger); +} + +QString AutoMoc::generateMocFileName(Artifact *artifact, FileType fileType) +{ + QString mocFileName; + switch (fileType) { + case UnknownFileType: + break; + case HppFileType: + mocFileName = "moc_" + FileInfo::baseName(artifact->filePath()) + ".cpp"; + break; + case CppFileType: + mocFileName = FileInfo::baseName(artifact->filePath()) + ".moc"; + break; + } + return mocFileName; +} + +AutoMoc::FileType AutoMoc::fileType(Artifact *artifact) +{ + foreach (const FileTag &fileTag, artifact->fileTags) + if (fileTag == "hpp") + return HppFileType; + else if (fileTag == "cpp") + return CppFileType; + return UnknownFileType; +} + +void AutoMoc::scan(Artifact *artifact, FileType fileType, bool &hasQObjectMacro, + QSet<QString> &includedMocCppFiles) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[AUTOMOC] checks " << relativeArtifactFileName(artifact); + + hasQObjectMacro = false; + + foreach (ScannerPlugin *scanner, fileType == HppFileType ? hppScanners() : cppScanners()) { + ScanResultCache::Result scanResult = m_scanResultCache->value(artifact->filePath()); + if (!scanResult.valid) { + scanResult.valid = true; + void *opaq = scanner->open(artifact->filePath().utf16(), + ScanForDependenciesFlag | ScanForFileTagsFlag); + if (!opaq || !scanner->additionalFileTags) + continue; + + int length = 0; + const char **szFileTagsFromScanner = scanner->additionalFileTags(opaq, &length); + if (szFileTagsFromScanner && length > 0) { + for (int i = length; --i >= 0;) + scanResult.additionalFileTags += szFileTagsFromScanner[i]; + } + + forever { + int flags = 0; + const char *szOutFilePath = scanner->next(opaq, &length, &flags); + if (szOutFilePath == 0) + break; + QString includedFilePath = QString::fromLocal8Bit(szOutFilePath, length); + if (includedFilePath.isEmpty()) + continue; + bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG); + scanResult.deps += ScanResultCache::Dependency(includedFilePath, isLocalInclude); + } + + scanner->close(opaq); + m_scanResultCache->insert(artifact->filePath(), scanResult); + } + + foreach (const FileTag &tag, scanResult.additionalFileTags) { + artifact->fileTags.insert(tag); + if (tag.name().startsWith("moc")) { + hasQObjectMacro = true; + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[AUTOMOC] finds Q_OBJECT macro"; + } + } + + foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) { + const QString &includedFilePath = dependency.filePath(); + if (includedFilePath.startsWith("moc_") && includedFilePath.endsWith(".cpp")) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[AUTOMOC] finds included file: " << includedFilePath; + includedMocCppFiles += includedFilePath; + } + } + } +} + +static FileTags provideMocHeaderFileTags() +{ + FileTags fileTags; + fileTags << "moc_hpp" << "moc_hpp_inc" << "moc_plugin_hpp"; + return fileTags; +} + +bool AutoMoc::isVictimOfMoc(Artifact *artifact, FileType fileType, FileTag &foundMocFileTag) +{ + static const FileTags mocHeaderFileTags = provideMocHeaderFileTags(); + static const FileTag mocCppFileTag = "moc_cpp"; + foundMocFileTag.clear(); + switch (fileType) { + case UnknownFileType: + break; + case HppFileType: + foreach (const FileTag &fileTag, artifact->fileTags) { + if (mocHeaderFileTags.contains(fileTag)) { + foundMocFileTag = fileTag; + break; + } + } + break; + case CppFileType: + if (artifact->fileTags.contains(mocCppFileTag)) + foundMocFileTag = mocCppFileTag; + break; + } + return foundMocFileTag.isValid(); +} + +void AutoMoc::unmoc(Artifact *artifact, const FileTag &mocFileTag) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[AUTOMOC] unmoc'ing " << relativeArtifactFileName(artifact); + + artifact->fileTags.remove(mocFileTag); + + Artifact *generatedMocArtifact = 0; + foreach (Artifact *parent, artifact->parents) { + foreach (const FileTag &fileTag, parent->fileTags) { + if (fileTag == "hpp" || fileTag == "cpp") { + generatedMocArtifact = parent; + break; + } + } + } + + if (!generatedMocArtifact) { + m_logger.qbsTrace() << "[AUTOMOC] generated moc artifact could not be found"; + return; + } + + TopLevelProject * const project = artifact->product->topLevelProject(); + if (mocFileTag == "moc_hpp") { + Artifact *mocObjArtifact = 0; + foreach (Artifact *parent, generatedMocArtifact->parents) { + foreach (const FileTag &fileTag, parent->fileTags) { + if (fileTag == "obj" || fileTag == "fpicobj") { + mocObjArtifact = parent; + break; + } + } + } + + if (!mocObjArtifact) { + m_logger.qbsTrace() << "[AUTOMOC] generated moc obj artifact could not be found"; + } else { + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << "[AUTOMOC] removing moc obj artifact " + << relativeArtifactFileName(mocObjArtifact); + } + project->buildData->removeArtifact(mocObjArtifact, m_logger); + delete mocObjArtifact; + } + } + + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << "[AUTOMOC] removing generated artifact " + << relativeArtifactFileName(generatedMocArtifact); + } + project->buildData->removeArtifact(generatedMocArtifact, m_logger); + delete generatedMocArtifact; +} + +const QList<ScannerPlugin *> &AutoMoc::cppScanners() const +{ + if (m_cppScanners.isEmpty()) + m_cppScanners = ScannerPluginManager::scannersForFileTag("cpp"); + + return m_cppScanners; +} + +const QList<ScannerPlugin *> &AutoMoc::hppScanners() const +{ + if (m_hppScanners.isEmpty()) + m_hppScanners = ScannerPluginManager::scannersForFileTag("hpp"); + + return m_hppScanners; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/automoc.h b/src/lib/corelib/buildgraph/automoc.h new file mode 100644 index 000000000..d57fc292e --- /dev/null +++ b/src/lib/corelib/buildgraph/automoc.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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_AUTOMOC_H +#define QBS_AUTOMOC_H + +#include "forward_decls.h" + +#include <language/forward_decls.h> +#include <logging/logger.h> + +#include <QObject> + +struct ScannerPlugin; + +namespace qbs { +namespace Internal { +class FileTag; +class ScanResultCache; + +/** + * Scans cpp and hpp files for the Q_OBJECT / Q_GADGET macro and + * applies the corresponding rule then. + * Also scans the files for moc_XXX.cpp files to find out if we must + * compile and link a moc_XXX.cpp file or not. + * + * This whole thing is an ugly hack, I know. + */ +class AutoMoc : public QObject +{ + Q_OBJECT + +public: + AutoMoc(const Logger &logger, QObject *parent = 0); + + void setScanResultCache(ScanResultCache *scanResultCache); + void apply(const ResolvedProductPtr &product); + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + +private: + enum FileType + { + UnknownFileType, + HppFileType, + CppFileType + }; + +private: + static QString generateMocFileName(Artifact *artifact, FileType fileType); + static FileType fileType(Artifact *artifact); + void scan(Artifact *artifact, FileType fileType, bool &hasQObjectMacro, + QSet<QString> &includedMocCppFiles); + bool isVictimOfMoc(Artifact *artifact, FileType fileType, FileTag &foundMocFileTag); + void unmoc(Artifact *artifact, const FileTag &mocFileTag); + const QList<ScannerPlugin *> &cppScanners() const; + const QList<ScannerPlugin *> &hppScanners() const; + + mutable QList<ScannerPlugin *> m_cppScanners; + mutable QList<ScannerPlugin *> m_hppScanners; + ScanResultCache *m_scanResultCache; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_AUTOMOC_H diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp new file mode 100644 index 000000000..5284de5a9 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.cpp @@ -0,0 +1,498 @@ +/**************************************************************************** +** +** 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 "buildgraph.h" + +#include "artifact.h" +#include "cycledetector.h" +#include "projectbuilddata.h" +#include "productbuilddata.h" +#include "transformer.h" + +#include <jsextensions/jsextensions.h> +#include <jsextensions/moduleproperties.h> +#include <language/language.h> +#include <language/preparescriptobserver.h> +#include <language/scriptengine.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +#include <QFile> + +namespace qbs { +namespace Internal { + +static void setupProductScriptValue(ScriptEngine *engine, QScriptValue &productScriptValue, + const ResolvedProductConstPtr &product, + PrepareScriptObserver *observer); + +class DependenciesFunction +{ +public: + DependenciesFunction(ScriptEngine *engine) + : m_engine(engine) + { + } + + void init(QScriptValue &productScriptValue, const ResolvedProductConstPtr &product) + { + QScriptValue depfunc = m_engine->newFunction(&js_productDependencies); + setProduct(depfunc, product.data()); + QScriptValue descriptor = m_engine->newObject(); + descriptor.setProperty(QLatin1String("get"), depfunc); + descriptor.setProperty(QLatin1String("set"), m_engine->evaluate("(function(){})")); + descriptor.setProperty(QLatin1String("enumerable"), true); + m_engine->defineProperty(productScriptValue, QLatin1String("dependencies"), descriptor); + } + +private: + static QScriptValue js_productDependencies(QScriptContext *context, QScriptEngine *engine) + { + QScriptValue callee = context->callee(); + const ResolvedProduct * const product = getProduct(callee); + QScriptValue result = engine->newArray(); + quint32 idx = 0; + foreach (const ResolvedProductPtr &dependency, product->dependencies) { + QScriptValue obj = engine->newObject(); + setupProductScriptValue(static_cast<ScriptEngine *>(engine), obj, dependency, 0); + result.setProperty(idx++, obj); + } + foreach (const ResolvedModuleConstPtr &dependency, product->modules) { + QScriptValue obj = engine->newObject(); + setupModuleScriptValue(static_cast<ScriptEngine *>(engine), obj, + product->properties->value(), dependency->name); + result.setProperty(idx++, obj); + } + return result; + } + + static QScriptValue js_moduleDependencies(QScriptContext *context, QScriptEngine *engine) + { + QScriptValue callee = context->callee(); + const QVariantMap modulesMap = callee.data().toVariant().toMap(); + QScriptValue result = engine->newArray(); + quint32 idx = 0; + for (QVariantMap::const_iterator it = modulesMap.begin(); it != modulesMap.end(); ++it) { + QScriptValue obj = engine->newObject(); + obj.setProperty(QLatin1String("name"), it.key()); + setupModuleScriptValue(static_cast<ScriptEngine *>(engine), obj, it.value().toMap(), + it.key()); + result.setProperty(idx++, obj); + } + return result; + } + + static void setupModuleScriptValue(ScriptEngine *engine, QScriptValue &moduleScriptValue, + const QVariantMap &propertyMap, + const QString &moduleName) + { + const QVariantMap propMap + = propertyMap.value(QLatin1String("modules")).toMap().value(moduleName).toMap(); + for (QVariantMap::ConstIterator it = propMap.constBegin(); it != propMap.constEnd(); ++it) { + const QVariant &value = it.value(); + if (value.isValid() && it.key() != QLatin1String("modules")) + moduleScriptValue.setProperty(it.key(), engine->toScriptValue(value)); + } + QScriptValue depfunc = engine->newFunction(&js_moduleDependencies); + depfunc.setData(engine->toScriptValue(propMap.value(QLatin1String("modules")))); + QScriptValue descriptor = engine->newObject(); + descriptor.setProperty(QLatin1String("get"), depfunc); + descriptor.setProperty(QLatin1String("set"), engine->evaluate("(function(){})")); + descriptor.setProperty(QLatin1String("enumerable"), true); + engine->defineProperty(moduleScriptValue, QLatin1String("dependencies"), descriptor); + moduleScriptValue.setProperty(QLatin1String("type"), QLatin1String("module")); + } + + static void setProduct(QScriptValue scriptValue, const ResolvedProduct *product) + { + QVariant v; + v.setValue<quintptr>(reinterpret_cast<quintptr>(product)); + scriptValue.setData(scriptValue.engine()->newVariant(v)); + } + + static const ResolvedProduct *getProduct(const QScriptValue &scriptValue) + { + const quintptr ptr = scriptValue.data().toVariant().value<quintptr>(); + return reinterpret_cast<const ResolvedProduct *>(ptr); + } + + ScriptEngine *m_engine; +}; + +static void setupProductScriptValue(ScriptEngine *engine, QScriptValue &productScriptValue, + const ResolvedProductConstPtr &product, + PrepareScriptObserver *observer) +{ + ModuleProperties::init(productScriptValue, product); + DependenciesFunction(engine).init(productScriptValue, product); + if (observer) + observer->setProductObjectId(productScriptValue.objectId()); + const QVariantMap &propMap = product->properties->value(); + for (QVariantMap::ConstIterator it = propMap.constBegin(); it != propMap.constEnd(); ++it) { + const QVariant &value = it.value(); + if (it.key() != QLatin1String("modules")) { + engine->setObservedProperty(productScriptValue, it.key(), + engine->toScriptValue(value), observer); + } + } +} + +void setupScriptEngineForFile(ScriptEngine *engine, const ResolvedFileContextConstPtr &fileContext, + QScriptValue targetObject) +{ + engine->import(fileContext->jsImports, targetObject, targetObject); + JsExtensions::setupExtensions(fileContext->jsExtensions, targetObject); +} + +void setupScriptEngineForProduct(ScriptEngine *engine, const ResolvedProductConstPtr &product, + const RuleConstPtr &rule, QScriptValue targetObject, + PrepareScriptObserver *observer) +{ + ScriptEngine::ScriptValueCache * const cache = engine->scriptValueCache(); + if (cache->observer != observer) { + cache->project = 0; + cache->product = 0; + } + + if (cache->project != product->project) { + cache->project = product->project.data(); + cache->projectScriptValue = engine->newObject(); + cache->projectScriptValue.setProperty(QLatin1String("filePath"), + product->project->location.fileName()); + cache->projectScriptValue.setProperty(QLatin1String("path"), + FileInfo::path(product->project->location.fileName())); + const QVariantMap &projectProperties = product->project->projectProperties(); + for (QVariantMap::const_iterator it = projectProperties.begin(); + it != projectProperties.end(); ++it) { + engine->setObservedProperty(cache->projectScriptValue, it.key(), + engine->toScriptValue(it.value()), observer); + } + } + targetObject.setProperty(QLatin1String("project"), cache->projectScriptValue); + if (observer) + observer->setProjectObjectId(cache->projectScriptValue.objectId()); + + if (cache->product != product) { + cache->product = product.data(); + { + QVariant v; + v.setValue<void*>(&product->buildEnvironment); + engine->setProperty("_qbs_procenv", v); + } + cache->productScriptValue = engine->newObject(); + setupProductScriptValue(engine, cache->productScriptValue, product, observer); + } + targetObject.setProperty(QLatin1String("product"), cache->productScriptValue); + + // If the Rule is in a Module, set up the 'moduleName' property + cache->productScriptValue.setProperty(QLatin1String("moduleName"), + rule->module->name.isEmpty() ? QScriptValue() : rule->module->name); +} + +bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path) +{ + if (u == v) { + path.append(v); + return true; + } + + for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) { + if (findPath(*it, v, path)) { + path.prepend(u); + return true; + } + } + + return false; +} + +/* + * c must be built before p + * p ----> c + * p.children = c + * c.parents = p + * + * also: children means i depend on or i am produced by + * parent means "produced by me" or "depends on me" + */ +void connect(Artifact *p, Artifact *c) +{ + QBS_CHECK(p != c); + p->children.insert(c); + c->parents.insert(p); + p->product->topLevelProject()->buildData->isDirty = true; +} + +void loggedConnect(Artifact *u, Artifact *v, const Logger &logger) +{ + QBS_CHECK(u != v); + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] connect '%1' -> '%2'") + .arg(relativeArtifactFileName(u), relativeArtifactFileName(v)); + } + connect(u, v); +} + +static bool existsPath(Artifact *u, Artifact *v) +{ + if (u == v) + return true; + + for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) + if (existsPath(*it, v)) + return true; + + return false; +} + +bool safeConnect(Artifact *u, Artifact *v, const Logger &logger) +{ + QBS_CHECK(u != v); + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] safeConnect: '%1' '%2'") + .arg(relativeArtifactFileName(u), relativeArtifactFileName(v)); + } + + if (existsPath(v, u)) { + QList<Artifact *> circle; + findPath(v, u, circle); + logger.qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle); + return false; + } + + connect(u, v); + return true; +} + +void disconnect(Artifact *u, Artifact *v, const Logger &logger) +{ + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnect: '%1' '%2'") + .arg(relativeArtifactFileName(u), relativeArtifactFileName(v)); + } + u->children.remove(v); + u->childrenAddedByScanner.remove(v); + v->parents.remove(u); +} + +void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger) +{ + if (artifact->artifactType != Artifact::Generated) + return; + + QFile file(artifact->filePath()); + if (!file.exists()) + return; + + logger.qbsDebug() << "removing " << artifact->fileName(); + if (!file.remove()) { + logger.qbsWarning() << QString::fromLocal8Bit("Cannot remove '%1'.") + .arg(artifact->filePath()); + } +} + +QString relativeArtifactFileName(const Artifact *artifact) +{ + const QString &buildDir = artifact->product->topLevelProject()->buildDirectory; + QString str = artifact->filePath(); + if (str.startsWith(buildDir)) + str.remove(0, buildDir.count()); + if (str.startsWith('/')) + str.remove(0, 1); + return str; +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, + const ProjectBuildData *projectBuildData, const QString &dirPath, const QString &fileName, + bool compareByName) +{ + const QList<FileResourceBase *> lookupResults + = projectBuildData->lookupFiles(dirPath, fileName); + for (QList<FileResourceBase *>::const_iterator it = lookupResults.constBegin(); + it != lookupResults.constEnd(); ++it) { + Artifact *artifact = dynamic_cast<Artifact *>(*it); + if (artifact && (compareByName + ? artifact->product->name == product->name + : artifact->product == product)) + return artifact; + } + return 0; +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath, + const QString &fileName, bool compareByName) +{ + return lookupArtifact(product, product->topLevelProject()->buildData.data(), dirPath, fileName, + compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath, + bool compareByName) +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupArtifact(product, dirPath, fileName, compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData, + const QString &filePath, bool compareByName) +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupArtifact(product, buildData, dirPath, fileName, compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact, + bool compareByName) +{ + return lookupArtifact(product, artifact->dirPath(), artifact->fileName(), compareByName); +} + +Artifact *createArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact, const Logger &logger) +{ + Artifact *artifact = new Artifact; + artifact->artifactType = Artifact::SourceFile; + artifact->setFilePath(sourceArtifact->absoluteFilePath); + artifact->fileTags = sourceArtifact->fileTags; + artifact->properties = sourceArtifact->properties; + insertArtifact(product, artifact, logger); + return artifact; +} + +void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const Logger &logger) +{ + QBS_CHECK(!artifact->product); + QBS_CHECK(!artifact->filePath().isEmpty()); + QBS_CHECK(!product->buildData->artifacts.contains(artifact)); +#ifdef QT_DEBUG + foreach (const ResolvedProductConstPtr &otherProduct, product->project->products) { + if (lookupArtifact(otherProduct, artifact->filePath())) { + if (artifact->artifactType == Artifact::Generated) { + QString pl; + pl.append(QString(" - %1 \n").arg(product->name)); + foreach (const ResolvedProductConstPtr &p, product->project->products) { + if (lookupArtifact(p, artifact->filePath())) + pl.append(QString(" - %1 \n").arg(p->name)); + } + throw ErrorInfo(QString ("BUG: already inserted in this project: %1\n%2") + .arg(artifact->filePath()).arg(pl), CodeLocation(), true); + } + } + } +#endif + product->buildData->artifacts.insert(artifact); + artifact->product = product; + product->topLevelProject()->buildData->insertIntoLookupTable(artifact); + product->topLevelProject()->buildData->isDirty = true; + + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] insert artifact '%1'") + .arg(artifact->filePath()); + } +} + +static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, const Logger &logger) +{ + logger.qbsDebug() << "Sanity checking product '" << product->name << "'"; + CycleDetector cycleDetector(logger); + cycleDetector.visitProduct(product); + const ProductBuildData * const buildData = product->buildData.data(); + if (logger.traceEnabled()) + logger.qbsTrace() << "enabled: " << product->enabled << "; build data: " << buildData; + QBS_CHECK(!!product->enabled == !!buildData); + if (!product->enabled) + return; + foreach (Artifact * const ta, buildData->targetArtifacts) { + if (logger.traceEnabled()) + logger.qbsTrace() << "Checking target artifact '" << ta->fileName() << "'."; + QBS_CHECK(buildData->artifacts.contains(ta)); + } + QSet<QString> filePaths; + foreach (Artifact * const artifact, buildData->artifacts) { + logger.qbsDebug() << "Sanity checking artifact '" << artifact->fileName() << "'"; + QBS_CHECK(!filePaths.contains(artifact->filePath())); + filePaths << artifact->filePath(); + QBS_CHECK(artifact->product == product); + foreach (const Artifact * const parent, artifact->parents) + QBS_CHECK(parent->children.contains(artifact)); + foreach (const Artifact * const child, artifact->children) + QBS_CHECK(child->parents.contains(artifact)); + foreach (Artifact * const child, artifact->childrenAddedByScanner) + QBS_CHECK(artifact->children.contains(child)); + const TransformerConstPtr transformer = artifact->transformer; + if (artifact->artifactType == Artifact::SourceFile) + continue; + + QBS_CHECK(transformer); + QBS_CHECK(transformer->outputs.contains(artifact)); + ArtifactList transformerOutputChildren; + foreach (const Artifact * const output, transformer->outputs) { + QBS_CHECK(output->transformer == transformer); + transformerOutputChildren.unite(output->children); + } + if (logger.traceEnabled()) { + logger.qbsTrace() << "The transformer output children are:"; + foreach (const Artifact * const a, transformerOutputChildren) + logger.qbsTrace() << "\t" << a->fileName(); + logger.qbsTrace() << "The transformer inputs are:"; + foreach (const Artifact * const a, transformer->inputs) + logger.qbsTrace() << "\t" << a->fileName(); + } + QBS_CHECK(transformer->inputs.count() <= transformerOutputChildren.count()); + foreach (Artifact * const transformerInput, transformer->inputs) + QBS_CHECK(transformerOutputChildren.contains(transformerInput)); + } +} + +static void doSanityChecks(const ResolvedProjectPtr &project, QSet<QString> &productNames, + const Logger &logger) +{ + logger.qbsDebug() << "Sanity checking project '" << project->name << "'"; + foreach (const ResolvedProjectPtr &subProject, project->subProjects) + doSanityChecks(subProject, productNames, logger); + + foreach (const ResolvedProductConstPtr &product, project->products) { + QBS_CHECK(product->project == project); + QBS_CHECK(product->topLevelProject() == project->topLevelProject()); + doSanityChecksForProduct(product, logger); + QBS_CHECK(!productNames.contains(product->name)); + productNames << product->name; + } +} + +void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger) +{ + QSet<QString> productNames; + doSanityChecks(project, productNames, logger); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraph.h b/src/lib/corelib/buildgraph/buildgraph.h new file mode 100644 index 000000000..5244894cb --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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_BUILDGRAPH_H +#define QBS_BUILDGRAPH_H + +#include "forward_decls.h" +#include "rulesapplicator.h" + +#include <language/forward_decls.h> + +#include <QScriptValue> +#include <QStringList> + +namespace qbs { +namespace Internal { +class Logger; +class ScriptEngine; +class PrepareScriptObserver; + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, + const ProjectBuildData *projectBuildData, + const QString &dirPath, const QString &fileName, + bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath, + const QString &fileName, bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData, + const QString &filePath, bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath, + bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact, + bool compareByName); + +Artifact *createArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact, const Logger &logger); +void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const Logger &logger); +void addTargetArtifacts(const ResolvedProductPtr &product, + ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger); +void dumpProductBuildData(const ResolvedProductConstPtr &product); + + +bool findPath(Artifact *u, Artifact *v, QList<Artifact*> &path); +void connect(Artifact *p, Artifact *c); +void loggedConnect(Artifact *u, Artifact *v, const Logger &logger); +bool safeConnect(Artifact *u, Artifact *v, const Logger &logger); +void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger); +void disconnect(Artifact *u, Artifact *v, const Logger &logger); + +void setupScriptEngineForFile(ScriptEngine *engine, const ResolvedFileContextConstPtr &fileContext, + QScriptValue targetObject); +void setupScriptEngineForProduct(ScriptEngine *engine, const ResolvedProductConstPtr &product, + const RuleConstPtr &rule, QScriptValue targetObject, + PrepareScriptObserver *observer = 0); +QString relativeArtifactFileName(const Artifact *artifact); // Debugging helpers + +void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger); + +template <typename T> +QStringList toStringList(const T &artifactContainer) +{ + QStringList l; + foreach (Artifact *n, artifactContainer) + l.append(relativeArtifactFileName(n)); + return l; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPH_H diff --git a/src/lib/corelib/buildgraph/buildgraph.pri b/src/lib/corelib/buildgraph/buildgraph.pri new file mode 100644 index 000000000..fa091f1b3 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.pri @@ -0,0 +1,65 @@ +SOURCES += \ + $$PWD/abstractcommandexecutor.cpp \ + $$PWD/artifact.cpp \ + $$PWD/artifactcleaner.cpp \ + $$PWD/artifactlist.cpp \ + $$PWD/artifactvisitor.cpp \ + $$PWD/automoc.cpp \ + $$PWD/buildgraph.cpp \ + $$PWD/buildgraphloader.cpp \ + $$PWD/command.cpp \ + $$PWD/cycledetector.cpp \ + $$PWD/executor.cpp \ + $$PWD/executorjob.cpp \ + $$PWD/filedependency.cpp \ + $$PWD/inputartifactscanner.cpp \ + $$PWD/jscommandexecutor.cpp \ + $$PWD/processcommandexecutor.cpp \ + $$PWD/productbuilddata.cpp \ + $$PWD/productinstaller.cpp \ + $$PWD/projectbuilddata.cpp \ + $$PWD/rulegraph.cpp \ + $$PWD/rulesapplicator.cpp \ + $$PWD/rulesevaluationcontext.cpp \ + $$PWD/scanresultcache.cpp \ + $$PWD/timestampsupdater.cpp \ + $$PWD/transformer.cpp + +HEADERS += \ + $$PWD/abstractcommandexecutor.h \ + $$PWD/artifact.h \ + $$PWD/artifactcleaner.h \ + $$PWD/artifactlist.h \ + $$PWD/artifactvisitor.h \ + $$PWD/automoc.h \ + $$PWD/buildgraph.h \ + $$PWD/buildgraphloader.h \ + $$PWD/command.h \ + $$PWD/cycledetector.h \ + $$PWD/executor.h \ + $$PWD/executorjob.h \ + $$PWD/filedependency.h \ + $$PWD/forward_decls.h \ + $$PWD/inputartifactscanner.h \ + $$PWD/jscommandexecutor.h \ + $$PWD/processcommandexecutor.h \ + $$PWD/productbuilddata.h \ + $$PWD/productinstaller.h \ + $$PWD/projectbuilddata.h \ + $$PWD/rulegraph.h \ + $$PWD/rulesapplicator.h \ + $$PWD/rulesevaluationcontext.h \ + $$PWD/scanresultcache.h \ + $$PWD/timestampsupdater.h \ + $$PWD/transformer.h + +all_tests { + HEADERS += $$PWD/tst_buildgraph.h + SOURCES += $$PWD/tst_buildgraph.cpp +} + +!qbs_no_dev_install { + buildgraph_headers.files = $$PWD/forward_decls.h + buildgraph_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/buildgraph + INSTALLS += buildgraph_headers +} diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp new file mode 100644 index 000000000..b76b33eb6 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -0,0 +1,826 @@ +/**************************************************************************** +** +** 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 "buildgraphloader.h" + +#include "artifact.h" +#include "artifactlist.h" +#include "buildgraph.h" +#include "command.h" +#include "cycledetector.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" +#include <language/artifactproperties.h> +#include <language/language.h> +#include <language/loader.h> +#include <logging/translator.h> +#include <tools/propertyfinder.h> +#include <tools/qbsassert.h> +#include <tools/setupprojectparameters.h> + +#include <QDir> +#include <QFileInfo> + +namespace qbs { +namespace Internal { + +BuildGraphLoader::BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger) : + m_logger(logger), m_environment(env) +{ +} + +BuildGraphLoader::~BuildGraphLoader() +{ + qDeleteAll(m_objectsToDelete); +} + +static bool isConfigCompatible(const QVariantMap &cfg1, const QVariantMap &cfg2) +{ + if (cfg1.count() != cfg2.count()) + return false; + QVariantMap::const_iterator it = cfg1.begin(); + for (; it != cfg1.end(); ++it) { + if (it.value().type() == QVariant::Map) { + if (!isConfigCompatible(it.value().toMap(), cfg2.value(it.key()).toMap())) + return false; + } else { + QVariant value = cfg2.value(it.key()); + if (value != it.value()) + return false; + } + } + return true; +} + +static void restoreBackPointers(const ResolvedProjectPtr &project) +{ + foreach (const ResolvedProductPtr &product, project->products) { + product->project = project; + if (!product->buildData) + continue; + foreach (Artifact * const a, product->buildData->artifacts) + project->topLevelProject()->buildData->insertIntoLookupTable(a); + } + + foreach (const ResolvedProjectPtr &subProject, project->subProjects) { + subProject->parentProject = project; + restoreBackPointers(subProject); + } +} + +BuildGraphLoadResult BuildGraphLoader::load(const SetupProjectParameters ¶meters, + const RulesEvaluationContextPtr &evalContext) +{ + m_result = BuildGraphLoadResult(); + m_evalContext = evalContext; + + const QString projectId = TopLevelProject::deriveId(parameters.buildConfigurationTree()); + const QString buildDir + = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), projectId); + const QString buildGraphFilePath + = ProjectBuildData::deriveBuildGraphFilePath(buildDir, projectId); + + PersistentPool pool(m_logger); + m_logger.qbsDebug() << "[BG] trying to load: " << buildGraphFilePath; + try { + pool.load(buildGraphFilePath); + } catch (const ErrorInfo &loadError) { + if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) + throw; + m_logger.qbsInfo() << loadError.toString(); + return m_result; + } + + const TopLevelProjectPtr project = TopLevelProject::create(); + + // TODO: Store some meta data that will enable us to show actual progress (e.g. number of products). + evalContext->initializeObserver(Tr::tr("Restoring build graph from disk"), 1); + + project->load(pool); + project->buildData->evaluationContext = evalContext; + + if (QFileInfo(project->location.fileName()) != QFileInfo(parameters.projectFilePath())) { + QString errorMessage = Tr::tr("Stored build graph at '%1' is for project file '%2', but " + "input file is '%3'. ") + .arg(QDir::toNativeSeparators(buildGraphFilePath), + QDir::toNativeSeparators(project->location.fileName()), + QDir::toNativeSeparators(parameters.projectFilePath())); + if (!parameters.ignoreDifferentProjectFilePath()) { + errorMessage += Tr::tr("Aborting."); + throw ErrorInfo(errorMessage); + } + + // Okay, let's assume it's the same project anyway (the source dir might have moved). + errorMessage += Tr::tr("Ignoring."); + m_logger.qbsWarning() << errorMessage; + } + + restoreBackPointers(project); + + project->location = CodeLocation(parameters.projectFilePath(), project->location.line(), + project->location.column()); + project->setBuildConfiguration(pool.headData().projectConfig); + project->buildDirectory = buildDir; + m_result.loadedProject = project; + evalContext->incrementProgressValue(); + doSanityChecks(project, m_logger); + + if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) + return m_result; + QBS_CHECK(parameters.restoreBehavior() == SetupProjectParameters::RestoreAndTrackChanges); + + trackProjectChanges(parameters, buildGraphFilePath, project, pool.headData().projectConfig); + return m_result; +} + +void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶meters, + const QString &buildGraphFilePath, const TopLevelProjectPtr &restoredProject, + const QVariantMap &oldProjectConfig) +{ + const FileTime buildGraphTimeStamp = FileInfo(buildGraphFilePath).lastModified(); + QSet<QString> buildSystemFiles = restoredProject->buildSystemFiles; + QList<ResolvedProductPtr> allRestoredProducts = restoredProject->allProducts(); + QList<ResolvedProductPtr> changedProducts; + QList<ResolvedProductPtr> productsWithChangedFiles; + bool reResolvingNecessary = false; + if (!isConfigCompatible(parameters.finalBuildConfigurationTree(), oldProjectConfig)) + reResolvingNecessary = true; + if (hasProductFileChanged(allRestoredProducts, buildGraphTimeStamp, + buildSystemFiles, productsWithChangedFiles)) { + reResolvingNecessary = true; + } + + // "External" changes, e.g. in the environment or in a JavaScript file, + // can make the list of source files in a product change without the respective file + // having been touched. In such a case, the build data for that product will have to be set up + // anew. + if (hasBuildSystemFileChanged(buildSystemFiles, buildGraphTimeStamp) + || hasEnvironmentChanged(restoredProject) + || hasFileExistsResultChanged(restoredProject) + || hasFileLastModifiedResultChanged(restoredProject)) { + reResolvingNecessary = true; + } + + if (!reResolvingNecessary) + return; + + restoredProject->buildData->isDirty = true; + Loader ldr(m_evalContext->engine(), m_logger); + ldr.setSearchPaths(parameters.searchPaths()); + ldr.setProgressObserver(m_evalContext->observer()); + m_result.newlyResolvedProject = ldr.loadProject(parameters); + + QMap<QString, ResolvedProductPtr> freshProductsByName; + QList<ResolvedProductPtr> allNewlyResolvedProducts + = m_result.newlyResolvedProject->allProducts(); + foreach (const ResolvedProductPtr &cp, allNewlyResolvedProducts) + freshProductsByName.insert(cp->name, cp); + + checkAllProductsForChanges(allRestoredProducts, freshProductsByName, changedProducts, + productsWithChangedFiles); + + QSharedPointer<ProjectBuildData> oldBuildData; + ChildListHash childLists; + if (!changedProducts.isEmpty() || !productsWithChangedFiles.isEmpty()) { + oldBuildData = QSharedPointer<ProjectBuildData>( + new ProjectBuildData(restoredProject->buildData.data())); + foreach (const ResolvedProductConstPtr &product, allRestoredProducts) { + if (!product->buildData) + continue; + + // If the product gets temporarily removed, its artifacts will get disconnected + // and this structural information will no longer be directly available from them. + foreach (const Artifact * const a, product->buildData->artifacts) + childLists.insert(a, a->children); + } + } + + // For products with "serious" changes such as different prepare scripts, we set up the + // build data from scratch to be on the safe side. This can be made more fine-grained + // if needed. + foreach (const ResolvedProductPtr &product, changedProducts) { + ResolvedProductPtr freshProduct = freshProductsByName.value(product->name); + if (!freshProduct) + continue; + onProductRemoved(product, product->topLevelProject()->buildData.data(), false); + allRestoredProducts.removeOne(product); + productsWithChangedFiles.removeOne(product); + } + + // Move over restored build data to newly resolved project. + m_result.newlyResolvedProject->buildData.swap(restoredProject->buildData); + QBS_CHECK(m_result.newlyResolvedProject->buildData); + m_result.newlyResolvedProject->buildData->isDirty = true; + for (int i = allNewlyResolvedProducts.count() - 1; i >= 0; --i) { + const ResolvedProductPtr &newlyResolvedProduct = allNewlyResolvedProducts.at(i); + for (int j = allRestoredProducts.count() - 1; j >= 0; --j) { + const ResolvedProductPtr &restoredProduct = allRestoredProducts.at(j); + if (newlyResolvedProduct->name == restoredProduct->name) { + if (newlyResolvedProduct->enabled) { + newlyResolvedProduct->buildData.swap(restoredProduct->buildData); + } else { + if (restoredProduct->enabled) { + QBS_CHECK(restoredProduct->buildData); + foreach (Artifact * const a, newlyResolvedProduct->buildData->artifacts) { + const bool removeFromDisk = a->artifactType == Artifact::Generated; + newlyResolvedProduct->topLevelProject()->buildData->removeArtifact(a, + m_logger, removeFromDisk, true); + m_objectsToDelete << a; + } + } + productsWithChangedFiles.removeOne(restoredProduct); + } + if (newlyResolvedProduct->buildData) { + foreach (Artifact * const a, newlyResolvedProduct->buildData->artifacts) + a->product = newlyResolvedProduct; + } + + // Keep in list if build data still needs to be resolved. + if (!newlyResolvedProduct->enabled || newlyResolvedProduct->buildData) + allNewlyResolvedProducts.removeAt(i); + + allRestoredProducts.removeAt(j); + break; + } + } + } + + // Products still left in the list do not exist anymore. + foreach (const ResolvedProductPtr &removedProduct, allRestoredProducts) { + onProductRemoved(removedProduct, m_result.newlyResolvedProject->buildData.data()); + productsWithChangedFiles.removeOne(removedProduct); + } + + // Products still left in the list need resolving, either because they are new + // or because they are newly enabled. + if (!allNewlyResolvedProducts.isEmpty()) { + BuildDataResolver bpr(m_logger); + bpr.resolveProductBuildDataForExistingProject(m_result.newlyResolvedProject, + allNewlyResolvedProducts); + } + + // For products where only the list of files has changed, we adapt the existing build data + // so we won't recompile existing files just because new ones have been added. + foreach (const ResolvedProductPtr &product, productsWithChangedFiles) { + ResolvedProductPtr freshProduct = freshProductsByName.value(product->name); + if (!freshProduct) + continue; + onProductFileListChanged(product, freshProduct, oldBuildData.data()); + } + + foreach (const ResolvedProductConstPtr &changedProduct, changedProducts) { + rescueOldBuildData(changedProduct, freshProductsByName.value(changedProduct->name), + oldBuildData.data(), childLists); + } + + doSanityChecks(m_result.newlyResolvedProject, m_logger); +} + +bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (QHash<QString, QString>::ConstIterator it = restoredProject->usedEnvironment.constBegin(); + it != restoredProject->usedEnvironment.constEnd(); ++it) { + if (m_environment.value(it.key()) != it.value()) { + m_logger.qbsDebug() << "A relevant environment variable changed, " + "must re-resolve project."; + return true; + } + } + return false; +} + +bool BuildGraphLoader::hasFileExistsResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (QHash<QString, bool>::ConstIterator it = restoredProject->fileExistsResults.constBegin(); + it != restoredProject->fileExistsResults.constEnd(); ++it) { + if (FileInfo(it.key()).exists() != it.value()) { + m_logger.qbsDebug() << "Existence check for file '" << it.key() + << " 'changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasFileLastModifiedResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (QHash<QString, FileTime>::ConstIterator it + = restoredProject->fileLastModifiedResults.constBegin(); + it != restoredProject->fileLastModifiedResults.constEnd(); ++it) { + if (FileInfo(it.key()).lastModified() != it.value()) { + m_logger.qbsDebug() << "Timestamp for file '" << it.key() + << " 'changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasProductFileChanged(const QList<ResolvedProductPtr> &restoredProducts, + const FileTime &referenceTime, QSet<QString> &remainingBuildSystemFiles, + QList<ResolvedProductPtr> &productsWithChangedFiles) +{ + bool hasChanged = false; + foreach (const ResolvedProductPtr &product, restoredProducts) { + const QString fileName = product->location.fileName(); + const FileInfo pfi(fileName); + remainingBuildSystemFiles.remove(fileName); + if (!pfi.exists()) { + m_logger.qbsDebug() << "A product was removed, must re-resolve project"; + hasChanged = true; + } else if (referenceTime < pfi.lastModified()) { + m_logger.qbsDebug() << "A product was changed, must re-resolve project"; + hasChanged = true; + } else if (!productsWithChangedFiles.contains(product)) { + foreach (const GroupPtr &group, product->groups) { + if (!group->wildcards) + continue; + const QSet<QString> files + = group->wildcards->expandPatterns(group, product->sourceDirectory); + QSet<QString> wcFiles; + foreach (const SourceArtifactConstPtr &sourceArtifact, group->wildcards->files) + wcFiles += sourceArtifact->absoluteFilePath; + if (files == wcFiles) + continue; + hasChanged = true; + productsWithChangedFiles += product; + break; + } + } + } + + return hasChanged; +} + +bool BuildGraphLoader::hasBuildSystemFileChanged(const QSet<QString> &buildSystemFiles, + const FileTime &referenceTime) +{ + foreach (const QString &file, buildSystemFiles) { + const FileInfo fi(file); + if (!fi.exists() || referenceTime < fi.lastModified()) { + m_logger.qbsDebug() << "A qbs or js file changed, must re-resolve project."; + return true; + } + } + return false; +} + +void BuildGraphLoader::checkAllProductsForChanges(const QList<ResolvedProductPtr> &restoredProducts, + const QMap<QString, ResolvedProductPtr> &newlyResolvedProductsByName, + QList<ResolvedProductPtr> &changedProducts, + QList<ResolvedProductPtr> &productsWithChangedFiles) +{ + foreach (const ResolvedProductPtr &restoredProduct, restoredProducts) { + if (changedProducts.contains(restoredProduct)) + continue; + const ResolvedProductPtr newlyResolvedProduct + = newlyResolvedProductsByName.value(restoredProduct->name); + if (!newlyResolvedProduct) + continue; + if (!productsWithChangedFiles.contains(restoredProduct) + && !sourceArtifactListsAreEqual(restoredProduct->allFiles(), + newlyResolvedProduct->allFiles())) { + m_logger.qbsDebug() << "File list of product '" << restoredProduct->name + << "' was changed."; + productsWithChangedFiles += restoredProduct; + } + if (checkProductForChanges(restoredProduct, newlyResolvedProduct)) { + m_logger.qbsDebug() << "Product '" << restoredProduct->name + << "' was changed, must set up build data from scratch"; + changedProducts << restoredProduct; + } + } +} + +static bool dependenciesAreEqual(const ResolvedProductConstPtr &p1, + const ResolvedProductConstPtr &p2) +{ + if (p1->dependencies.count() != p2->dependencies.count()) + return false; + QSet<QString> names1; + QSet<QString> names2; + foreach (const ResolvedProductConstPtr &dep, p1->dependencies) + names1 << dep->name; + foreach (const ResolvedProductConstPtr &dep, p2->dependencies) + names2 << dep->name; + return names1 == names2; +} + +bool BuildGraphLoader::checkProductForChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + // This check must come first, as it can prevent build data rescuing as a side effect. + // TODO: Similar special checks must be done for qbs.getEnv() and File.exists() in + // commands (or possibly it could be reasonable to just forbid such "dynamic" constructs + // within commands). + if (checkForPropertyChanges(restoredProduct, newlyResolvedProduct)) + return true; + + return !transformerListsAreEqual(restoredProduct->transformers, + newlyResolvedProduct->transformers) + || !ruleListsAreEqual(restoredProduct->rules.toList(), + newlyResolvedProduct->rules.toList()) + || !dependenciesAreEqual(restoredProduct, newlyResolvedProduct); +} + +bool BuildGraphLoader::checkProductForInstallInfoChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + // These are not requested from rules at build time, but we still need to take + // them into account. + const QStringList specialProperties = QStringList() << QLatin1String("install") + << QLatin1String("installDir") << QLatin1String("installPrefix"); + foreach (const QString &key, specialProperties) { + if (restoredProduct->properties->qbsPropertyValue(key) + != newlyResolvedProduct->properties->qbsPropertyValue(key)) { + m_logger.qbsDebug() << "Product property 'qbs." << key << "' changed."; + return true; + } + } + return false; +} + +bool BuildGraphLoader::checkForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + m_logger.qbsDebug() << "Checking for changes in properties requested in prepare scripts for " + "product '" << restoredProduct->name << "'."; + if (!restoredProduct->buildData) + return false; + + // This check must come first, as it can prevent build data rescuing. + if (checkTransformersForPropertyChanges(restoredProduct, newlyResolvedProduct)) + return true; + + if (checkProductForInstallInfoChanges(restoredProduct, newlyResolvedProduct)) + return true; + if (!artifactPropertyListsAreEqual(restoredProduct->artifactProperties, + newlyResolvedProduct->artifactProperties)) { + return true; + } + return false; +} + +bool BuildGraphLoader::checkTransformersForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + bool transformerChanges = false; + QSet<TransformerConstPtr> seenTransformers; + foreach (Artifact * const artifact, restoredProduct->buildData->artifacts) { + const TransformerPtr transformer = artifact->transformer; + if (!transformer || seenTransformers.contains(transformer)) + continue; + seenTransformers.insert(transformer); + if (checkForPropertyChanges(transformer, newlyResolvedProduct)) + transformerChanges = true; + } + if (transformerChanges) { + m_logger.qbsDebug() << "Property changes in product '" + << newlyResolvedProduct->name << "'."; + } + return transformerChanges; +} + +void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, + ProjectBuildData *projectBuildData, bool removeArtifactsFromDisk) +{ + m_logger.qbsDebug() << "[BG] product '" << product->name << "' removed."; + + product->project->products.removeOne(product); + if (product->buildData) { + foreach (Artifact *artifact, product->buildData->artifacts) + projectBuildData->removeArtifact(artifact, m_logger, removeArtifactsFromDisk, false); + } +} + +void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, const ProjectBuildData *oldBuildData) +{ + m_logger.qbsDebug() << "[BG] product '" << restoredProduct->name << "' changed."; + + QBS_CHECK(newlyResolvedProduct->enabled); + + ArtifactsPerFileTagMap artifactsPerFileTag; + QList<Artifact *> addedArtifacts; + ArtifactList artifactsToRemove; + QHash<QString, SourceArtifactConstPtr> oldArtifacts, newArtifacts; + + const QList<SourceArtifactPtr> restoredProductAllFiles = restoredProduct->allEnabledFiles(); + foreach (const SourceArtifactConstPtr &a, restoredProductAllFiles) + oldArtifacts.insert(a->absoluteFilePath, a); + foreach (const SourceArtifactPtr &a, newlyResolvedProduct->allEnabledFiles()) { + newArtifacts.insert(a->absoluteFilePath, a); + if (!oldArtifacts.contains(a->absoluteFilePath)) { + // artifact added + m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath + << "' added to product " << restoredProduct->name; + Artifact *newArtifact = lookupArtifact(newlyResolvedProduct, oldBuildData, + a->absoluteFilePath, true); + if (newArtifact) { + // User added a source file that was a generated artifact in the previous + // build, e.g. a C++ source file that was generated and now is a non-generated + // source file. + newArtifact->artifactType = Artifact::SourceFile; + } else { + newArtifact = createArtifact(newlyResolvedProduct, a, m_logger); + foreach (FileResourceBase *oldArtifactLookupResult, + oldBuildData->lookupFiles(newArtifact->filePath())) { + if (oldArtifactLookupResult == newArtifact) + continue; + FileDependency *oldFileDependency + = dynamic_cast<FileDependency *>(oldArtifactLookupResult); + if (!oldFileDependency) { + // The source file already exists in another product. + continue; + } + // User added a source file that was recognized as file dependency in the + // previous build, e.g. a C++ header file. + replaceFileDependencyWithArtifact(newlyResolvedProduct, + oldFileDependency, + newArtifact); + } + } + addedArtifacts += newArtifact; + } + } + + foreach (const SourceArtifactPtr &a, restoredProductAllFiles) { + const SourceArtifactConstPtr changedArtifact = newArtifacts.value(a->absoluteFilePath); + if (!changedArtifact) { + // artifact removed + m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath + << "' removed from product " << restoredProduct->name; + Artifact *artifact + = lookupArtifact(restoredProduct, oldBuildData, a->absoluteFilePath, true); + QBS_CHECK(artifact); + newlyResolvedProduct->topLevelProject()->buildData + ->removeArtifactAndExclusiveDependents(artifact, m_logger, true, + &artifactsToRemove); + continue; + } + + // TODO: overrideFileTags and properties have to be checked for changes as well. + if (changedArtifact->fileTags != a->fileTags) { + // artifact's filetags have changed + m_logger.qbsDebug() << "[BG] filetags have changed for artifact '" + << a->absoluteFilePath << "' from " << a->fileTags << " to " + << changedArtifact->fileTags; + Artifact *artifact + = lookupArtifact(restoredProduct, oldBuildData, a->absoluteFilePath, true); + QBS_CHECK(artifact); + + // handle added filetags + foreach (const FileTag &addedFileTag, changedArtifact->fileTags - a->fileTags) { + artifact->fileTags += addedFileTag; + artifactsPerFileTag[addedFileTag] += artifact; + } + + // handle removed filetags + foreach (const FileTag &removedFileTag, a->fileTags - changedArtifact->fileTags) { + artifact->fileTags -= removedFileTag; + foreach (Artifact *parent, artifact->parents) { + if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) { + // this parent has been created because of the removed filetag + newlyResolvedProduct->topLevelProject()->buildData + ->removeArtifactAndExclusiveDependents(parent, m_logger, true, + &artifactsToRemove); + } + } + } + } + } + + // apply rules for new artifacts + foreach (Artifact *artifact, addedArtifacts) + foreach (const FileTag &ft, artifact->fileTags) + artifactsPerFileTag[ft] += artifact; + RulesApplicator(newlyResolvedProduct, artifactsPerFileTag, m_logger).applyAllRules(); + + addTargetArtifacts(newlyResolvedProduct, artifactsPerFileTag, m_logger); + + // parents of removed artifacts must update their transformers + foreach (Artifact *removedArtifact, artifactsToRemove) + foreach (Artifact *parent, removedArtifact->parents) + newlyResolvedProduct->topLevelProject()->buildData->artifactsThatMustGetNewTransformers += parent; + newlyResolvedProduct->topLevelProject()->buildData->updateNodesThatMustGetNewTransformer(m_logger); + + // defer destruction of removed artifacts + foreach (Artifact *artifact, artifactsToRemove) + m_objectsToDelete << artifact; +} + +static SourceArtifactConstPtr findSourceArtifact(const ResolvedProductConstPtr &product, + const QString &artifactFilePath, QMap<QString, SourceArtifactConstPtr> &artifactMap) +{ + SourceArtifactConstPtr &artifact = artifactMap[artifactFilePath]; + if (!artifact) { + foreach (const SourceArtifactConstPtr &a, product->allFiles()) { + if (a->absoluteFilePath == artifactFilePath) { + artifact = a; + break; + } + } + } + return artifact; +} + +bool BuildGraphLoader::checkForPropertyChanges(const TransformerPtr &restoredTrafo, + const ResolvedProductPtr &freshProduct) +{ + // This check must come first, as it can prevent build data rescuing. + foreach (const Property &property, restoredTrafo->propertiesRequestedInCommands) { + const QVariantMap properties = property.kind == Property::PropertyInProject + ? freshProduct->project->projectProperties() : freshProduct->properties->value(); + if (checkForPropertyChange(property, properties)) { + JavaScriptCommand * const pseudoCommand = new JavaScriptCommand; + pseudoCommand->setSourceCode(QLatin1String("random stuff that will cause " + "commandsEqual() to fail")); + restoredTrafo->commands << pseudoCommand; + return true; + } + } + + foreach (const Property &property, restoredTrafo->propertiesRequestedInPrepareScript) { + const QVariantMap properties = property.kind == Property::PropertyInProject + ? freshProduct->project->projectProperties() : freshProduct->properties->value(); + if (checkForPropertyChange(property, properties)) + return true; + } + + QMap<QString, SourceArtifactConstPtr> artifactMap; + for (QHash<QString, PropertyList>::ConstIterator it = + restoredTrafo->propertiesRequestedFromArtifactInPrepareScript.constBegin(); + it != restoredTrafo->propertiesRequestedFromArtifactInPrepareScript.constEnd(); ++it) { + const SourceArtifactConstPtr artifact + = findSourceArtifact(freshProduct, it.key(), artifactMap); + if (!artifact) + continue; + foreach (const Property &property, it.value()) { + if (checkForPropertyChange(property, artifact->properties->value())) + return true; + } + } + return false; +} + +bool BuildGraphLoader::checkForPropertyChange(const Property &restoredProperty, + const QVariantMap &newProperties) +{ + PropertyFinder finder; + QVariant v; + switch (restoredProperty.kind) { + case Property::PropertyInProduct: + case Property::PropertyInProject: + v = newProperties.value(restoredProperty.propertyName); + break; + case Property::PropertyInModule: + if (restoredProperty.value.type() == QVariant::List) { + v = finder.propertyValues(newProperties, restoredProperty.moduleName, + restoredProperty.propertyName); + } else { + v = finder.propertyValue(newProperties, restoredProperty.moduleName, + restoredProperty.propertyName); + } + break; + } + if (restoredProperty.value != v) { + m_logger.qbsDebug() << "Value for property '" << restoredProperty.moduleName << "." + << restoredProperty.propertyName << "' has changed."; + m_logger.qbsDebug() << "Old value was '" << restoredProperty.value << "'."; + m_logger.qbsDebug() << "New value is '" << v << "'."; + return true; + } + return false; +} + +void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct, + FileDependency *filedep, Artifact *artifact) +{ + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() + << QString::fromLocal8Bit("[BG] replace file dependency '%1' " + "with artifact of type '%2'") + .arg(filedep->filePath()).arg(artifact->artifactType); + } + foreach (const ResolvedProductPtr &product, fileDepProduct->topLevelProject()->allProducts()) { + if (!product->buildData) + continue; + foreach (Artifact *artifactInProduct, product->buildData->artifacts) { + if (artifactInProduct->fileDependencies.contains(filedep)) { + artifactInProduct->fileDependencies.remove(filedep); + loggedConnect(artifactInProduct, artifact, m_logger); + } + } + } + fileDepProduct->topLevelProject()->buildData->fileDependencies.remove(filedep); + fileDepProduct->topLevelProject()->buildData->removeFromLookupTable(filedep); + m_objectsToDelete << filedep; +} + +static bool commandsEqual(const TransformerConstPtr &t1, const TransformerConstPtr &t2) +{ + if (t1->commands.count() != t2->commands.count()) + return false; + for (int i = 0; i < t1->commands.count(); ++i) + if (!t1->commands.at(i)->equals(t2->commands.at(i))) + return false; + return true; +} + +/** + * Rescues the following data from the restoredProduct to newlyResolvedProduct: + * - dependencies between artifacts, + * - time stamps of artifacts, if their commands have not changed. + */ +void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, + const ProjectBuildData *oldBuildData, const ChildListHash &childLists) +{ + if (!restoredProduct->enabled || !newlyResolvedProduct->enabled) + return; + + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] rescue data of " + "product '%1'").arg(restoredProduct->name); + } + + foreach (Artifact *artifact, newlyResolvedProduct->buildData->artifacts) { + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] artifact '%1'") + .arg(artifact->fileName()); + } + + Artifact * const oldArtifact = lookupArtifact(restoredProduct, oldBuildData, + artifact->dirPath(), artifact->fileName(), true); + if (!oldArtifact || !oldArtifact->transformer) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] no transformer data"); + continue; + } + + if (artifact->transformer + && !commandsEqual(artifact->transformer, oldArtifact->transformer)) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] artifact invalidated"); + removeGeneratedArtifactFromDisk(oldArtifact, m_logger); + continue; + } + artifact->setTimestamp(oldArtifact->timestamp()); + + foreach (Artifact * const oldChild, childLists.value(oldArtifact)) { + foreach (FileResourceBase *childFileRes, + newlyResolvedProduct->topLevelProject()->buildData->lookupFiles(oldChild)) { + Artifact * const child = dynamic_cast<Artifact *>(childFileRes); + if (child && !artifact->children.contains(child)) + safeConnect(artifact, child, m_logger); + } + } + } +} + +void addTargetArtifacts(const ResolvedProductPtr &product, + ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger) +{ + foreach (const FileTag &fileTag, product->fileTags) { + foreach (Artifact * const artifact, artifactsPerFileTag.value(fileTag)) { + if (artifact->artifactType == Artifact::Generated) + product->buildData->targetArtifacts += artifact; + } + } + if (product->buildData->targetArtifacts.isEmpty()) { + const QString msg = QString::fromLocal8Bit("No artifacts generated for product '%1'."); + logger.qbsDebug() << msg.arg(product->name); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h new file mode 100644 index 000000000..f7f7b79e3 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphloader.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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_BUILDGRAPHLOADER_H +#define QBS_BUILDGRAPHLOADER_H + +#include "forward_decls.h" + +#include <buildgraph/artifactlist.h> +#include <language/forward_decls.h> +#include <logging/logger.h> + +#include <QProcessEnvironment> +#include <QVariantMap> + +namespace qbs { +class SetupProjectParameters; + +namespace Internal { +class FileDependency; +class FileResourceBase; +class FileTime; +class Property; + +class BuildGraphLoadResult +{ +public: + TopLevelProjectPtr newlyResolvedProject; + TopLevelProjectPtr loadedProject; +}; + + +class BuildGraphLoader +{ +public: + BuildGraphLoader(const QProcessEnvironment &env, const Logger &logger); + ~BuildGraphLoader(); + + BuildGraphLoadResult load(const SetupProjectParameters ¶meters, + const RulesEvaluationContextPtr &evalContext); + +private: + void trackProjectChanges(const SetupProjectParameters ¶meters, + const QString &buildGraphFilePath, + const TopLevelProjectPtr &restoredProject, + const QVariantMap &oldProjectConfig); + bool hasEnvironmentChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasFileExistsResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasFileLastModifiedResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasProductFileChanged(const QList<ResolvedProductPtr> &restoredProducts, + const FileTime &referenceTime, + QSet<QString> &remainingBuildSystemFiles, + QList<ResolvedProductPtr> &productsWithChangedFiles); + bool hasBuildSystemFileChanged(const QSet<QString> &buildSystemFiles, + const FileTime &referenceTime); + void checkAllProductsForChanges(const QList<ResolvedProductPtr> &restoredProducts, + const QMap<QString, ResolvedProductPtr> &newlyResolvedProductsByName, + QList<ResolvedProductPtr> &changedProducts, + QList<ResolvedProductPtr> &productsWithChangedFiles); + bool checkProductForChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkProductForInstallInfoChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkTransformersForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + void onProductRemoved(const ResolvedProductPtr &product, ProjectBuildData *projectBuildData, + bool removeArtifactsFromDisk = true); + void onProductFileListChanged(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, const ProjectBuildData *oldBuildData); + void removeArtifactAndExclusiveDependents(Artifact *artifact, + ArtifactList *removedArtifacts = 0); + bool checkForPropertyChanges(const TransformerPtr &restoredTrafo, + const ResolvedProductPtr &freshProduct); + bool checkForPropertyChange(const Property &restoredProperty, + const QVariantMap &newProperties); + void replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct, + FileDependency *filedep, Artifact *artifact); + + typedef QHash<const Artifact *, ArtifactList> ChildListHash; + void rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, + const ProjectBuildData *oldBuildData, + const ChildListHash &childLists); + + RulesEvaluationContextPtr m_evalContext; + BuildGraphLoadResult m_result; + Logger m_logger; + QProcessEnvironment m_environment; + + // These must only be deleted at the end so we can still peek into the old look-up table. + QList<FileResourceBase *> m_objectsToDelete; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/command.cpp b/src/lib/corelib/buildgraph/command.cpp new file mode 100644 index 000000000..8e30906ee --- /dev/null +++ b/src/lib/corelib/buildgraph/command.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** 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 "command.h" +#include <logging/translator.h> +#include <tools/qbsassert.h> +#include <tools/hostosinfo.h> + +#include <QScriptEngine> +#include <QScriptValueIterator> +#include <QSet> + +namespace qbs { +namespace Internal { + +AbstractCommand::AbstractCommand() + : m_description(defaultDescription()), + m_highlight(defaultHighLight()), + m_silent(defaultIsSilent()) +{ +} + +AbstractCommand::~AbstractCommand() +{ +} + +AbstractCommand *AbstractCommand::createByType(AbstractCommand::CommandType commandType) +{ + switch (commandType) { + case AbstractCommand::ProcessCommandType: + return new ProcessCommand; + case AbstractCommand::JavaScriptCommandType: + return new JavaScriptCommand; + } + qFatal("%s: Invalid command type %d", Q_FUNC_INFO, commandType); + return 0; +} + +bool AbstractCommand::equals(const AbstractCommand *other) const +{ + return m_description == other->m_description && m_highlight == other->m_highlight + && m_silent == other->m_silent && type() == other->type(); +} + +void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + m_description = scriptValue->property("description").toString(); + m_highlight = scriptValue->property("highlight").toString(); + m_silent = scriptValue->property("silent").toBool(); + m_codeLocation = codeLocation; +} + +void AbstractCommand::load(QDataStream &s) +{ + s >> m_description >> m_highlight >> m_silent >> m_codeLocation; +} + +void AbstractCommand::store(QDataStream &s) +{ + s << m_description << m_highlight << m_silent << m_codeLocation; +} + +static QScriptValue js_CommandBase(QScriptContext *context, QScriptEngine *engine) +{ + QScriptValue cmd = context->thisObject(); + QBS_ASSERT(context->isCalledAsConstructor(), cmd = engine->newObject()); + cmd.setProperty("description", engine->toScriptValue(AbstractCommand::defaultDescription())); + cmd.setProperty("highlight", engine->toScriptValue(AbstractCommand::defaultHighLight())); + cmd.setProperty("silent", engine->toScriptValue(AbstractCommand::defaultIsSilent())); + return cmd; +} + +static QScriptValue js_Command(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(!context->isCalledAsConstructor())) + return context->throwError(Tr::tr("Command constructor called without new.")); + + static ProcessCommand commandPrototype; + + QScriptValue program = context->argument(0); + if (program.isUndefined()) + program = engine->toScriptValue(commandPrototype.program()); + QScriptValue arguments = context->argument(1); + if (arguments.isUndefined()) + arguments = engine->toScriptValue(commandPrototype.arguments()); + QScriptValue cmd = js_CommandBase(context, engine); + cmd.setProperty("className", engine->toScriptValue(QString("Command"))); + cmd.setProperty("program", program); + cmd.setProperty("arguments", arguments); + cmd.setProperty("workingDir", engine->toScriptValue(commandPrototype.workingDir())); + cmd.setProperty("maxExitCode", engine->toScriptValue(commandPrototype.maxExitCode())); + cmd.setProperty("stdoutFilterFunction", engine->toScriptValue(commandPrototype.stdoutFilterFunction())); + cmd.setProperty("stderrFilterFunction", engine->toScriptValue(commandPrototype.stderrFilterFunction())); + cmd.setProperty("responseFileThreshold", engine->toScriptValue(commandPrototype.responseFileThreshold())); + cmd.setProperty("responseFileUsagePrefix", engine->toScriptValue(commandPrototype.responseFileUsagePrefix())); + cmd.setProperty("environment", engine->toScriptValue(commandPrototype.environment().toStringList())); + return cmd; +} + + +void ProcessCommand::setupForJavaScript(QScriptValue targetObject) +{ + QBS_CHECK(targetObject.isObject()); + QScriptValue ctor = targetObject.engine()->newFunction(js_Command, 2); + targetObject.setProperty("Command", ctor); +} + +ProcessCommand::ProcessCommand() + : m_maxExitCode(0) + , m_responseFileThreshold(HostOsInfo::isWindowsHost() ? 32000 : -1) +{ +} + +void ProcessCommand::getEnvironmentFromList(const QStringList &envList) +{ + m_environment.clear(); + foreach (const QString &env, envList) { + const int equalsIndex = env.indexOf(QLatin1Char('=')); + if (equalsIndex <= 0 || equalsIndex == env.count() - 1) + continue; + const QString &var = env.left(equalsIndex); + const QString &value = env.mid(equalsIndex + 1); + m_environment.insert(var, value); + } +} + +bool ProcessCommand::equals(const AbstractCommand *otherAbstractCommand) const +{ + if (!AbstractCommand::equals(otherAbstractCommand)) + return false; + const ProcessCommand * const other = static_cast<const ProcessCommand *>(otherAbstractCommand); + return m_program == other->m_program + && m_arguments == other->m_arguments + && m_workingDir == other->m_workingDir + && m_maxExitCode == other->m_maxExitCode + && m_stdoutFilterFunction == other->m_stdoutFilterFunction + && m_stderrFilterFunction == other->m_stderrFilterFunction + && m_responseFileThreshold == other->m_responseFileThreshold + && m_responseFileUsagePrefix == other->m_responseFileUsagePrefix + && m_environment == other->m_environment; +} + +void ProcessCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); + m_program = scriptValue->property("program").toString(); + m_arguments = scriptValue->property("arguments").toVariant().toStringList(); + m_workingDir = scriptValue->property("workingDirectory").toString(); + m_maxExitCode = scriptValue->property("maxExitCode").toInt32(); + m_stdoutFilterFunction = scriptValue->property("stdoutFilterFunction").toString(); + m_stderrFilterFunction = scriptValue->property("stderrFilterFunction").toString(); + m_responseFileThreshold = scriptValue->property("responseFileThreshold").toInt32(); + m_responseFileUsagePrefix = scriptValue->property("responseFileUsagePrefix").toString(); + QStringList envList = scriptValue->property(QLatin1String("environment")).toVariant() + .toStringList(); + getEnvironmentFromList(envList); +} + +void ProcessCommand::load(QDataStream &s) +{ + AbstractCommand::load(s); + QStringList envList; + s >> m_program + >> m_arguments + >> envList + >> m_workingDir + >> m_maxExitCode + >> m_stdoutFilterFunction + >> m_stderrFilterFunction + >> m_responseFileThreshold + >> m_responseFileUsagePrefix; + getEnvironmentFromList(envList); +} + +void ProcessCommand::store(QDataStream &s) +{ + AbstractCommand::store(s); + s << m_program + << m_arguments + << m_environment.toStringList() + << m_workingDir + << m_maxExitCode + << m_stdoutFilterFunction + << m_stderrFilterFunction + << m_responseFileThreshold + << m_responseFileUsagePrefix; +} + +static QScriptValue js_JavaScriptCommand(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(!context->isCalledAsConstructor())) + return context->throwError(Tr::tr("JavaScriptCommand constructor called without new.")); + if (Q_UNLIKELY(context->argumentCount() != 0)) { + return context->throwError(QScriptContext::SyntaxError, + "JavaScriptCommand c'tor doesn't take arguments."); + } + + static JavaScriptCommand commandPrototype; + QScriptValue cmd = js_CommandBase(context, engine); + cmd.setProperty("className", engine->toScriptValue(QString("JavaScriptCommand"))); + cmd.setProperty("sourceCode", engine->toScriptValue(commandPrototype.sourceCode())); + return cmd; +} + +void JavaScriptCommand::setupForJavaScript(QScriptValue targetObject) +{ + QBS_CHECK(targetObject.isObject()); + QScriptValue ctor = targetObject.engine()->newFunction(js_JavaScriptCommand, 0); + targetObject.setProperty("JavaScriptCommand", ctor); +} + +JavaScriptCommand::JavaScriptCommand() +{ +} + +bool JavaScriptCommand::equals(const AbstractCommand *otherAbstractCommand) const +{ + if (!AbstractCommand::equals(otherAbstractCommand)) + return false; + const JavaScriptCommand * const other + = static_cast<const JavaScriptCommand *>(otherAbstractCommand); + return m_sourceCode == other->m_sourceCode + && m_properties == other->m_properties; +} + +void JavaScriptCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); + QScriptValue sourceCode = scriptValue->property("sourceCode"); + if (sourceCode.isFunction()) + m_sourceCode = "(" + sourceCode.toString() + ")()"; + else + m_sourceCode = sourceCode.toString(); + static QSet<QString> predefinedProperties = QSet<QString>() + << "description" << "highlight" << "silent" << "className" << "sourceCode"; + + QScriptValueIterator it(*scriptValue); + while (it.hasNext()) { + it.next(); + if (predefinedProperties.contains(it.name())) + continue; + m_properties.insert(it.name(), it.value().toVariant()); + } +} + +void JavaScriptCommand::load(QDataStream &s) +{ + AbstractCommand::load(s); + s >> m_sourceCode + >> m_properties; +} + +void JavaScriptCommand::store(QDataStream &s) +{ + AbstractCommand::store(s); + s << m_sourceCode + << m_properties; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/command.h b/src/lib/corelib/buildgraph/command.h new file mode 100644 index 000000000..69fa32299 --- /dev/null +++ b/src/lib/corelib/buildgraph/command.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 QBS_COMMAND_H +#define QBS_COMMAND_H + +#include <tools/codelocation.h> + +#include <QProcessEnvironment> +#include <QStringList> +#include <QVariantMap> +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class AbstractCommand +{ +public: + virtual ~AbstractCommand(); + + enum CommandType { + ProcessCommandType, + JavaScriptCommandType + }; + + static AbstractCommand *createByType(CommandType commandType); + static QString defaultDescription() { return QString(); } + static QString defaultHighLight() { return QString(); } + static bool defaultIsSilent() { return false; } + + virtual CommandType type() const = 0; + virtual bool equals(const AbstractCommand *other) const; + virtual void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); + virtual void load(QDataStream &s); + virtual void store(QDataStream &s); + + const QString description() const { return m_description; } + const QString highlight() const { return m_highlight; } + bool isSilent() const { return m_silent; } + CodeLocation codeLocation() const { return m_codeLocation; } + +protected: + AbstractCommand(); + +private: + QString m_description; + QString m_highlight; + bool m_silent; + CodeLocation m_codeLocation; +}; + +class ProcessCommand : public AbstractCommand +{ +public: + static void setupForJavaScript(QScriptValue targetObject); + + ProcessCommand(); + + CommandType type() const { return ProcessCommandType; } + bool equals(const AbstractCommand *otherAbstractCommand) const; + void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); + void load(QDataStream &s); + void store(QDataStream &s); + + const QString program() const { return m_program; } + const QStringList arguments() const { return m_arguments; } + const QString workingDir() const { return m_workingDir; } + int maxExitCode() const { return m_maxExitCode; } + QString stdoutFilterFunction() const { return m_stdoutFilterFunction; } + QString stderrFilterFunction() const { return m_stderrFilterFunction; } + int responseFileThreshold() const { return m_responseFileThreshold; } + QString responseFileUsagePrefix() const { return m_responseFileUsagePrefix; } + QProcessEnvironment environment() const { return m_environment; } + +private: + void getEnvironmentFromList(const QStringList &envList); + + QString m_program; + QStringList m_arguments; + QString m_workingDir; + int m_maxExitCode; + QString m_stdoutFilterFunction; + QString m_stderrFilterFunction; + int m_responseFileThreshold; // When to use response files? In bytes of (program name + arguments). + QString m_responseFileUsagePrefix; + QProcessEnvironment m_environment; +}; + +class JavaScriptCommand : public AbstractCommand +{ +public: + static void setupForJavaScript(QScriptValue targetObject); + + JavaScriptCommand(); + + virtual CommandType type() const { return JavaScriptCommandType; } + bool equals(const AbstractCommand *otherAbstractCommand) const; + void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); + void load(QDataStream &s); + void store(QDataStream &s); + + const QString &sourceCode() const { return m_sourceCode; } + void setSourceCode(const QString &str) { m_sourceCode = str; } + + const QVariantMap &properties() const { return m_properties; } + +private: + QString m_sourceCode; + QVariantMap m_properties; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_COMMAND_H diff --git a/src/lib/corelib/buildgraph/cycledetector.cpp b/src/lib/corelib/buildgraph/cycledetector.cpp new file mode 100644 index 000000000..1ab6b4bb8 --- /dev/null +++ b/src/lib/corelib/buildgraph/cycledetector.cpp @@ -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. +** +****************************************************************************/ +#include "cycledetector.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "productbuilddata.h" + +#include <language/language.h> +#include <logging/translator.h> +#include <tools/error.h> + +namespace qbs { +namespace Internal { + +CycleDetector::CycleDetector(const Logger &logger) + : ArtifactVisitor(0), m_parent(0), m_logger(logger) +{ +} + +void CycleDetector::visitProject(const ResolvedProjectConstPtr &project) +{ + const QString description = QString::fromLocal8Bit("Cycle detection for project '%1'") + .arg(project->name); + TimedActivityLogger timeLogger(m_logger, description, QLatin1String("[BG] "), LoggerTrace); + ArtifactVisitor::visitProject(project); +} + +void CycleDetector::visitProduct(const ResolvedProductConstPtr &product) +{ + if (!product->buildData) + return; + foreach (Artifact * const artifact, product->buildData->targetArtifacts) + visitArtifact(artifact); +} + +void CycleDetector::visitArtifact(Artifact *artifact) +{ + if (Q_UNLIKELY(m_artifactsInCurrentPath.contains(artifact))) { + ErrorInfo error(Tr::tr("Cycle in build graph detected.")); + foreach (const Artifact * const a, cycle(artifact)) + error.append(a->filePath()); + throw error; + } + + if (m_allArtifacts.contains(artifact)) + return; + + m_artifactsInCurrentPath += artifact; + m_parent = artifact; + foreach (Artifact * const child, artifact->children) + visitArtifact(child); + m_artifactsInCurrentPath -= artifact; + m_allArtifacts += artifact; +} + +void CycleDetector::doVisit(Artifact *) { } + +QList<Artifact *> CycleDetector::cycle(Artifact *doubleEntry) +{ + QList<Artifact *> path; + findPath(doubleEntry, m_parent, path); + return path << doubleEntry; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/cycledetector.h b/src/lib/corelib/buildgraph/cycledetector.h new file mode 100644 index 000000000..6e55efac3 --- /dev/null +++ b/src/lib/corelib/buildgraph/cycledetector.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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_CYCLEDETECTOR_H +#define QBS_CYCLEDETECTOR_H + +#include "artifactvisitor.h" +#include <logging/logger.h> + +#include <QSet> + +namespace qbs { +namespace Internal { + +class CycleDetector : public ArtifactVisitor +{ +public: + CycleDetector(const Logger &logger); + + void visitProject(const ResolvedProjectConstPtr &project); + void visitProduct(const ResolvedProductConstPtr &product); + void visitArtifact(Artifact *artifact); + +private: + void doVisit(Artifact *artifact); + + QList<Artifact *> cycle(Artifact *doubleEntry); + + QSet<Artifact *> m_allArtifacts; + QSet<Artifact *> m_artifactsInCurrentPath; + Artifact *m_parent; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_CYCLEDETECTOR_H diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp new file mode 100644 index 000000000..c1f7c6af3 --- /dev/null +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -0,0 +1,905 @@ +/**************************************************************************** +** +** 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 "executor.h" + +#include "artifactvisitor.h" +#include "automoc.h" +#include "buildgraph.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "cycledetector.h" +#include "executorjob.h" +#include "inputartifactscanner.h" +#include "rulesevaluationcontext.h" + +#include <buildgraph/transformer.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QSet> +#include <QTimer> + +#include <algorithm> +#include <climits> + +namespace qbs { +namespace Internal { + +class MocEffortCalculator : public ArtifactVisitor +{ +public: + MocEffortCalculator() : ArtifactVisitor(Artifact::SourceFile), m_effort(0) {} + + int effort() const { return m_effort; } + +private: + void doVisit(Artifact *) { m_effort += 10; } + + int m_effort; +}; + +class BuildEffortCalculator : public ArtifactVisitor +{ +public: + BuildEffortCalculator() : ArtifactVisitor(Artifact::Generated), m_effort(0) {} + + int effort() const { return m_effort; } + + static int multiplier(const Artifact *artifact) { + return artifact->transformer->rule->multiplex ? 200 : 20; + } + +private: + void doVisit(Artifact *artifact) + { + m_effort += multiplier(artifact); + } + + int m_effort; +}; + + +bool Executor::ComparePriority::operator() (const Artifact *x, const Artifact *y) const +{ + return x->product->buildData->buildPriority < y->product->buildData->buildPriority; +} + + +Executor::Executor(const Logger &logger, QObject *parent) + : QObject(parent) + , m_logger(logger) + , m_progressObserver(0) + , m_state(ExecutorIdle) + , m_doTrace(logger.traceEnabled()) + , m_doDebug(logger.debugEnabled()) +{ + m_inputArtifactScanContext = new InputArtifactScannerContext(&m_scanResultCache); + m_autoMoc = new AutoMoc(logger); + connect(m_autoMoc, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString))); + m_autoMoc->setScanResultCache(&m_scanResultCache); +} + +Executor::~Executor() +{ + // jobs must be destroyed before deleting the shared scan result cache + foreach (ExecutorJob *job, m_availableJobs) + delete job; + foreach (ExecutorJob *job, m_processingJobs.keys()) + delete job; + delete m_autoMoc; // delete before shared scan result cache + delete m_inputArtifactScanContext; +} + +FileTime Executor::recursiveFileTime(const QString &filePath) const +{ + FileTime newest; + if (m_progressObserver) { + qApp->processEvents(); // let the cancel event do its work + if (m_progressObserver->canceled()) + return newest; + } + FileInfo fileInfo(filePath); + if (!fileInfo.exists()) { + const QString nativeFilePath = QDir::toNativeSeparators(filePath); + m_logger.qbsWarning() << Tr::tr("File '%1' not found.").arg(nativeFilePath); + return newest; + } + newest = qMax(fileInfo.lastModified(), fileInfo.lastStatusChange()); + if (!fileInfo.isDir()) + return newest; + const QStringList dirContents = QDir(filePath) + .entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &curFileName, dirContents) { + const FileTime ft = recursiveFileTime(filePath + QLatin1Char('/') + curFileName); + if (ft > newest) + newest = ft; + } + return newest; +} + +void Executor::retrieveSourceFileTimestamp(Artifact *artifact) const +{ + QBS_CHECK(artifact->artifactType == Artifact::SourceFile); + + artifact->setTimestamp(recursiveFileTime(artifact->filePath())); + artifact->timestampRetrieved = true; +} + +void Executor::build() +{ + try { + doBuild(); + } catch (const ErrorInfo &e) { + m_error = e; + QTimer::singleShot(0, this, SLOT(finish())); + } +} + +void Executor::setProject(const TopLevelProjectPtr &project) +{ + m_project = project; +} + +void Executor::setProducts(const QList<ResolvedProductPtr> &productsToBuild) +{ + m_productsToBuild = productsToBuild; +} + +class ProductPrioritySetter +{ + const TopLevelProject *m_topLevelProject; + unsigned int m_priority; + QSet<ResolvedProductPtr> m_seenProducts; +public: + ProductPrioritySetter(const TopLevelProject *tlp) + : m_topLevelProject(tlp) + { + } + + void apply() + { + QList<ResolvedProductPtr> allProducts = m_topLevelProject->allProducts(); + QSet<ResolvedProductPtr> allDependencies; + foreach (const ResolvedProductPtr &product, allProducts) + allDependencies += product->dependencies; + QSet<ResolvedProductPtr> rootProducts = allProducts.toSet() - allDependencies; + m_priority = UINT_MAX; + m_seenProducts.clear(); + foreach (const ResolvedProductPtr &rootProduct, rootProducts) + traverse(rootProduct); + } + +private: + void traverse(const ResolvedProductPtr &product) + { + if (m_seenProducts.contains(product)) + return; + m_seenProducts += product; + foreach (const ResolvedProductPtr &dependency, product->dependencies) + traverse(dependency); + if (!product->buildData) + return; + product->buildData->buildPriority = m_priority--; + } +}; + +void Executor::doBuild() +{ + if (m_buildOptions.maxJobCount() <= 0) { + m_buildOptions.setMaxJobCount(BuildOptions::defaultMaxJobCount()); + m_logger.qbsDebug() << "max job count not explicitly set, using value of " + << m_buildOptions.maxJobCount(); + } + QBS_CHECK(m_state == ExecutorIdle); + m_leaves = Leaves(); + m_error.clear(); + m_explicitlyCanceled = false; + m_activeFileTags = FileTags::fromStringList(m_buildOptions.activeFileTags()); + + setState(ExecutorRunning); + + if (m_productsToBuild.isEmpty()) { + m_logger.qbsTrace() << "No products to build, finishing."; + QTimer::singleShot(0, this, SLOT(finish())); // Don't call back on the caller. + return; + } + + doSanityChecks(); + m_evalContext = m_project->buildData->evaluationContext; + if (!m_evalContext) { // Is null before the first build. + m_evalContext = RulesEvaluationContextPtr(new RulesEvaluationContext(m_logger)); + m_project->buildData->evaluationContext = m_evalContext; + } + + m_logger.qbsDebug() << QString::fromLocal8Bit("[EXEC] preparing executor for %1 jobs " + "in parallel").arg(m_buildOptions.maxJobCount()); + addExecutorJobs(m_buildOptions.maxJobCount()); + foreach (ExecutorJob * const job, m_availableJobs) + job->setDryRun(m_buildOptions.dryRun()); + + bool sourceFilesChanged = false; + prepareAllArtifacts(&sourceFilesChanged); + Artifact::BuildState initialBuildState = m_buildOptions.changedFiles().isEmpty() + ? Artifact::Buildable : Artifact::Built; + + QList<Artifact *> changedArtifacts; + foreach (const QString &filePath, m_buildOptions.changedFiles()) { + QList<FileResourceBase *> lookupResults; + lookupResults.append(m_project->buildData->lookupFiles(filePath)); + if (lookupResults.isEmpty()) { + m_logger.qbsWarning() << QString::fromLocal8Bit("Out of date file '%1' provided " + "but not found.").arg(QDir::toNativeSeparators(filePath)); + continue; + } + foreach (FileResourceBase *lookupResult, lookupResults) + if (Artifact *artifact = dynamic_cast<Artifact *>(lookupResult)) + changedArtifacts += artifact; + } + qSort(changedArtifacts); + changedArtifacts.erase(std::unique(changedArtifacts.begin(), changedArtifacts.end()), + changedArtifacts.end()); + + // prepare products + ProductPrioritySetter prioritySetter(m_productsToBuild.first()->topLevelProject()); + prioritySetter.apply(); + foreach (ResolvedProductPtr product, m_productsToBuild) + product->setupBuildEnvironment(m_evalContext->engine(), m_project->environment); + + // find the root nodes + m_roots.clear(); + foreach (const ResolvedProductPtr &product, m_productsToBuild) { + foreach (Artifact *targetArtifact, product->buildData->targetArtifacts) { + m_roots += targetArtifact; + + // The user expects that he can delete target artifacts and they get rebuilt. + // To achieve this we must retrieve their timestamps. + targetArtifact->setTimestamp(FileInfo(targetArtifact->filePath()).lastModified()); + } + } + + prepareReachableArtifacts(initialBuildState); + setupProgressObserver(sourceFilesChanged); + if (sourceFilesChanged) + runAutoMoc(); + initLeaves(changedArtifacts); + if (!scheduleJobs()) { + m_logger.qbsTrace() << "Nothing to do at all, finishing."; + QTimer::singleShot(0, this, SLOT(finish())); // Don't call back on the caller. + } +} + +void Executor::setBuildOptions(const BuildOptions &buildOptions) +{ + m_buildOptions = buildOptions; +} + +static void initArtifactsBottomUp(Artifact *artifact) +{ + if (artifact->buildState == Artifact::Untouched) + return; + artifact->buildState = Artifact::Buildable; + foreach (Artifact *parent, artifact->parents) + initArtifactsBottomUp(parent); +} + +void Executor::initLeaves(const QList<Artifact *> &changedArtifacts) +{ + if (changedArtifacts.isEmpty()) { + QSet<Artifact *> seenArtifacts; + foreach (Artifact *root, m_roots) + initLeavesTopDown(root, seenArtifacts); + } else { + foreach (Artifact *artifact, changedArtifacts) { + m_leaves.push(artifact); + initArtifactsBottomUp(artifact); + } + } +} + +void Executor::initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts) +{ + if (seenArtifacts.contains(artifact)) + return; + seenArtifacts += artifact; + + // Artifacts that appear in the build graph after + // prepareBuildGraph() has been called, must be initialized. + if (artifact->buildState == Artifact::Untouched) { + artifact->buildState = Artifact::Buildable; + if (artifact->artifactType == Artifact::SourceFile) + retrieveSourceFileTimestamp(artifact); + } + + if (artifact->children.isEmpty()) { + m_leaves.push(artifact); + } else { + foreach (Artifact *child, artifact->children) + initLeavesTopDown(child, seenArtifacts); + } +} + +// Returns true if some artifacts are still waiting to be built or currently building. +bool Executor::scheduleJobs() +{ + QBS_CHECK(m_state == ExecutorRunning); + while (!m_leaves.empty() && !m_availableJobs.isEmpty()) { + Artifact * const artifact = m_leaves.top(); + m_leaves.pop(); + buildArtifact(artifact); + } + return !m_leaves.empty() || !m_processingJobs.isEmpty(); +} + +bool Executor::isUpToDate(Artifact *artifact) const +{ + QBS_CHECK(artifact->artifactType == Artifact::Generated); + + const bool debug = false; + if (debug) { + m_logger.qbsDebug() << "[UTD] check " << artifact->filePath() << " " + << artifact->timestamp().toString(); + } + + if (m_buildOptions.forceTimestampCheck()) { + artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified()); + if (debug) { + m_logger.qbsDebug() << "[UTD] timestamp retrieved from filesystem: " + << artifact->timestamp().toString(); + } + } + + if (!artifact->timestamp().isValid()) { + if (debug) + m_logger.qbsDebug() << "[UTD] invalid timestamp. Out of date."; + return false; + } + + foreach (Artifact *child, artifact->children) { + QBS_CHECK(child->timestamp().isValid()); + if (debug) + m_logger.qbsDebug() << "[UTD] child timestamp " << child->timestamp().toString(); + if (artifact->timestamp() < child->timestamp()) + return false; + } + + foreach (FileDependency *fileDependency, artifact->fileDependencies) { + if (!fileDependency->timestamp().isValid()) { + FileInfo fi(fileDependency->filePath()); + fileDependency->setTimestamp(fi.lastModified()); + } + if (debug) + m_logger.qbsDebug() << "[UTD] file dependency timestamp " + << fileDependency->timestamp().toString(); + if (artifact->timestamp() < fileDependency->timestamp()) + return false; + } + + return true; +} + +bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const +{ + foreach (Artifact *artifact, transformer->outputs) + if (artifact->alwaysUpdated) + return !isUpToDate(artifact); + + // All outputs of the transformer have alwaysUpdated == false. + // We need at least on output that is always updated. + QBS_CHECK(false); + return true; +} + +void Executor::buildArtifact(Artifact *artifact) +{ + QBS_CHECK(!m_availableJobs.isEmpty()); + + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] " << relativeArtifactFileName(artifact); + + // Skip artifacts that are already built. + if (artifact->buildState == Artifact::Built) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] artifact already built. Skipping."; + return; + } + + // skip artifacts without transformer + if (artifact->artifactType != Artifact::Generated) { + // For source artifacts, that were not reachable when initializing the build, we must + // retrieve timestamps. This can happen, if a dependency that's added during the build + // makes the source artifact reachable. + if (artifact->artifactType == Artifact::SourceFile && !artifact->timestampRetrieved) + retrieveSourceFileTimestamp(artifact); + + if (m_doDebug) + m_logger.qbsDebug() << QString::fromLocal8Bit("[EXEC] artifact type %1. Skipping.") + .arg(toString(artifact->artifactType)); + finishArtifact(artifact); + return; + } + + // Every generated artifact must have a transformer. + QBS_CHECK(artifact->transformer); + + // Skip if outputs of this transformer are already built. + // That means we already ran the transformation. + foreach (Artifact *sideBySideArtifact, artifact->transformer->outputs) { + if (sideBySideArtifact == artifact) + continue; + switch (sideBySideArtifact->buildState) + { + case Artifact::Untouched: + case Artifact::Buildable: + break; + case Artifact::Built: + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] Side by side artifact already finished. Skipping."; + finishArtifact(artifact); + return; + case Artifact::Building: + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] Side by side artifact processing. Skipping."; + artifact->buildState = Artifact::Building; + return; + } + } + + // Skip if we're building just one file and the file tags do not match. + if (!m_activeFileTags.isEmpty() && !m_activeFileTags.matches(artifact->fileTags)) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] file tags do not match. Skipping."; + finishArtifact(artifact); + return; + } + + // Skip transformers that do not need to be built. + if (!mustExecuteTransformer(artifact->transformer)) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] Up to date. Skipping."; + finishArtifact(artifact); + return; + } + + // create the output directories + if (!m_buildOptions.dryRun()) { + ArtifactList::const_iterator it = artifact->transformer->outputs.begin(); + for (; it != artifact->transformer->outputs.end(); ++it) { + Artifact *output = *it; + QDir outDir = QFileInfo(output->filePath()).absoluteDir(); + if (!outDir.exists()) + outDir.mkpath("."); + } + } + + // scan all input artifacts + InputArtifactScanner scanner(artifact, m_inputArtifactScanContext, m_logger); + scanner.scan(); + + // postpone the build of this artifact, if new dependencies found + if (scanner.newDependencyAdded()) { + bool buildingDependenciesFound = false; + QVector<Artifact *> unbuiltDependencies; + foreach (Artifact *dependency, artifact->children) { + switch (dependency->buildState) { + case Artifact::Untouched: + case Artifact::Buildable: + unbuiltDependencies += dependency; + break; + case Artifact::Building: + buildingDependenciesFound = true; + break; + case Artifact::Built: + // do nothing + break; + } + } + if (!unbuiltDependencies.isEmpty()) { + artifact->inputsScanned = false; + insertLeavesAfterAddingDependencies(unbuiltDependencies); + return; + } + if (buildingDependenciesFound) { + artifact->inputsScanned = false; + return; + } + } + + ExecutorJob *job = m_availableJobs.takeFirst(); + artifact->buildState = Artifact::Building; + m_processingJobs.insert(job, artifact); + + Q_ASSERT_X(artifact->product, Q_FUNC_INFO, + qPrintable(QString("Generated artifact '%1' belongs to no product.") + .arg(QDir::toNativeSeparators(artifact->filePath())))); + + job->run(artifact->transformer.data(), artifact->product); +} + +void Executor::finishJob(ExecutorJob *job, bool success) +{ + QBS_CHECK(job); + QBS_CHECK(m_state != ExecutorIdle); + + const QHash<ExecutorJob *, Artifact *>::Iterator it = m_processingJobs.find(job); + QBS_CHECK(it != m_processingJobs.end()); + if (success) + finishArtifact(it.value()); + m_processingJobs.erase(it); + m_availableJobs.append(job); + + if (!success && !m_buildOptions.keepGoing()) + cancelJobs(); + + if (m_state == ExecutorRunning && m_progressObserver && m_progressObserver->canceled()) { + m_logger.qbsTrace() << "Received cancel request; canceling build."; + m_explicitlyCanceled = true; + cancelJobs(); + } + + if (m_state == ExecutorCanceling) { + if (m_processingJobs.isEmpty()) { + m_logger.qbsTrace() << "All pending jobs are done, finishing."; + finish(); + } + return; + } + + if (!scheduleJobs()) { + m_logger.qbsTrace() << "Nothing left to build; finishing."; + finish(); + } +} + +static bool allChildrenBuilt(Artifact *artifact) +{ + foreach (Artifact *child, artifact->children) + if (child->buildState != Artifact::Built) + return false; + return true; +} + +void Executor::finishArtifact(Artifact *leaf) +{ + QBS_CHECK(leaf); + + if (m_doTrace) + m_logger.qbsTrace() << "[EXEC] finishArtifact " << relativeArtifactFileName(leaf); + + leaf->buildState = Artifact::Built; + m_scanResultCache.remove(leaf->filePath()); + foreach (Artifact *parent, leaf->parents) { + if (parent->buildState != Artifact::Buildable) { + if (m_doTrace) { + m_logger.qbsTrace() << "[EXEC] parent " << relativeArtifactFileName(parent) + << " build state: " << toString(parent->buildState); + } + continue; + } + + if (allChildrenBuilt(parent)) { + m_leaves.push(parent); + if (m_doTrace) { + m_logger.qbsTrace() << "[EXEC] finishArtifact adds leaf " + << relativeArtifactFileName(parent) << " " << toString(parent->buildState); + } + } else { + if (m_doTrace) { + m_logger.qbsTrace() << "[EXEC] parent " << relativeArtifactFileName(parent) + << " build state: " << toString(parent->buildState); + } + } + } + + if (leaf->transformer) + foreach (Artifact *sideBySideArtifact, leaf->transformer->outputs) + if (leaf != sideBySideArtifact && sideBySideArtifact->buildState == Artifact::Building) + finishArtifact(sideBySideArtifact); + + if (m_progressObserver && leaf->artifactType == Artifact::Generated) + m_progressObserver->incrementProgressValue(BuildEffortCalculator::multiplier(leaf)); +} + +void Executor::insertLeavesAfterAddingDependencies_recurse(Artifact *const artifact, + QSet<Artifact *> *seenArtifacts, Leaves *leaves) const +{ + if (seenArtifacts->contains(artifact)) + return; + seenArtifacts->insert(artifact); + + if (artifact->buildState == Artifact::Untouched) + artifact->buildState = Artifact::Buildable; + + bool isLeaf = true; + foreach (Artifact *child, artifact->children) { + if (child->buildState != Artifact::Built) { + isLeaf = false; + insertLeavesAfterAddingDependencies_recurse(child, seenArtifacts, leaves); + } + } + + if (isLeaf) { + if (m_doDebug) + m_logger.qbsDebug() << "[EXEC] adding leaf " << relativeArtifactFileName(artifact); + leaves->push(artifact); + } +} + +QString Executor::configString() const +{ + return tr(" for configuration %1").arg(m_project->id()); +} + +void Executor::insertLeavesAfterAddingDependencies(QVector<Artifact *> dependencies) +{ + QSet<Artifact *> seenArtifacts; + foreach (Artifact *dependency, dependencies) + insertLeavesAfterAddingDependencies_recurse(dependency, &seenArtifacts, &m_leaves); +} + +void Executor::cancelJobs() +{ + m_logger.qbsTrace() << "Canceling all jobs."; + setState(ExecutorCanceling); + QList<ExecutorJob *> jobs = m_processingJobs.keys(); + foreach (ExecutorJob *job, jobs) + job->cancel(); +} + +void Executor::setupProgressObserver(bool mocWillRun) +{ + if (!m_progressObserver) + return; + MocEffortCalculator mocEffortCalculator; + BuildEffortCalculator buildEffortCalculator; + foreach (const ResolvedProductConstPtr &product, m_productsToBuild) + buildEffortCalculator.visitProduct(product); + if (mocWillRun) { + foreach (const ResolvedProductConstPtr &product, m_productsToBuild) + mocEffortCalculator.visitProduct(product); + } + m_mocEffort = mocEffortCalculator.effort(); + const int totalEffort = m_mocEffort + buildEffortCalculator.effort(); + m_progressObserver->initialize(tr("Building%1").arg(configString()), totalEffort); +} + +void Executor::doSanityChecks() +{ + QBS_CHECK(m_project); + QBS_CHECK(!m_productsToBuild.isEmpty()); + foreach (const ResolvedProductConstPtr &product, m_productsToBuild) { + QBS_CHECK(product->buildData); + QBS_CHECK(product->topLevelProject() == m_project); + } +} + +void Executor::handleError(const ErrorInfo &error) +{ + m_error = error; + if (m_processingJobs.isEmpty()) + finish(); + else + cancelJobs(); +} + +void Executor::addExecutorJobs(int jobNumber) +{ + for (int i = 1; i <= jobNumber; i++) { + ExecutorJob *job = new ExecutorJob(m_logger, this); + job->setMainThreadScriptEngine(m_evalContext->engine()); + job->setObjectName(QString(QLatin1String("J%1")).arg(i)); + m_availableJobs.append(job); + connect(job, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString)), Qt::QueuedConnection); + connect(job, SIGNAL(reportProcessResult(qbs::ProcessResult)), + this, SIGNAL(reportProcessResult(qbs::ProcessResult)), Qt::QueuedConnection); + connect(job, SIGNAL(error(qbs::ErrorInfo)), + this, SLOT(onProcessError(qbs::ErrorInfo)), Qt::QueuedConnection); + connect(job, SIGNAL(success()), this, SLOT(onProcessSuccess()), Qt::QueuedConnection); + } +} + +void Executor::runAutoMoc() +{ + bool autoMocApplied = false; + foreach (const ResolvedProductPtr &product, m_productsToBuild) { + if (m_progressObserver && m_progressObserver->canceled()) + throw ErrorInfo(Tr::tr("Build canceled%1.").arg(configString())); + // HACK call the automoc thingy here only if we have use Qt/core module + foreach (const ResolvedModuleConstPtr &m, product->modules) { + if (m->name == "Qt/core") { + autoMocApplied = true; + m_autoMoc->apply(product); + break; + } + } + } + if (autoMocApplied) { + foreach (const ResolvedProductConstPtr &product, m_productsToBuild) + CycleDetector(m_logger).visitProduct(product); + } + if (m_progressObserver) + m_progressObserver->incrementProgressValue(m_mocEffort); +} + +void Executor::onProcessError(const qbs::ErrorInfo &err) +{ + try { + if (m_buildOptions.keepGoing()) { + ErrorInfo fullWarning(err); + fullWarning.prepend(Tr::tr("Ignoring the following errors on user request:")); + m_logger.printWarning(fullWarning); + } else { + m_error = err; + } + ExecutorJob * const job = qobject_cast<ExecutorJob *>(sender()); + finishJob(job, false); + } catch (const ErrorInfo &error) { + handleError(error); + } +} + +void Executor::onProcessSuccess() +{ + try { + ExecutorJob *job = qobject_cast<ExecutorJob *>(sender()); + QBS_CHECK(job); + Artifact *processedArtifact = m_processingJobs.value(job); + QBS_CHECK(processedArtifact); + + // Update the timestamps of the outputs of the transformer we just executed. + processedArtifact->product->topLevelProject()->buildData->isDirty = true; + foreach (Artifact *artifact, processedArtifact->transformer->outputs) { + if (artifact->alwaysUpdated) + artifact->setTimestamp(FileTime::currentTime()); + else + artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified()); + } + + finishJob(job, true); + } catch (const ErrorInfo &error) { + handleError(error); + } +} + +void Executor::finish() +{ + QBS_ASSERT(m_state != ExecutorIdle, /* ignore */); + + QStringList unbuiltProductNames; + foreach (const ResolvedProductPtr &product, m_productsToBuild) { + foreach (Artifact *artifact, product->buildData->targetArtifacts) { + if (artifact->buildState != Artifact::Built) { + unbuiltProductNames += product->name; + break; + } + } + } + + if (unbuiltProductNames.isEmpty()) { + m_logger.qbsInfo() << Tr::tr("Build done%1.").arg(configString()); + } else { + m_error.append(Tr::tr("The following products could not be built%1: %2.") + .arg(configString(), unbuiltProductNames.join(", "))); + } + + if (m_explicitlyCanceled) + m_error.append(Tr::tr("Build canceled%1.").arg(configString())); + setState(ExecutorIdle); + if (m_progressObserver) + m_progressObserver->setFinished(); + emit finished(); +} + +/** + * Sets the state of all artifacts in the graph to "untouched". + * This must be done before doing a build. + * + * Retrieves the timestamps of source artifacts. + * + * This function sets *sourceFilesChanged to true, if the timestamp of a reachable source artifact + * changed. + */ +void Executor::prepareAllArtifacts(bool *sourceFilesChanged) +{ + foreach (const ResolvedProductPtr &product, m_productsToBuild) { + foreach (Artifact *artifact, product->buildData->artifacts) { + artifact->buildState = Artifact::Untouched; + artifact->inputsScanned = false; + artifact->timestampRetrieved = false; + + if (artifact->artifactType == Artifact::SourceFile) { + const FileTime oldTimestamp = artifact->timestamp(); + retrieveSourceFileTimestamp(artifact); + if (oldTimestamp != artifact->timestamp()) + *sourceFilesChanged = true; + } + + // Timestamps of file dependencies must be invalid for every build. + foreach (FileDependency *fileDependency, artifact->fileDependencies) + fileDependency->clearTimestamp(); + } + } +} + +/** + * Walk the build graph top-down from the roots and for each reachable node N + * - mark N as buildable. + */ +void Executor::prepareReachableArtifacts(const Artifact::BuildState buildState) +{ + foreach (Artifact *root, m_roots) + prepareReachableArtifacts_impl(root, buildState); +} + +void Executor::prepareReachableArtifacts_impl(Artifact *artifact, + const Artifact::BuildState buildState) +{ + if (artifact->buildState != Artifact::Untouched) + return; + + artifact->buildState = buildState; + foreach (Artifact *child, artifact->children) + prepareReachableArtifacts_impl(child, buildState); +} + +void Executor::updateBuildGraph(Artifact::BuildState buildState) +{ + QSet<Artifact *> seenArtifacts; + foreach (Artifact *root, m_roots) + updateBuildGraph_impl(root, buildState, seenArtifacts); +} + +void Executor::updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts) +{ + if (seenArtifacts.contains(artifact)) + return; + + seenArtifacts += artifact; + artifact->buildState = buildState; + + foreach (Artifact *child, artifact->children) + updateBuildGraph_impl(child, buildState, seenArtifacts); +} + +void Executor::setState(ExecutorState s) +{ + if (m_state == s) + return; + m_state = s; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/executor.h b/src/lib/corelib/buildgraph/executor.h new file mode 100644 index 000000000..ece3b39a3 --- /dev/null +++ b/src/lib/corelib/buildgraph/executor.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** 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_BUILDGRAPHEXECUTOR_H +#define QBS_BUILDGRAPHEXECUTOR_H + +#include "forward_decls.h" +#include <buildgraph/artifact.h> +#include <buildgraph/scanresultcache.h> +#include <language/forward_decls.h> + +#include <logging/logger.h> +#include <tools/buildoptions.h> +#include <tools/error.h> + +#include <QObject> +#include <queue> + +namespace qbs { +class ProcessResult; + +namespace Internal { +class AutoMoc; +class ExecutorJob; +class FileTime; +class InputArtifactScannerContext; +class ProgressObserver; + +class Executor : public QObject +{ + Q_OBJECT + +public slots: + void build(); + +public: + Executor(const Logger &logger, QObject *parent = 0); + ~Executor(); + + void setProject(const TopLevelProjectPtr &project); + void setProducts(const QList<ResolvedProductPtr> &productsToBuild); + void setBuildOptions(const BuildOptions &buildOptions); + void setProgressObserver(ProgressObserver *observer) { m_progressObserver = observer; } + + ErrorInfo error() const { return m_error; } + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + + void finished(); + +private slots: + void onProcessError(const qbs::ErrorInfo &err); + void onProcessSuccess(); + void finish(); + +private: + enum ExecutorState { ExecutorIdle, ExecutorRunning, ExecutorCanceling }; + + struct ComparePriority + { + bool operator() (const Artifact *x, const Artifact *y) const; + }; + + typedef std::priority_queue<Artifact *, std::vector<Artifact *>, ComparePriority> Leaves; + + void doBuild(); + void prepareAllArtifacts(bool *sourceFilesChanged); + void prepareReachableArtifacts(const Artifact::BuildState buildState); + void prepareReachableArtifacts_impl(Artifact *artifact, const Artifact::BuildState buildState); + void updateBuildGraph(Artifact::BuildState buildState); + void updateBuildGraph_impl(Artifact *artifact, Artifact::BuildState buildState, QSet<Artifact *> &seenArtifacts); + void initLeaves(const QList<Artifact *> &changedArtifacts); + void initLeavesTopDown(Artifact *artifact, QSet<Artifact *> &seenArtifacts); + bool scheduleJobs(); + void buildArtifact(Artifact *artifact); + void finishJob(ExecutorJob *job, bool success); + void finishArtifact(Artifact *artifact); + void setState(ExecutorState); + void addExecutorJobs(int jobNumber); + void runAutoMoc(); + void insertLeavesAfterAddingDependencies(QVector<Artifact *> dependencies); + void cancelJobs(); + void setupProgressObserver(bool mocWillRun); + void doSanityChecks(); + void handleError(const ErrorInfo &error); + + bool mustExecuteTransformer(const TransformerPtr &transformer) const; + bool isUpToDate(Artifact *artifact) const; + void retrieveSourceFileTimestamp(Artifact *artifact) const; + FileTime recursiveFileTime(const QString &filePath) const; + void insertLeavesAfterAddingDependencies_recurse(Artifact *const artifact, + QSet<Artifact *> *seenArtifacts, Leaves *leaves) const; + QString configString() const; + + RulesEvaluationContextPtr m_evalContext; + BuildOptions m_buildOptions; + Logger m_logger; + ProgressObserver *m_progressObserver; + QList<ExecutorJob*> m_availableJobs; + QHash<ExecutorJob*, Artifact *> m_processingJobs; + ExecutorState m_state; + TopLevelProjectPtr m_project; + QList<ResolvedProductPtr> m_productsToBuild; + QList<Artifact *> m_roots; + Leaves m_leaves; + ScanResultCache m_scanResultCache; + InputArtifactScannerContext *m_inputArtifactScanContext; + AutoMoc *m_autoMoc; + int m_mocEffort; + ErrorInfo m_error; + bool m_explicitlyCanceled; + FileTags m_activeFileTags; + const bool m_doTrace; + const bool m_doDebug; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/executorjob.cpp b/src/lib/corelib/buildgraph/executorjob.cpp new file mode 100644 index 000000000..3ad3df6d5 --- /dev/null +++ b/src/lib/corelib/buildgraph/executorjob.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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 "executorjob.h" + +#include "command.h" +#include "jscommandexecutor.h" +#include "processcommandexecutor.h" +#include "transformer.h" +#include <language/language.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +#include <QThread> + +namespace qbs { +namespace Internal { + +ExecutorJob::ExecutorJob(const Logger &logger, QObject *parent) + : QObject(parent) + , m_processCommandExecutor(new ProcessCommandExecutor(logger, this)) + , m_jsCommandExecutor(new JsCommandExecutor(logger, this)) +{ + connect(m_processCommandExecutor, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString))); + connect(m_processCommandExecutor, SIGNAL(reportProcessResult(qbs::ProcessResult)), + this, SIGNAL(reportProcessResult(qbs::ProcessResult))); + connect(m_processCommandExecutor, SIGNAL(error(qbs::ErrorInfo)), + this, SLOT(onCommandError(qbs::ErrorInfo))); + connect(m_processCommandExecutor, SIGNAL(finished()), SLOT(onCommandFinished())); + connect(m_jsCommandExecutor, SIGNAL(reportCommandDescription(QString,QString)), + this, SIGNAL(reportCommandDescription(QString,QString))); + connect(m_jsCommandExecutor, SIGNAL(error(qbs::ErrorInfo)), + this, SLOT(onCommandError(qbs::ErrorInfo))); + connect(m_jsCommandExecutor, SIGNAL(finished()), SLOT(onCommandFinished())); + setInactive(); +} + +ExecutorJob::~ExecutorJob() +{ +} + +void ExecutorJob::setMainThreadScriptEngine(ScriptEngine *engine) +{ + m_processCommandExecutor->setMainThreadScriptEngine(engine); + m_jsCommandExecutor->setMainThreadScriptEngine(engine); +} + +void ExecutorJob::setDryRun(bool enabled) +{ + m_processCommandExecutor->setDryRunEnabled(enabled); + m_jsCommandExecutor->setDryRunEnabled(enabled); +} + +void ExecutorJob::run(Transformer *t, const ResolvedProductPtr &product) +{ + QBS_ASSERT(m_currentCommandIdx == -1, return); + + if (t->commands.isEmpty()) { + emit success(); + return; + } + + t->propertiesRequestedInCommands.clear(); + m_processCommandExecutor->setProcessEnvironment(product->buildEnvironment); + + m_transformer = t; + runNextCommand(); +} + +void ExecutorJob::cancel() +{ + if (!m_transformer) + return; + m_currentCommandIdx = m_transformer->commands.count(); +} + +void ExecutorJob::waitForFinished() +{ + if (m_currentCommandExecutor) + m_currentCommandExecutor->waitForFinished(); +} + +void ExecutorJob::runNextCommand() +{ + QBS_ASSERT(m_currentCommandIdx <= m_transformer->commands.count(), return); + ++m_currentCommandIdx; + if (m_currentCommandIdx >= m_transformer->commands.count()) { + setInactive(); + emit success(); + return; + } + + const AbstractCommand * const command = m_transformer->commands.at(m_currentCommandIdx); + switch (command->type()) { + case AbstractCommand::ProcessCommandType: + m_currentCommandExecutor = m_processCommandExecutor; + break; + case AbstractCommand::JavaScriptCommandType: + m_currentCommandExecutor = m_jsCommandExecutor; + break; + default: + qFatal("Missing implementation for command type %d", command->type()); + } + + m_currentCommandExecutor->start(m_transformer, command); +} + +void ExecutorJob::onCommandError(const ErrorInfo &err) +{ + setInactive(); + emit error(err); +} + +void ExecutorJob::onCommandFinished() +{ + if (!m_transformer) + return; + runNextCommand(); +} + +void ExecutorJob::setInactive() +{ + m_transformer = 0; + m_currentCommandExecutor = 0; + m_currentCommandIdx = -1; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/executorjob.h b/src/lib/corelib/buildgraph/executorjob.h new file mode 100644 index 000000000..fcd06cc6d --- /dev/null +++ b/src/lib/corelib/buildgraph/executorjob.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_EXECUTORJOB_H +#define QBS_EXECUTORJOB_H + +#include <language/forward_decls.h> +#include <tools/error.h> + +#include <QObject> + +namespace qbs { +class CodeLocation; +class ProcessResult; + +namespace Internal { +class AbstractCommandExecutor; +class ProductBuildData; +class JsCommandExecutor; +class Logger; +class ProcessCommandExecutor; +class ScriptEngine; +class Transformer; + +class ExecutorJob : public QObject +{ + Q_OBJECT +public: + ExecutorJob(const Logger &logger, QObject *parent); + ~ExecutorJob(); + + void setMainThreadScriptEngine(ScriptEngine *engine); + void setDryRun(bool enabled); + void run(Transformer *t, const ResolvedProductPtr &product); + void cancel(); + void waitForFinished(); + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + void error(const qbs::ErrorInfo &error); + void success(); + +private slots: + void runNextCommand(); + void onCommandError(const qbs::ErrorInfo &err); + void onCommandFinished(); + +private: + void setInactive(); + + AbstractCommandExecutor *m_currentCommandExecutor; + ProcessCommandExecutor *m_processCommandExecutor; + JsCommandExecutor *m_jsCommandExecutor; + Transformer *m_transformer; + int m_currentCommandIdx; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EXECUTORJOB_H diff --git a/src/lib/corelib/buildgraph/filedependency.cpp b/src/lib/corelib/buildgraph/filedependency.cpp new file mode 100644 index 000000000..8341de050 --- /dev/null +++ b/src/lib/corelib/buildgraph/filedependency.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 "filedependency.h" + +#include <tools/fileinfo.h> +#include <tools/persistence.h> + +namespace qbs { +namespace Internal { + +FileResourceBase::FileResourceBase() +{ +} + +FileResourceBase::~FileResourceBase() +{ +} + +void FileResourceBase::setTimestamp(const FileTime &t) + +{ + m_timestamp = t; +} + +const FileTime &FileResourceBase::timestamp() const +{ + return m_timestamp; +} + +void FileResourceBase::setFilePath(const QString &filePath) +{ + m_filePath = filePath; + FileInfo::splitIntoDirectoryAndFileName(m_filePath, &m_dirPath, &m_fileName); +} + +const QString &FileResourceBase::filePath() const +{ + return m_filePath; +} + +void FileResourceBase::load(PersistentPool &pool) +{ + setFilePath(pool.idLoadString()); + pool.stream() + >> m_timestamp; +} + +void FileResourceBase::store(PersistentPool &pool) const +{ + pool.storeString(m_filePath); + pool.stream() + << m_timestamp; +} + + +FileDependency::FileDependency() +{ +} + +FileDependency::~FileDependency() +{ +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/filedependency.h b/src/lib/corelib/buildgraph/filedependency.h new file mode 100644 index 000000000..0379c0ec6 --- /dev/null +++ b/src/lib/corelib/buildgraph/filedependency.h @@ -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. +** +****************************************************************************/ + +#ifndef QBS_FILEDEPENDENCY_H +#define QBS_FILEDEPENDENCY_H + +#include <tools/filetime.h> +#include <tools/persistentobject.h> + +namespace qbs { +namespace Internal { + +class FileResourceBase : public virtual PersistentObject +{ +protected: + FileResourceBase(); + +public: + ~FileResourceBase(); + + void setTimestamp(const FileTime &t); + const FileTime ×tamp() const; + void clearTimestamp() { m_timestamp.clear(); } + + void setFilePath(const QString &filePath); + const QString &filePath() const; + QString dirPath() const { return m_dirPath.toString(); } + QString fileName() const { return m_fileName.toString(); } + +protected: + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + +private: + FileTime m_timestamp; + QString m_filePath; + QStringRef m_dirPath; + QStringRef m_fileName; +}; + +class FileDependency : public FileResourceBase +{ +public: + FileDependency(); + ~FileDependency(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILEDEPENDENCY_H diff --git a/src/lib/corelib/buildgraph/forward_decls.h b/src/lib/corelib/buildgraph/forward_decls.h new file mode 100644 index 000000000..7f3986a9b --- /dev/null +++ b/src/lib/corelib/buildgraph/forward_decls.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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_BG_FORWARD_DECLS_H +#define QBS_BG_FORWARD_DECLS_H + +#include <QSharedPointer> + +namespace qbs { +namespace Internal { + +class Artifact; +class ProjectBuildData; +class ProductBuildData; + +class Transformer; +typedef QSharedPointer<Transformer> TransformerPtr; +typedef QSharedPointer<const Transformer> TransformerConstPtr; + +class RulesEvaluationContext; +typedef QSharedPointer<RulesEvaluationContext> RulesEvaluationContextPtr; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BG_FORWARD_DECLS_H diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp new file mode 100644 index 000000000..d5a0fabb6 --- /dev/null +++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp @@ -0,0 +1,369 @@ +/**************************************************************************** +** +** 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 "inputartifactscanner.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "transformer.h" + +#include <language/language.h> +#include <tools/fileinfo.h> +#include <tools/scannerpluginmanager.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QSet> +#include <QStringList> +#include <QVariantMap> + +namespace qbs { +namespace Internal { + +InputArtifactScannerContext::InputArtifactScannerContext(ScanResultCache *scanResultCache) + : scanResultCache(scanResultCache) +{ +} + +InputArtifactScannerContext::~InputArtifactScannerContext() +{ +} + +static void collectIncludePaths(const QVariantMap &modules, QSet<QString> *collectedPaths) +{ + QMapIterator<QString, QVariant> iterator(modules); + while (iterator.hasNext()) { + iterator.next(); + if (iterator.key() == "cpp") { + QVariant includePathsVariant = iterator .value().toMap().value("includePaths"); + if (includePathsVariant.isValid()) + collectedPaths->unite(QSet<QString>::fromList(includePathsVariant.toStringList())); + } else { + collectIncludePaths(iterator.value().toMap().value("modules").toMap(), collectedPaths); + } + } +} + +static QStringList collectIncludePaths(const QVariantMap &modules) +{ + QSet<QString> collectedPaths; + + collectIncludePaths(modules, &collectedPaths); + return QStringList(collectedPaths.toList()); +} + +static void resolveWithIncludePath(const QString &includePath, + const ScanResultCache::Dependency &dependency, const ResolvedProduct *product, + ResolvedDependency *result) +{ + QString absDirPath = dependency.dirPath().isEmpty() ? includePath : FileInfo::resolvePath(includePath, dependency.dirPath()); + if (!dependency.isClean()) + absDirPath = QDir::cleanPath(absDirPath); + + ResolvedProject *project = product->project.data(); + FileDependency *fileDependencyArtifact = 0; + Artifact *dependencyInProduct = 0; + Artifact *dependencyInOtherProduct = 0; + foreach (FileResourceBase *lookupResult, project->topLevelProject() + ->buildData->lookupFiles(absDirPath, dependency.fileName())) { + if ((fileDependencyArtifact = dynamic_cast<FileDependency *>(lookupResult))) + continue; + Artifact * const foundArtifact = dynamic_cast<Artifact *>(lookupResult); + if (foundArtifact->product == product) + dependencyInProduct = foundArtifact; + else + dependencyInOtherProduct = foundArtifact; + } + + // prioritize found artifacts + if ((result->file = dependencyInProduct) + || (result->file = dependencyInOtherProduct) + || (result->file = fileDependencyArtifact)) + { + result->filePath = result->file->filePath(); + return; + } + + QString absFilePath = absDirPath + QLatin1Char('/') + dependency.fileName(); + if (FileInfo::exists(absFilePath)) + result->filePath = absFilePath; +} + +static bool scanWithScannerPlugin(ScannerPlugin *scannerPlugin, + const QString &filePathToBeScanned, + ScanResultCache::Result *scanResult) +{ + void *scannerHandle = scannerPlugin->open(filePathToBeScanned.utf16(), ScanForDependenciesFlag); + if (!scannerHandle) + return false; + while (true) { + int flags = 0; + int length = 0; + const char *szOutFilePath = scannerPlugin->next(scannerHandle, &length, &flags); + if (szOutFilePath == 0) + break; + QString outFilePath = QString::fromLocal8Bit(szOutFilePath, length); + if (outFilePath.isEmpty()) + continue; + bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG); + scanResult->deps += ScanResultCache::Dependency(outFilePath, isLocalInclude); + } + scannerPlugin->close(scannerHandle); + scanResult->valid = true; + return true; +} + + +InputArtifactScanner::InputArtifactScanner(Artifact *artifact, InputArtifactScannerContext *ctx, + const Logger &logger) + : m_artifact(artifact), m_context(ctx), m_newDependencyAdded(false), m_logger(logger) +{ +} + +void InputArtifactScanner::scan() +{ + if (m_artifact->inputsScanned) + return; + + m_artifact->inputsScanned = true; + + // clear file dependencies; they will be regenerated + m_artifact->fileDependencies.clear(); + + // Remove all connections to children that were added by the dependency scanner. + // They will be regenerated. + foreach (Artifact *dependency, m_artifact->childrenAddedByScanner) + disconnect(m_artifact, dependency, m_logger); + + ArtifactList::const_iterator it = m_artifact->transformer->inputs.begin(); + for (; it != m_artifact->transformer->inputs.end(); ++it) { + Artifact *inputArtifact = *it; + QStringList includePaths; + bool mustCollectIncludePaths = false; + + QSet<ScannerPlugin *> scanners; + foreach (const FileTag &fileTag, inputArtifact->fileTags) { + foreach (ScannerPlugin *scanner, ScannerPluginManager::scannersForFileTag(fileTag)) { + scanners += scanner; + if (scanner->flags & ScannerUsesCppIncludePaths) + mustCollectIncludePaths = true; + } + } + + InputArtifactScannerContext::CacheItem &cacheItem = m_context->cache[inputArtifact->properties]; + if (mustCollectIncludePaths) { + const bool cacheHit = cacheItem.valid; + if (cacheHit) { + includePaths = cacheItem.includePaths; + } else { + includePaths = collectIncludePaths(inputArtifact->properties->value().value("modules").toMap()); + cacheItem.includePaths = includePaths; + cacheItem.valid = true; + } + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() + << "[DEPSCAN] include paths (cache " << (cacheHit ? "hit)" : "miss)"); + foreach (const QString &s, includePaths) + m_logger.qbsTrace() << " " << s; + } + } + + const QStringList emptyIncludePaths; + foreach (ScannerPlugin *scanner, scanners) { + scanForFileDependencies(scanner, + (scanner->flags & ScannerUsesCppIncludePaths) + ? includePaths : emptyIncludePaths, + inputArtifact, + cacheItem.resolvedDependenciesCache[scanner]); + } + } +} + +void InputArtifactScanner::scanForFileDependencies(ScannerPlugin *scannerPlugin, + const QStringList &includePaths, Artifact *inputArtifact, InputArtifactScannerContext::ResolvedDependenciesCache &resolvedDependenciesCache) +{ + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << QString::fromLocal8Bit("scanning %1 [%2]\n from %3") + .arg(inputArtifact->filePath()).arg(scannerPlugin->fileTag) + .arg(m_artifact->filePath()); + } + + QSet<QString> visitedFilePaths; + QStringList filePathsToScan; + filePathsToScan.append(inputArtifact->filePath()); + QStringList * const filePathsToScanPtr = + (scannerPlugin->flags & ScannerRecursiveDependencies) ? &filePathsToScan : 0; + + while (!filePathsToScan.isEmpty()) { + const QString filePathToBeScanned = filePathsToScan.takeFirst(); + if (visitedFilePaths.contains(filePathToBeScanned)) + continue; + visitedFilePaths.insert(filePathToBeScanned); + + ScanResultCache::Result scanResult = m_context->scanResultCache->value(filePathToBeScanned); + if (!scanResult.valid) { + bool successfulScan = scanWithScannerPlugin(scannerPlugin, filePathToBeScanned, &scanResult); + if (!successfulScan) + continue; + m_context->scanResultCache->insert(filePathToBeScanned, scanResult); + } + + resolveScanResultDependencies(includePaths, inputArtifact, scanResult, filePathToBeScanned, + filePathsToScanPtr, resolvedDependenciesCache); + } +} + +void InputArtifactScanner::resolveScanResultDependencies(const QStringList &includePaths, + const Artifact *inputArtifact, const ScanResultCache::Result &scanResult, + const QString &filePathToBeScanned, QStringList *filePathsToScan, InputArtifactScannerContext::ResolvedDependenciesCache &resolvedDependenciesCache) +{ + QString baseDirOfInFilePath; + foreach (const ScanResultCache::Dependency &dependency, scanResult.deps) { + const QString &dependencyFilePath = dependency.filePath(); + ResolvedDependency pristineResolvedDependency; + ResolvedDependency *resolvedDependency = &pristineResolvedDependency; + InputArtifactScannerContext::ResolvedDependencyCacheItem *cachedResolvedDependencyItem = 0; + + if (FileInfo::isAbsolute(dependencyFilePath)) { + resolvedDependency->filePath = dependencyFilePath; + goto resolved; + } + + if (dependency.isLocal()) { + // try base directory of source file + if (baseDirOfInFilePath.isNull()) + baseDirOfInFilePath = FileInfo::path(filePathToBeScanned); + resolveWithIncludePath(baseDirOfInFilePath, dependency, inputArtifact->product.data(), + resolvedDependency); + if (resolvedDependency->isValid()) + goto resolved; + } + + cachedResolvedDependencyItem = &resolvedDependenciesCache[dependency.fileName()][dependency.dirPath()]; + resolvedDependency = &cachedResolvedDependencyItem->resolvedDependency; + if (cachedResolvedDependencyItem->valid) { +// qDebug() << "RESCACHE HIT" << dependency.filePath(); + if (resolvedDependency->filePath.isEmpty()) + goto unresolved; + goto resolved; + } +// qDebug() << "RESCACHE MISS"; + cachedResolvedDependencyItem->valid = true; + + // try include paths + foreach (const QString &includePath, includePaths) { + resolveWithIncludePath(includePath, dependency, inputArtifact->product.data(), + resolvedDependency); + if (resolvedDependency->isValid()) + goto resolved; + } + +unresolved: + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] unresolved '%1'") + .arg(dependencyFilePath); + } + continue; + +resolved: + // Do not scan artifacts that are being built. Otherwise we might read an incomplete + // file or conflict with the writing process. + if (filePathsToScan) { + Artifact *artifactDependency = dynamic_cast<Artifact *>(resolvedDependency->file); + if (!artifactDependency || artifactDependency->buildState != Artifact::Building) + filePathsToScan->append(resolvedDependency->filePath); + } + handleDependency(*resolvedDependency); + } +} + +void InputArtifactScanner::handleDependency(ResolvedDependency &dependency) +{ + const ResolvedProductPtr product = m_artifact->product; + bool insertIntoProduct = true; + QBS_CHECK(m_artifact->artifactType == Artifact::Generated); + QBS_CHECK(product); + + Artifact *artifactDependency = dynamic_cast<Artifact *>(dependency.file); + FileDependency *fileDependency + = artifactDependency ? 0 : dynamic_cast<FileDependency *>(dependency.file); + + if (!dependency.file) { + // The dependency is an existing file but does not exist in the build graph. + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] + '%1'") + .arg(dependency.filePath); + } + fileDependency = new FileDependency(); + dependency.file = fileDependency; + fileDependency->setFilePath(dependency.filePath); + product->topLevelProject()->buildData->insertFileDependency(fileDependency); + } else if (fileDependency) { + // The dependency exists in the project's list of file dependencies. + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] ok in deps '%1'") + .arg(dependency.filePath); + } + } else if (artifactDependency->product == product) { + // The dependency is in our product. + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] ok in product '%1'") + .arg(dependency.filePath); + } + insertIntoProduct = false; + } else { + // The dependency is in some other product. + ResolvedProduct * const otherProduct = artifactDependency->product; + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[DEPSCAN] found in product '%1': '%2'") + .arg(otherProduct->name, dependency.filePath); + } + insertIntoProduct = false; + } + + if (m_artifact == dependency.file) + return; + + if (fileDependency) { + m_artifact->fileDependencies.insert(fileDependency); + } else { + if (m_artifact->children.contains(artifactDependency)) + return; + if (insertIntoProduct && !product->buildData->artifacts.contains(artifactDependency)) + insertArtifact(product, artifactDependency, m_logger); + safeConnect(m_artifact, artifactDependency, m_logger); + m_artifact->childrenAddedByScanner += artifactDependency; + m_newDependencyAdded = true; + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.h b/src/lib/corelib/buildgraph/inputartifactscanner.h new file mode 100644 index 000000000..234d25d83 --- /dev/null +++ b/src/lib/corelib/buildgraph/inputartifactscanner.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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_INPUTARTIFACTSCANNER_H +#define QBS_INPUTARTIFACTSCANNER_H + +#include "scanresultcache.h" +#include <language/forward_decls.h> +#include <logging/logger.h> + +#include <QHash> +#include <QStringList> + +struct ScannerPlugin; + +namespace qbs { +namespace Internal { + +class Artifact; +class FileResourceBase; +class PropertyMapInternal; + +class ResolvedDependency +{ +public: + ResolvedDependency() + : file(0) + {} + + bool isValid() const { return !filePath.isNull(); } + + QString filePath; + FileResourceBase *file; +}; + +class InputArtifactScannerContext +{ +public: + InputArtifactScannerContext(ScanResultCache *scanResultCache); + ~InputArtifactScannerContext(); + +private: + ScanResultCache *scanResultCache; + + struct ResolvedDependencyCacheItem + { + ResolvedDependencyCacheItem() + : valid(false) + {} + + bool valid; + ResolvedDependency resolvedDependency; + }; + + typedef QHash<QString, QHash<QString, ResolvedDependencyCacheItem> > ResolvedDependenciesCache; + + struct CacheItem + { + CacheItem() + : valid(false) + {} + + bool valid; + QStringList includePaths; + QHash<ScannerPlugin *, ResolvedDependenciesCache> resolvedDependenciesCache; + }; + + QHash<PropertyMapConstPtr, CacheItem> cache; + + friend class InputArtifactScanner; +}; + +class InputArtifactScanner +{ +public: + InputArtifactScanner(Artifact *artifact, InputArtifactScannerContext *ctx, + const Logger &logger); + void scan(); + bool newDependencyAdded() const { return m_newDependencyAdded; } + +private: + void scanForFileDependencies(ScannerPlugin *scannerPlugin, const QStringList &includePaths, + Artifact *inputArtifact, InputArtifactScannerContext::ResolvedDependenciesCache &cacheItem); + void resolveScanResultDependencies(const QStringList &includePaths, + const Artifact *inputArtifact, const ScanResultCache::Result &scanResult, + const QString &filePathToBeScanned, QStringList *filePathsToScan, InputArtifactScannerContext::ResolvedDependenciesCache &cacheItem); + void handleDependency(ResolvedDependency &dependency); + + Artifact * const m_artifact; + InputArtifactScannerContext *const m_context; + bool m_newDependencyAdded; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_INPUTARTIFACTSCANNER_H diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp new file mode 100644 index 000000000..ef84da432 --- /dev/null +++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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 "jscommandexecutor.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "command.h" +#include "transformer.h" + +#include <language/language.h> +#include <language/preparescriptobserver.h> +#include <language/scriptengine.h> +#include <logging/logger.h> +#include <tools/codelocation.h> +#include <tools/error.h> + +#include <QEventLoop> +#include <QThread> +#include <QTimer> + +namespace qbs { +namespace Internal { + +struct JavaScriptCommandResult +{ + bool success; + QString errorMessage; + CodeLocation errorLocation; +}; + +class JsCommandExecutorThreadObject : public QObject +{ + Q_OBJECT +public: + JsCommandExecutorThreadObject(const Logger &logger) + : m_logger(logger) + , m_scriptEngine(0) + { + } + + const JavaScriptCommandResult &result() const + { + return m_result; + } + +signals: + void finished(); + +public slots: + void start(const JavaScriptCommand *cmd, Transformer *transformer) + { + m_result.success = true; + m_result.errorMessage.clear(); + ScriptEngine * const scriptEngine = provideScriptEngine(); + QScriptValue scope = scriptEngine->newObject(); + PrepareScriptObserver observer(scriptEngine); + setupScriptEngineForFile(scriptEngine, transformer->rule->script->fileContext, scope); + setupScriptEngineForProduct(scriptEngine, transformer->product(), transformer->rule, scope, + &observer); + transformer->setupInputs(scriptEngine, scope); + transformer->setupOutputs(scriptEngine, scope); + + for (QVariantMap::const_iterator it = cmd->properties().constBegin(); + it != cmd->properties().constEnd(); ++it) { + scope.setProperty(it.key(), scriptEngine->toScriptValue(it.value())); + } + + QScriptContext *ctx = scriptEngine->currentContext(); + ctx->pushScope(scope); + scriptEngine->evaluate(cmd->sourceCode()); + ctx->popScope(); + transformer->propertiesRequestedInCommands + += scriptEngine->propertiesRequestedInScript(); + scriptEngine->clearRequestedProperties(); + if (scriptEngine->hasUncaughtException()) { + m_result.success = false; + m_result.errorMessage = scriptEngine->uncaughtException().toString(); + // ### We don't know the line number of the command's sourceCode property assignment. + m_result.errorLocation = cmd->codeLocation(); + } + emit finished(); + } + +private: + ScriptEngine *provideScriptEngine() + { + if (!m_scriptEngine) + m_scriptEngine = new ScriptEngine(m_logger, this); + return m_scriptEngine; + } + + Logger m_logger; + ScriptEngine *m_scriptEngine; + JavaScriptCommandResult m_result; +}; + + +JsCommandExecutor::JsCommandExecutor(const Logger &logger, QObject *parent) + : AbstractCommandExecutor(logger, parent) + , m_thread(new QThread(this)) + , m_objectInThread(new JsCommandExecutorThreadObject(logger)) + , m_running(false) +{ + m_objectInThread->moveToThread(m_thread); + connect(m_objectInThread, SIGNAL(finished()), this, SLOT(onJavaScriptCommandFinished())); + connect(this, SIGNAL(startRequested(const JavaScriptCommand *, Transformer *)), + m_objectInThread, SLOT(start(const JavaScriptCommand *, Transformer *))); +} + +JsCommandExecutor::~JsCommandExecutor() +{ + waitForFinished(); + delete m_objectInThread; + m_thread->quit(); + m_thread->wait(); +} + +void JsCommandExecutor::waitForFinished() +{ + if (!m_running) + return; + QEventLoop loop; + loop.connect(m_objectInThread, SIGNAL(finished()), SLOT(quit())); + loop.exec(); +} + +void JsCommandExecutor::doStart() +{ + QBS_ASSERT(!m_running, return); + m_thread->start(); + + if (dryRun()) { + QTimer::singleShot(0, this, SIGNAL(finished())); // Don't call back on the caller. + return; + } + + m_running = true; + emit startRequested(jsCommand(), transformer()); +} + +void JsCommandExecutor::onJavaScriptCommandFinished() +{ + m_running = false; + const JavaScriptCommandResult &result = m_objectInThread->result(); + if (!result.success) { + logger().qbsDebug() << "JS context:\n" << jsCommand()->properties(); + logger().qbsDebug() << "JS code:\n" << jsCommand()->sourceCode(); + QString msg = "Error while executing JavaScriptCommand:\n"; + msg += result.errorMessage; + emit error(ErrorInfo(msg, result.errorLocation)); + } + emit finished(); +} + +const JavaScriptCommand *JsCommandExecutor::jsCommand() const +{ + return static_cast<const JavaScriptCommand *>(command()); +} + +} // namespace Internal +} // namespace qbs + +#include "jscommandexecutor.moc" diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.h b/src/lib/corelib/buildgraph/jscommandexecutor.h new file mode 100644 index 000000000..94a25e610 --- /dev/null +++ b/src/lib/corelib/buildgraph/jscommandexecutor.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_JSCOMMANDEXECUTOR_H +#define QBS_JSCOMMANDEXECUTOR_H + +#include "abstractcommandexecutor.h" + +#include <QString> + +namespace qbs { +class CodeLocation; + +namespace Internal { +class JavaScriptCommand; +class JsCommandExecutorThreadObject; + +class JsCommandExecutor : public AbstractCommandExecutor +{ + Q_OBJECT +public: + explicit JsCommandExecutor(const Logger &logger, QObject *parent = 0); + ~JsCommandExecutor(); + +signals: + void startRequested(const JavaScriptCommand *cmd, Transformer *transformer); + +private slots: + void onJavaScriptCommandFinished(); + +private: + void doStart(); + void waitForFinished(); + + const JavaScriptCommand *jsCommand() const; + + QThread *m_thread; + JsCommandExecutorThreadObject *m_objectInThread; + bool m_running; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_JSCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp new file mode 100644 index 000000000..8887cc99f --- /dev/null +++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** 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 "processcommandexecutor.h" + +#include "artifact.h" +#include "command.h" +#include "transformer.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/processresult.h> +#include <tools/processresult_p.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QScriptEngine> +#include <QScriptValue> +#include <QTemporaryFile> +#include <QTimer> + +namespace qbs { +namespace Internal { + +static QStringList populateExecutableSuffixes() +{ + QStringList result; + result << QString(); + if (HostOsInfo::isWindowsHost()) { + result << QLatin1String(".com") << QLatin1String(".exe") + << QLatin1String(".bat") << QLatin1String(".cmd"); + } + return result; +} + +const QStringList ProcessCommandExecutor::m_executableSuffixes = populateExecutableSuffixes(); + +ProcessCommandExecutor::ProcessCommandExecutor(const Logger &logger, QObject *parent) + : AbstractCommandExecutor(logger, parent) +{ + connect(&m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(onProcessError())); + connect(&m_process, SIGNAL(finished(int)), SLOT(onProcessFinished(int))); +} + +// returns an empty string or one that starts with a space! +static QString commandArgsToString(const QStringList &args) +{ + QString result; + QRegExp ws("\\s"); + foreach (const QString &arg, args) { + result += QLatin1Char(' '); + + if (arg.contains(ws) || arg.isEmpty()) + result += QLatin1Char('"') + arg + QLatin1Char('"'); + else + result += arg; + } + return result; +} + +void ProcessCommandExecutor::doStart() +{ + QBS_ASSERT(m_process.state() == QProcess::NotRunning, return); + + const ProcessCommand * const cmd = processCommand(); + QString program = cmd->program(); + if (FileInfo::isAbsolute(cmd->program())) { + if (HostOsInfo::isWindowsHost()) + program = findProcessCommandBySuffix(); + } else { + program = findProcessCommandInPath(); + } + + QProcessEnvironment env = m_buildEnvironment; + const QProcessEnvironment &additionalVariables = cmd->environment(); + foreach (const QString &key, additionalVariables.keys()) + env.insert(key, additionalVariables.value(key)); + m_process.setProcessEnvironment(env); + + QStringList arguments = cmd->arguments(); + QString argString = commandArgsToString(arguments); + + if (dryRun()) { + QTimer::singleShot(0, this, SIGNAL(finished())); // Don't call back on the caller. + return; + } + + // Automatically use response files, if the command line gets to long. + if (!cmd->responseFileUsagePrefix().isEmpty()) { + const int commandLineLength = program.length() + argString.length(); + if (cmd->responseFileThreshold() >= 0 && commandLineLength > cmd->responseFileThreshold()) { + if (logger().debugEnabled()) { + logger().qbsDebug() << QString::fromLocal8Bit("[EXEC] Using response file. " + "Threshold is %1. Command line length %2.") + .arg(cmd->responseFileThreshold()).arg(commandLineLength); + } + + // The QTemporaryFile keeps a handle on the file, even if closed. + // On Windows, some commands (e.g. msvc link.exe) won't accept that. + // We need to delete the file manually, later. + QTemporaryFile responseFile; + responseFile.setAutoRemove(false); + responseFile.setFileTemplate(QDir::tempPath() + "/qbsresp"); + if (!responseFile.open()) { + emit error(ErrorInfo(Tr::tr("Cannot create response file '%1'.") + .arg(responseFile.fileName()))); + return; + } + for (int i = 0; i < cmd->arguments().count(); ++i) { + responseFile.write(cmd->arguments().at(i).toLocal8Bit()); + responseFile.write("\n"); + } + responseFile.close(); + m_responseFileName = responseFile.fileName(); + arguments.clear(); + arguments += QDir::toNativeSeparators(cmd->responseFileUsagePrefix() + + responseFile.fileName()); + } + } + + logger().qbsDebug() << "[EXEC] Running external process; full command line is: " << program + << commandArgsToString(arguments); + logger().qbsTrace() << "[EXEC] Additional environment:" << additionalVariables.toStringList(); + m_process.setWorkingDirectory(cmd->workingDir()); + m_process.start(program, arguments); + + m_program = program; + m_arguments = arguments; +} + +void ProcessCommandExecutor::waitForFinished() +{ + m_process.waitForFinished(-1); +} + +QString ProcessCommandExecutor::filterProcessOutput(const QByteArray &_output, + const QString &filterFunctionSource) +{ + const QString output = QString::fromLocal8Bit(_output); + if (filterFunctionSource.isEmpty()) + return output; + + QScriptValue filterFunction = scriptEngine()->evaluate("var f = " + filterFunctionSource + "; f"); + if (!filterFunction.isFunction()) { + emit error(ErrorInfo(Tr::tr("Error in filter function: %1.\n%2") + .arg(filterFunctionSource, filterFunction.toString()))); + return output; + } + + QScriptValue outputArg = scriptEngine()->newArray(1); + outputArg.setProperty(0, scriptEngine()->toScriptValue(output)); + QScriptValue filteredOutput = filterFunction.call(scriptEngine()->undefinedValue(), outputArg); + if (scriptEngine()->hasErrorOrException(filteredOutput)) { + emit error(ErrorInfo(Tr::tr("Error when calling output filter function: %1") + .arg(filteredOutput.toString()))); + return output; + } + + return filteredOutput.toString(); +} + +void ProcessCommandExecutor::sendProcessOutput(bool success) +{ + ProcessResult result; + result.d->executableFilePath = m_program; + result.d->arguments = m_arguments; + result.d->workingDirectory = m_process.workingDirectory(); + if (result.workingDirectory().isEmpty()) + result.d->workingDirectory = QDir::currentPath(); + result.d->exitCode = m_process.exitCode(); + result.d->exitStatus = m_process.exitStatus(); + result.d->success = success; + + QString tmp = filterProcessOutput(m_process.readAllStandardOutput(), + processCommand()->stdoutFilterFunction()); + if (!tmp.isEmpty()) { + if (tmp.endsWith(QLatin1Char('\n'))) + tmp.chop(1); + result.d->stdOut = tmp.split(QLatin1Char('\n')); + } + tmp = filterProcessOutput(m_process.readAllStandardError(), + processCommand()->stderrFilterFunction()); + if (!tmp.isEmpty()) { + if (tmp.endsWith(QLatin1Char('\n'))) + tmp.chop(1); + result.d->stdErr = tmp.split(QLatin1Char('\n')); + } + + emit reportProcessResult(result); +} + +void ProcessCommandExecutor::onProcessError() +{ + sendProcessOutput(false); + removeResponseFile(); + QString errorMessage; + const QString binary = QDir::toNativeSeparators(processCommand()->program()); + switch (m_process.error()) { + case QProcess::FailedToStart: + errorMessage = Tr::tr("The process '%1' could not be started: %2"). + arg(binary, m_process.errorString()); + break; + case QProcess::Crashed: + errorMessage = Tr::tr("The process '%1' crashed.").arg(binary); + break; + case QProcess::Timedout: + errorMessage = Tr::tr("The process '%1' timed out.").arg(binary); + break; + case QProcess::ReadError: + errorMessage = Tr::tr("Error reading process output from '%1'.").arg(binary); + break; + case QProcess::WriteError: + errorMessage = Tr::tr("Error writing to process '%1'.").arg(binary); + break; + default: + errorMessage = Tr::tr("Unknown process error running '%1'.").arg(binary); + break; + } + emit error(ErrorInfo(errorMessage)); +} + +void ProcessCommandExecutor::onProcessFinished(int exitCode) +{ + removeResponseFile(); + const bool errorOccurred = quint32(exitCode) > quint32(processCommand()->maxExitCode()); + sendProcessOutput(!errorOccurred); + + if (Q_UNLIKELY(errorOccurred)) { + emit error(ErrorInfo(Tr::tr("Process failed with exit code %1.").arg(exitCode))); + return; + } + + + emit finished(); +} + +void ProcessCommandExecutor::removeResponseFile() +{ + if (m_responseFileName.isEmpty()) + return; + QFile::remove(m_responseFileName); + m_responseFileName.clear(); +} + +QString ProcessCommandExecutor::findProcessCommandInPath() +{ + const ResolvedProductPtr product = transformer()->product(); + const ProcessCommand * const cmd = processCommand(); + QString fullProgramPath = product->executablePathCache.value(cmd->program()); + if (!fullProgramPath.isEmpty()) + return fullProgramPath; + + fullProgramPath = cmd->program(); + if (logger().traceEnabled()) + logger().qbsTrace() << "[EXEC] looking for executable in PATH " << fullProgramPath; + const QProcessEnvironment &buildEnvironment = product->buildEnvironment; + QStringList pathEnv = buildEnvironment.value("PATH").split(HostOsInfo::pathListSeparator(), + QString::SkipEmptyParts); + if (HostOsInfo::isWindowsHost()) + pathEnv.prepend(QLatin1String(".")); + for (int i = 0; i < pathEnv.count(); ++i) { + QString directory = pathEnv.at(i); + if (directory == QLatin1String(".")) + directory = cmd->workingDir(); + if (!directory.isEmpty()) { + const QChar lastChar = directory.at(directory.count() - 1); + if (lastChar != QLatin1Char('/') && lastChar != QLatin1Char('\\')) + directory.append(QLatin1Char('/')); + } + if (findProcessCandidateCheck(directory, fullProgramPath, fullProgramPath)) + break; + } + product->executablePathCache.insert(cmd->program(), fullProgramPath); + return fullProgramPath; +} + +QString ProcessCommandExecutor::findProcessCommandBySuffix() +{ + const ResolvedProductPtr product = transformer()->product(); + const ProcessCommand * const cmd = processCommand(); + QString fullProgramPath = product->executablePathCache.value(cmd->program()); + if (!fullProgramPath.isEmpty()) + return fullProgramPath; + + fullProgramPath = cmd->program(); + if (logger().traceEnabled()) + logger().qbsTrace() << "[EXEC] looking for executable by suffix " << fullProgramPath; + const QString emptyDirectory; + findProcessCandidateCheck(emptyDirectory, fullProgramPath, fullProgramPath); + product->executablePathCache.insert(cmd->program(), fullProgramPath); + return fullProgramPath; +} + +bool ProcessCommandExecutor::findProcessCandidateCheck(const QString &directory, + const QString &program, QString &fullProgramPath) +{ + for (int i = 0; i < m_executableSuffixes.count(); ++i) { + QString candidate = directory + program + m_executableSuffixes.at(i); + if (logger().traceEnabled()) + logger().qbsTrace() << "[EXEC] candidate: " << candidate; + if (FileInfo::exists(candidate)) { + fullProgramPath = candidate; + return true; + } + } + return false; +} + +const ProcessCommand *ProcessCommandExecutor::processCommand() const +{ + return static_cast<const ProcessCommand *>(command()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.h b/src/lib/corelib/buildgraph/processcommandexecutor.h new file mode 100644 index 000000000..c90141303 --- /dev/null +++ b/src/lib/corelib/buildgraph/processcommandexecutor.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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_PROCESSCOMMANDEXECUTOR_H +#define QBS_PROCESSCOMMANDEXECUTOR_H + +#include "abstractcommandexecutor.h" + +#include <QProcess> +#include <QProcessEnvironment> +#include <QString> + +namespace qbs { +class ProcessResult; + +namespace Internal { +class ProcessCommand; + +class ProcessCommandExecutor : public AbstractCommandExecutor +{ + Q_OBJECT +public: + explicit ProcessCommandExecutor(const Internal::Logger &logger, QObject *parent = 0); + + void setProcessEnvironment(const QProcessEnvironment &processEnvironment) { + m_buildEnvironment = processEnvironment; + } + +signals: + void reportProcessResult(const qbs::ProcessResult &result); + +private slots: + void onProcessError(); + void onProcessFinished(int exitCode); + +private: + void doStart(); + void waitForFinished(); + + void startProcessCommand(); + QString filterProcessOutput(const QByteArray &output, const QString &filterFunctionSource); + void sendProcessOutput(bool success); + void removeResponseFile(); + QString findProcessCommandInPath(); + QString findProcessCommandBySuffix(); + bool findProcessCandidateCheck(const QString &directory, const QString &program, + QString &fullProgramPath); + const ProcessCommand *processCommand() const; + +private: + static const QStringList m_executableSuffixes; + QString m_program; + QStringList m_arguments; + + QProcess m_process; + QProcessEnvironment m_buildEnvironment; + QString m_responseFileName; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROCESSCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/productbuilddata.cpp b/src/lib/corelib/buildgraph/productbuilddata.cpp new file mode 100644 index 000000000..0b9f36ab1 --- /dev/null +++ b/src/lib/corelib/buildgraph/productbuilddata.cpp @@ -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. +** +****************************************************************************/ +#include "productbuilddata.h" + +#include "artifact.h" +#include "projectbuilddata.h" +#include <language/language.h> +#include <logging/logger.h> +#include <tools/error.h> +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +ProductBuildData::~ProductBuildData() +{ + qDeleteAll(artifacts); +} + +void ProductBuildData::load(PersistentPool &pool) +{ + pool.loadContainer(artifacts); + pool.loadContainer(targetArtifacts); +} + +void ProductBuildData::store(PersistentPool &pool) const +{ + pool.storeContainer(artifacts); + pool.storeContainer(targetArtifacts); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/productbuilddata.h b/src/lib/corelib/buildgraph/productbuilddata.h new file mode 100644 index 000000000..15c8cc78f --- /dev/null +++ b/src/lib/corelib/buildgraph/productbuilddata.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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_PRODUCTBUILDDATA_H +#define QBS_PRODUCTBUILDDATA_H + +#include "artifactlist.h" +#include <language/forward_decls.h> + +#include <tools/persistentobject.h> + +#include <QList> +#include <QSet> + +namespace qbs { +namespace Internal { +class Logger; + +class ProductBuildData : public PersistentObject +{ +public: + ~ProductBuildData(); + + QSet<Artifact *> targetArtifacts; + ArtifactList artifacts; + QList<RuleConstPtr> topSortedRules; + + // Do not store, initialized in executor. Higher prioritized artifacts are built first. + unsigned int buildPriority; + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PRODUCTBUILDDATA_H diff --git a/src/lib/corelib/buildgraph/productinstaller.cpp b/src/lib/corelib/buildgraph/productinstaller.cpp new file mode 100644 index 000000000..867ad1370 --- /dev/null +++ b/src/lib/corelib/buildgraph/productinstaller.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** 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 "productinstaller.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include <language/language.h> +#include <logging/translator.h> +#include <tools/qbsassert.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/propertyfinder.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QFileInfo> + +namespace qbs { +namespace Internal { + +ProductInstaller::ProductInstaller(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const InstallOptions &options, + ProgressObserver *observer, const Logger &logger) + : m_project(project), + m_products(products), + m_options(options), + m_observer(observer), + m_logger(logger) +{ + if (!m_options.installRoot().isEmpty()) { + QFileInfo installRootFileInfo(m_options.installRoot()); + QBS_ASSERT(installRootFileInfo.isAbsolute(), /* just complain */); + if (m_options.removeExistingInstallation()) { + const QString cfp = installRootFileInfo.canonicalFilePath(); + if (cfp == QFileInfo(QDir::rootPath()).canonicalFilePath()) + throw ErrorInfo(Tr::tr("Refusing to remove root directory.")); + if (cfp == QFileInfo(QDir::homePath()).canonicalFilePath()) + throw ErrorInfo(Tr::tr("Refusing to remove home directory.")); + } + return; + } + + if (m_options.installIntoSysroot()) { + if (m_options.removeExistingInstallation()) + throw ErrorInfo(Tr::tr("Refusing to remove sysroot.")); + } + initInstallRoot(project.data(), m_options); +} + +void ProductInstaller::install() +{ + if (m_options.removeExistingInstallation()) + removeInstallRoot(); + + QList<const Artifact *> artifactsToInstall; + foreach (const ResolvedProductConstPtr &product, m_products) { + QBS_CHECK(product->buildData); + foreach (const Artifact *artifact, product->buildData->artifacts) { + if (artifact->properties->qbsPropertyValue(QLatin1String("install")).toBool()) + artifactsToInstall += artifact; + } + } + m_observer->initialize(Tr::tr("Installing"), artifactsToInstall.count()); + + foreach (const Artifact * const a, artifactsToInstall) { + copyFile(a); + m_observer->incrementProgressValue(); + } +} + +QString ProductInstaller::targetFilePath(const TopLevelProject *project, + const QString &sourceFilePath, const PropertyMapConstPtr &properties, + InstallOptions &options, QString *targetDirectory) +{ + if (!properties->qbsPropertyValue(QLatin1String("install")).toBool()) + return QString(); + const QString relativeInstallDir + = properties->qbsPropertyValue(QLatin1String("installDir")).toString(); + const QString installPrefix + = properties->qbsPropertyValue(QLatin1String("installPrefix")).toString(); + initInstallRoot(project, options); + QString targetDir = options.installRoot(); + targetDir.append(QLatin1Char('/')).append(installPrefix) + .append(QLatin1Char('/')).append(relativeInstallDir); + targetDir = QDir::cleanPath(targetDir); + const QString targetFilePath = QDir::cleanPath(targetDir + QLatin1Char('/') + + FileInfo::fileName(sourceFilePath)); + if (targetDirectory) + *targetDirectory = targetDir; + return targetFilePath; +} + +void ProductInstaller::initInstallRoot(const TopLevelProject *project, + InstallOptions &options) +{ + if (!options.installRoot().isEmpty()) + return; + + if (options.installIntoSysroot()) { + options.setInstallRoot(PropertyFinder().propertyValue(project->buildConfiguration(), + QLatin1String("qbs"), QLatin1String("sysroot")).toString()); + } else { + options.setInstallRoot(project->buildDirectory + QLatin1Char('/') + + InstallOptions::defaultInstallRoot()); + } +} + +void ProductInstaller::removeInstallRoot() +{ + const QString nativeInstallRoot = QDir::toNativeSeparators(m_options.installRoot()); + if (m_options.dryRun()) { + m_logger.qbsInfo() << Tr::tr("Would remove install root '%1'.").arg(nativeInstallRoot); + return; + } + m_logger.qbsDebug() << QString::fromLocal8Bit("Removing install root '%1'.") + .arg(nativeInstallRoot); + + QString errorMessage; + if (!removeDirectoryWithContents(m_options.installRoot(), &errorMessage)) { + const QString fullErrorMessage = Tr::tr("Cannot remove install root '%1': %2") + .arg(QDir::toNativeSeparators(m_options.installRoot()), errorMessage); + handleError(fullErrorMessage); + } +} + +void ProductInstaller::copyFile(const Artifact *artifact) +{ + if (m_observer->canceled()) { + throw ErrorInfo(Tr::tr("Installation canceled for configuration '%1'.") + .arg(m_products.first()->project->topLevelProject()->id())); + } + QString targetDir; + const QString targetFilePath = this->targetFilePath(m_project.data(), artifact->filePath(), + artifact->properties, m_options, &targetDir); + const QString nativeFilePath = QDir::toNativeSeparators(artifact->filePath()); + const QString nativeTargetDir = QDir::toNativeSeparators(targetDir); + if (m_options.dryRun()) { + m_logger.qbsInfo() << Tr::tr("Would copy file '%1' into target directory '%2'.") + .arg(nativeFilePath, nativeTargetDir); + return; + } + m_logger.qbsDebug() << QString::fromLocal8Bit("Copying file '%1' into target directory '%2'.") + .arg(nativeFilePath, nativeTargetDir); + + if (!QDir::root().mkpath(targetDir)) { + handleError(Tr::tr("Directory '%1' could not be created.").arg(nativeTargetDir)); + return; + } + QString errorMessage; + if (!copyFileRecursion(artifact->filePath(), targetFilePath, true, &errorMessage)) + handleError(Tr::tr("Installation error: %1").arg(errorMessage)); +} + +void ProductInstaller::handleError(const QString &message) +{ + if (!m_options.keepGoing()) + throw ErrorInfo(message); + m_logger.qbsWarning() << message; +} + +} // namespace Intern +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/productinstaller.h b/src/lib/corelib/buildgraph/productinstaller.h new file mode 100644 index 000000000..3f970b6aa --- /dev/null +++ b/src/lib/corelib/buildgraph/productinstaller.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_PRODUCT_INSTALLER_H +#define QBS_PRODUCT_INSTALLER_H + +#include "forward_decls.h" + +#include <language/forward_decls.h> +#include <logging/logger.h> +#include <tools/installoptions.h> + +#include <QList> + +namespace qbs { +namespace Internal { +class ProgressObserver; + +class ProductInstaller +{ +public: + ProductInstaller(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + const InstallOptions &options, ProgressObserver *observer, const Logger &logger); + void install(); + + static QString targetFilePath(const TopLevelProject *project, + const QString &sourceFilePath, const PropertyMapConstPtr &properties, + InstallOptions &options, QString *targetDirectory = 0); + static void initInstallRoot(const TopLevelProject *project, InstallOptions &options); + +private: + void removeInstallRoot(); + void copyFile(const Artifact *artifact); + void handleError(const QString &message); + + const TopLevelProjectConstPtr m_project; + const QList<ResolvedProductPtr> m_products; + InstallOptions m_options; + ProgressObserver * const m_observer; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Header guard diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp new file mode 100644 index 000000000..1ba53abf2 --- /dev/null +++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** 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 "projectbuilddata.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "productbuilddata.h" +#include "command.h" +#include "rulesapplicator.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" +#include <language/language.h> +#include <language/preparescriptobserver.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +ProjectBuildData::ProjectBuildData(const ProjectBuildData *other) + : isDirty(true), m_doCleanupInDestructor(true) +{ + // This is needed for temporary duplication of build data when doing change tracking. + if (other) { + *this = *other; + m_doCleanupInDestructor = false; + } +} + +ProjectBuildData::~ProjectBuildData() +{ + if (!m_doCleanupInDestructor) + return; + qDeleteAll(fileDependencies); +} + +QString ProjectBuildData::deriveBuildGraphFilePath(const QString &buildDir, const QString &projectId) +{ + return buildDir + QLatin1Char('/') + projectId + QLatin1String(".bg"); +} + +void ProjectBuildData::insertIntoLookupTable(FileResourceBase *fileres) +{ + QList<FileResourceBase *> &lst + = m_artifactLookupTable[fileres->fileName()][fileres->dirPath()]; + if (!lst.contains(fileres)) + lst.append(fileres); +} + +void ProjectBuildData::removeFromLookupTable(FileResourceBase *fileres) +{ + m_artifactLookupTable[fileres->fileName()][fileres->dirPath()].removeOne(fileres); +} + +QList<FileResourceBase *> ProjectBuildData::lookupFiles(const QString &filePath) const +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupFiles(dirPath, fileName); +} + +QList<FileResourceBase *> ProjectBuildData::lookupFiles(const QString &dirPath, + const QString &fileName) const +{ + return m_artifactLookupTable.value(fileName).value(dirPath); +} + +QList<FileResourceBase *> ProjectBuildData::lookupFiles(const Artifact *artifact) const +{ + return lookupFiles(artifact->dirPath(), artifact->fileName()); +} + +void ProjectBuildData::insertFileDependency(FileDependency *dependency) +{ + fileDependencies += dependency; + insertIntoLookupTable(dependency); +} + +static void disconnectArtifactChildren(Artifact *artifact, const Logger &logger) +{ + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnectChildren: '%1'") + .arg(relativeArtifactFileName(artifact)); + } + foreach (Artifact * const child, artifact->children) + child->parents.remove(artifact); + artifact->children.clear(); + artifact->childrenAddedByScanner.clear(); +} + +static void disconnectArtifactParents(Artifact *artifact, ProjectBuildData *projectBuildData, + const Logger &logger) +{ + if (logger.traceEnabled()) { + logger.qbsTrace() << QString::fromLocal8Bit("[BG] disconnectParents: '%1'") + .arg(relativeArtifactFileName(artifact)); + } + foreach (Artifact * const parent, artifact->parents) { + parent->children.remove(artifact); + parent->childrenAddedByScanner.remove(artifact); + if (parent->transformer) { + parent->transformer->inputs.remove(artifact); + projectBuildData->artifactsThatMustGetNewTransformers += parent; + } + } + + artifact->parents.clear(); +} + +static void disconnectArtifact(Artifact *artifact, ProjectBuildData *projectBuildData, + const Logger &logger) +{ + disconnectArtifactChildren(artifact, logger); + disconnectArtifactParents(artifact, projectBuildData, logger); +} + +/*! + * Removes the artifact and all the artifacts that depend exclusively on it. + * Example: if you remove a cpp artifact then the obj artifact is removed but + * not the resulting application (if there's more then one cpp artifact). + */ +void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact, + const Logger &logger, bool removeFromProduct, + ArtifactList *removedArtifacts) +{ + if (removedArtifacts) + removedArtifacts->insert(artifact); + foreach (Artifact *parent, artifact->parents) { + bool removeParent = false; + disconnect(parent, artifact, logger); + if (parent->children.isEmpty()) { + removeParent = true; + } else if (parent->transformer) { + artifactsThatMustGetNewTransformers += parent; + parent->transformer->inputs.remove(artifact); + removeParent = parent->transformer->inputs.isEmpty(); + } + if (removeParent) { + removeArtifactAndExclusiveDependents(parent, logger, removeFromProduct, + removedArtifacts); + } + } + const bool removeFromDisk = artifact->artifactType == Artifact::Generated; + removeArtifact(artifact, logger, removeFromDisk, removeFromProduct); +} + +void ProjectBuildData::removeArtifact(Artifact *artifact, + const Logger &logger, bool removeFromDisk, bool removeFromProduct) +{ + if (logger.traceEnabled()) + logger.qbsTrace() << "[BG] remove artifact " << relativeArtifactFileName(artifact); + + if (removeFromDisk) + removeGeneratedArtifactFromDisk(artifact, logger); + removeFromLookupTable(artifact); + if (removeFromProduct) { + artifact->product->buildData->artifacts.remove(artifact); + artifact->product->buildData->targetArtifacts.remove(artifact); + } + disconnectArtifact(artifact, this, logger); + artifactsThatMustGetNewTransformers -= artifact; + isDirty = true; +} + +void ProjectBuildData::updateNodesThatMustGetNewTransformer(const Logger &logger) +{ + RulesEvaluationContext::Scope s(evaluationContext.data()); + foreach (Artifact *artifact, artifactsThatMustGetNewTransformers) + updateNodeThatMustGetNewTransformer(artifact, logger); + artifactsThatMustGetNewTransformers.clear(); +} + +void ProjectBuildData::updateNodeThatMustGetNewTransformer(Artifact *artifact, const Logger &logger) +{ + QBS_CHECK(artifact->transformer); + + if (logger.debugEnabled()) { + logger.qbsDebug() << "[BG] updating transformer for " + << relativeArtifactFileName(artifact); + } + + removeGeneratedArtifactFromDisk(artifact, logger); + artifact->autoMocTimestamp.clear(); + artifact->clearTimestamp(); + + const RuleConstPtr rule = artifact->transformer->rule; + isDirty = true; + + QBS_CHECK(artifact->transformer); + foreach (Artifact * const sibling, artifact->transformer->outputs) + sibling->transformer.clear(); + + ArtifactsPerFileTagMap artifactsPerFileTag; + foreach (Artifact *input, artifact->children) { + foreach (const FileTag &fileTag, input->fileTags) + artifactsPerFileTag[fileTag] += input; + } + RulesApplicator rulesApplier(artifact->product, artifactsPerFileTag, logger); + rulesApplier.applyRule(rule); +} + +void ProjectBuildData::load(PersistentPool &pool) +{ + int count; + pool.stream() >> count; + fileDependencies.clear(); + fileDependencies.reserve(count); + for (; --count >= 0;) { + FileDependency *fileDependency = pool.idLoad<FileDependency>(); + insertFileDependency(fileDependency); + } +} + +void ProjectBuildData::store(PersistentPool &pool) const +{ + pool.storeContainer(fileDependencies); +} + + +BuildDataResolver::BuildDataResolver(const Logger &logger) : m_logger(logger) +{ +} + +void BuildDataResolver::resolveBuildData(const TopLevelProjectPtr &resolvedProject, + const RulesEvaluationContextPtr &evalContext) +{ + QBS_CHECK(!resolvedProject->buildData); + m_project = resolvedProject; + resolvedProject->buildData.reset(new ProjectBuildData); + resolvedProject->buildData->evaluationContext = evalContext; + const QList<ResolvedProductPtr> allProducts = resolvedProject->allProducts(); + evalContext->initializeObserver(Tr::tr("Setting up build graph for configuration %1") + .arg(resolvedProject->id()), allProducts.count() + 1); + foreach (ResolvedProductPtr rProduct, allProducts) { + if (rProduct->enabled) + resolveProductBuildData(rProduct); + evalContext->incrementProgressValue(); + } + evalContext->incrementProgressValue(); + doSanityChecks(resolvedProject, m_logger); +} + +void BuildDataResolver::resolveProductBuildDataForExistingProject(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &freshProducts) +{ + m_project = project; + foreach (const ResolvedProductPtr &product, freshProducts) { + if (product->enabled) + resolveProductBuildData(product); + } +} + +void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &product) +{ + if (product->buildData) + return; + + evalContext()->checkForCancelation(); + + product->buildData.reset(new ProductBuildData); + ArtifactsPerFileTagMap artifactsPerFileTag; + + foreach (ResolvedProductPtr dependency, product->dependencies) { + if (Q_UNLIKELY(!dependency->enabled)) { + QString msg = Tr::tr("Product '%1' depends on '%2' but '%2' is disabled."); + throw ErrorInfo(msg.arg(product->name, dependency->name)); + } + resolveProductBuildData(dependency); + } + + //add qbsFile artifact + Artifact *qbsFileArtifact = lookupArtifact(product, product->location.fileName()); + if (!qbsFileArtifact) { + qbsFileArtifact = new Artifact; + qbsFileArtifact->artifactType = Artifact::SourceFile; + qbsFileArtifact->setFilePath(product->location.fileName()); + qbsFileArtifact->properties = product->properties; + insertArtifact(product, qbsFileArtifact, m_logger); + } + qbsFileArtifact->fileTags.insert("qbs"); + artifactsPerFileTag["qbs"].insert(qbsFileArtifact); + + // read sources + foreach (const SourceArtifactConstPtr &sourceArtifact, product->allEnabledFiles()) { + QString filePath = sourceArtifact->absoluteFilePath; + if (lookupArtifact(product, filePath)) + continue; // ignore duplicate artifacts + + Artifact *artifact = createArtifact(product, sourceArtifact, m_logger); + foreach (const FileTag &fileTag, artifact->fileTags) + artifactsPerFileTag[fileTag].insert(artifact); + } + + // read manually added transformers + typedef QPair<ResolvedTransformerConstPtr, TransformerConstPtr> TrafoPair; + QList<TrafoPair> trafos; + foreach (const ResolvedTransformerConstPtr &rtrafo, product->transformers) { + ArtifactList inputArtifacts; + foreach (const QString &inputFileName, rtrafo->inputs) { + Artifact *artifact = lookupArtifact(product, inputFileName); + if (Q_UNLIKELY(!artifact)) + throw ErrorInfo(QString("Can't find artifact '%0' in the list of source files.").arg(inputFileName)); + inputArtifacts += artifact; + } + TransformerPtr transformer = Transformer::create(); + trafos += TrafoPair(rtrafo, transformer); + transformer->inputs = inputArtifacts; + const RulePtr rule = Rule::create(); + ResolvedModulePtr module = ResolvedModule::create(); + module->name = rtrafo->module->name; + rule->module = module; + rule->script = rtrafo->transform; + foreach (const SourceArtifactConstPtr &sourceArtifact, rtrafo->outputs) { + Artifact *outputArtifact = createArtifact(product, sourceArtifact, m_logger); + outputArtifact->artifactType = Artifact::Generated; + outputArtifact->transformer = transformer; + transformer->outputs += outputArtifact; + product->buildData->targetArtifacts += outputArtifact; + foreach (Artifact *inputArtifact, inputArtifacts) + safeConnect(outputArtifact, inputArtifact, m_logger); + foreach (const FileTag &fileTag, outputArtifact->fileTags) + artifactsPerFileTag[fileTag].insert(outputArtifact); + + RuleArtifactPtr ruleArtifact = RuleArtifact::create(); + ruleArtifact->fileName = outputArtifact->filePath(); + ruleArtifact->fileTags = outputArtifact->fileTags; + rule->artifacts += ruleArtifact; + } + transformer->rule = rule; + + RulesEvaluationContext::Scope s(evalContext().data()); + setupScriptEngineForFile(engine(), transformer->rule->script->fileContext, scope()); + QScriptValue prepareScriptContext = engine()->newObject(); + PrepareScriptObserver observer(engine()); + setupScriptEngineForProduct(engine(), product, transformer->rule, prepareScriptContext, + &observer); + transformer->setupInputs(engine(), prepareScriptContext); + transformer->setupOutputs(engine(), prepareScriptContext); + transformer->createCommands(rtrafo->transform, evalContext(), + ScriptEngine::argumentList(transformer->rule->script->argumentNames, + prepareScriptContext)); + if (Q_UNLIKELY(transformer->commands.isEmpty())) + throw ErrorInfo(QString("There's a transformer without commands."), rtrafo->transform->location); + } + + // Handle Transformer.explicitlyDependsOn after all transformer outputs have been created. + foreach (const TrafoPair &p, trafos) { + const ResolvedTransformerConstPtr &rtrafo = p.first; + const TransformerConstPtr &trafo = p.second; + foreach (const FileTag &tag, rtrafo->explicitlyDependsOn) { + foreach (Artifact *output, trafo->outputs) { + foreach (Artifact *dependency, artifactsPerFileTag.value(tag)) { + loggedConnect(output, dependency, m_logger); + } + } + } + } + + RulesApplicator(product, artifactsPerFileTag, m_logger).applyAllRules(); + addTargetArtifacts(product, artifactsPerFileTag, m_logger); +} + +RulesEvaluationContextPtr BuildDataResolver::evalContext() const +{ + return m_project->buildData->evaluationContext; +} + +ScriptEngine *BuildDataResolver::engine() const +{ + return evalContext()->engine(); +} + +QScriptValue BuildDataResolver::scope() const +{ + return evalContext()->scope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/projectbuilddata.h b/src/lib/corelib/buildgraph/projectbuilddata.h new file mode 100644 index 000000000..515d5f00e --- /dev/null +++ b/src/lib/corelib/buildgraph/projectbuilddata.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_PROJECTBUILDDATA_H +#define QBS_PROJECTBUILDDATA_H + +#include "artifactlist.h" +#include "forward_decls.h" +#include <language/forward_decls.h> +#include <logging/logger.h> +#include <tools/persistentobject.h> + +#include <QHash> +#include <QList> +#include <QScriptValue> +#include <QSet> +#include <QString> + +namespace qbs { +namespace Internal { +class FileDependency; +class FileResourceBase; +class ScriptEngine; + +class ProjectBuildData : public PersistentObject +{ +public: + ProjectBuildData(const ProjectBuildData *other = 0); + ~ProjectBuildData(); + + static QString deriveBuildGraphFilePath(const QString &buildDir, const QString &projectId); + + void insertIntoLookupTable(FileResourceBase *fileres); + void removeFromLookupTable(FileResourceBase *fileres); + + QList<FileResourceBase *> lookupFiles(const QString &filePath) const; + QList<FileResourceBase *> lookupFiles(const QString &dirPath, const QString &fileName) const; + QList<FileResourceBase *> lookupFiles(const Artifact *artifact) const; + void insertFileDependency(FileDependency *dependency); + void updateNodesThatMustGetNewTransformer(const Logger &logger); + void removeArtifactAndExclusiveDependents(Artifact *artifact, const Logger &logger, + bool removeFromProduct = true, ArtifactList *removedArtifacts = 0); + void removeArtifact(Artifact *artifact, const Logger &logger, bool removeFromDisk = true, + bool removeFromProduct = true); + + QSet<FileDependency *> fileDependencies; + RulesEvaluationContextPtr evaluationContext; + QSet<Artifact *> artifactsThatMustGetNewTransformers; + bool isDirty; + +private: + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + void updateNodeThatMustGetNewTransformer(Artifact *artifact, const Logger &logger); + + typedef QHash<QString, QList<FileResourceBase *> > ResultsPerDirectory; + typedef QHash<QString, ResultsPerDirectory> ArtifactLookupTable; + ArtifactLookupTable m_artifactLookupTable; + bool m_doCleanupInDestructor; +}; + + +class BuildDataResolver +{ +public: + BuildDataResolver(const Logger &logger); + void resolveBuildData(const TopLevelProjectPtr &resolvedProject, + const RulesEvaluationContextPtr &evalContext); + void resolveProductBuildDataForExistingProject(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &freshProducts); + +private: + void resolveProductBuildData(const ResolvedProductPtr &product); + RulesEvaluationContextPtr evalContext() const; + ScriptEngine *engine() const; + QScriptValue scope() const; + + TopLevelProjectPtr m_project; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROJECTBUILDDATA_H diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp new file mode 100644 index 000000000..b59edaaba --- /dev/null +++ b/src/lib/corelib/buildgraph/rulegraph.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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 "rulegraph.h" +#include <language/language.h> +#include <logging/translator.h> +#include <tools/error.h> + +namespace qbs { +namespace Internal { + +RuleGraph::RuleGraph() +{ +} + +void RuleGraph::build(const QSet<RulePtr> &rules, const FileTags &productFileTags) +{ + QMap<FileTag, QList<const Rule *> > inputFileTagToRule; + m_artifacts.reserve(rules.count()); + foreach (const RulePtr &rule, rules) { + foreach (const FileTag &fileTag, rule->outputFileTags()) + m_outputFileTagToRule[fileTag].append(rule.data()); + insert(rule); + } + + m_parents.resize(rules.count()); + m_children.resize(rules.count()); + + foreach (const RuleConstPtr &rule, m_artifacts) { + FileTags inFileTags = rule->inputs; + inFileTags += rule->auxiliaryInputs; + inFileTags += rule->explicitlyDependsOn; + foreach (const FileTag &fileTag, inFileTags) { + inputFileTagToRule[fileTag].append(rule.data()); + foreach (const Rule * const consumingRule, m_outputFileTagToRule.value(fileTag)) { + connect(rule.data(), consumingRule); + } + } + } + + QList<const Rule *> productRules; + foreach (const FileTag &productFileTag, productFileTags) { + QList<const Rule *> rules = m_outputFileTagToRule.value(productFileTag); + productRules += rules; + //### check: the rule graph must be a in valid shape! + } + foreach (const Rule *r, productRules) + m_rootRules += r->ruleGraphId; +} + +QList<RuleConstPtr> RuleGraph::topSorted() +{ + QSet<int> rootRules = m_rootRules; + QList<RuleConstPtr> result; + foreach (int rootIndex, rootRules) { + RuleConstPtr rule = m_artifacts.at(rootIndex); + QSet<const Rule *> seenRules; + QList<const Rule *> rulePath; + result.append(topSort(rule, &seenRules, &rulePath)); + } + + // remove duplicates from the result of our post-order traversal + QSet<const Rule*> seenRules; + seenRules.reserve(result.count()); + for (int i = 0; i < result.count();) { + const Rule * const rule = result.at(i).data(); + if (seenRules.contains(rule)) + result.removeAt(i); + else + ++i; + seenRules.insert(rule); + } + + return result; +} + +void RuleGraph::dump() const +{ + QByteArray indent; + printf("---rule graph dump:\n"); + QSet<int> rootRules; + foreach (const RuleConstPtr &rule, m_artifacts) + if (m_parents[rule->ruleGraphId].isEmpty()) + rootRules += rule->ruleGraphId; + foreach (int idx, rootRules) { + dump_impl(indent, idx); + } +} + +void RuleGraph::dump_impl(QByteArray &indent, int rootIndex) const +{ + const RuleConstPtr r = m_artifacts[rootIndex]; + printf("%s", indent.constData()); + printf("%s", qPrintable(r->toString())); + printf("\n"); + + indent.append(" "); + foreach (int childIndex, m_children[rootIndex]) + dump_impl(indent, childIndex); + indent.chop(2); +} + +int RuleGraph::insert(const RulePtr &rule) +{ + rule->ruleGraphId = m_artifacts.count(); + m_artifacts.append(rule); + return rule->ruleGraphId; +} + +void RuleGraph::connect(const Rule *creatingRule, const Rule *consumingRule) +{ + int maxIndex = qMax(creatingRule->ruleGraphId, consumingRule->ruleGraphId); + if (m_parents.count() <= maxIndex) { + const int c = maxIndex + 1; + m_parents.resize(c); + m_children.resize(c); + } + m_parents[consumingRule->ruleGraphId].append(creatingRule->ruleGraphId); + m_children[creatingRule->ruleGraphId].append(consumingRule->ruleGraphId); +} + +QList<RuleConstPtr> RuleGraph::topSort(const RuleConstPtr &rule, QSet<const Rule *> *seenRules, + QList<const Rule *> *rulePath) +{ + if (seenRules->contains(rule.data())) { + QString pathstr; + foreach (const Rule *r, *rulePath) { + pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t') + + r->script->location.toString(); + } + throw ErrorInfo(Tr::tr("Cycle detected in rule dependencies: %1").arg(pathstr)); + } + + seenRules->insert(rule.data()); + rulePath->prepend(rule.data()); + + QList<RuleConstPtr> result; + foreach (int childIndex, m_children.at(rule->ruleGraphId)) + result.append(topSort(m_artifacts.at(childIndex), seenRules, rulePath)); + + result.append(rule); + seenRules->remove(rule.data()); + rulePath->removeFirst(); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulegraph.h b/src/lib/corelib/buildgraph/rulegraph.h new file mode 100644 index 000000000..2f9a42a33 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulegraph.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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_RULEGRAPH_H +#define QBS_RULEGRAPH_H + +#include <language/filetags.h> +#include <language/forward_decls.h> + +#include <QList> +#include <QMap> +#include <QSet> +#include <QString> +#include <QVector> + +namespace qbs { +namespace Internal { + +class RuleGraph +{ +public: + RuleGraph(); + + void build(const QSet<RulePtr> &rules, const FileTags &productFileTag); + QList<RuleConstPtr> topSorted(); + + void dump() const; + +private: + void dump_impl(QByteArray &indent, int rootIndex) const; + int insert(const RulePtr &rule); + void connect(const Rule *creatingRule, const Rule *consumingRule); + QList<RuleConstPtr> topSort(const RuleConstPtr &rule, QSet<const Rule *> *seenRules, + QList<const Rule *> *rulePath); + +private: + QMap<FileTag, QList<const Rule*> > m_outputFileTagToRule; + QVector<RulePtr> m_artifacts; + QVector< QVector<int> > m_parents; + QVector< QVector<int> > m_children; + QSet<int> m_rootRules; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULEGRAPH_H diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp new file mode 100644 index 000000000..8aea1f916 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** 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 "rulesapplicator.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" +#include <jsextensions/moduleproperties.h> +#include <language/artifactproperties.h> +#include <language/language.h> +#include <language/preparescriptobserver.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/scripttools.h> +#include <tools/qbsassert.h> + +#include <QDir> + +namespace qbs { +namespace Internal { + +RulesApplicator::RulesApplicator(const ResolvedProductPtr &product, + ArtifactsPerFileTagMap &artifactsPerFileTag, const Logger &logger) + : m_product(product) + , m_artifactsPerFileTag(artifactsPerFileTag) + , m_logger(logger) +{ +} + +void RulesApplicator::applyAllRules() +{ + RulesEvaluationContext::Scope s(m_product->topLevelProject()->buildData->evaluationContext.data()); + foreach (const RuleConstPtr &rule, m_product->topSortedRules()) + applyRule(rule); +} + +void RulesApplicator::applyRule(const RuleConstPtr &rule) +{ + m_rule = rule; + QScriptValue prepareScriptContext = engine()->newObject(); + PrepareScriptObserver observer(engine()); + setupScriptEngineForFile(engine(), m_rule->script->fileContext, scope()); + setupScriptEngineForProduct(engine(), m_product, m_rule, prepareScriptContext, &observer); + + ArtifactList inputArtifacts; + foreach (const FileTag &fileTag, m_rule->inputs) + inputArtifacts.unite(m_artifactsPerFileTag.value(fileTag)); + if (m_rule->multiplex) { // apply the rule once for a set of inputs + if (!inputArtifacts.isEmpty()) + doApply(inputArtifacts, prepareScriptContext); + } else { // apply the rule once for each input + ArtifactList lst; + foreach (Artifact * const inputArtifact, inputArtifacts) { + setupScriptEngineForArtifact(inputArtifact); + lst += inputArtifact; + doApply(lst, prepareScriptContext); + lst.clear(); + } + } +} + +static void copyProperty(const QString &name, const QScriptValue &src, QScriptValue dst) +{ + dst.setProperty(name, src.property(name)); +} + +void RulesApplicator::doApply(const ArtifactList &inputArtifacts, + QScriptValue &prepareScriptContext) +{ + evalContext()->checkForCancelation(); + + if (m_logger.debugEnabled()) { + m_logger.qbsDebug() << "[BG] apply rule " << m_rule->toString() << " " + << toStringList(inputArtifacts).join(",\n "); + } + + QList<QPair<const RuleArtifact *, Artifact *> > ruleArtifactArtifactMap; + QList<Artifact *> outputArtifacts; + + ArtifactList usingArtifacts; + if (!m_rule->usings.isEmpty()) { + const FileTags usingsFileTags = m_rule->usings; + foreach (const ResolvedProductPtr &dep, m_product->dependencies) { + QBS_CHECK(dep->buildData); + ArtifactList artifactsToCheck; + foreach (Artifact *targetArtifact, dep->buildData->targetArtifacts) + artifactsToCheck.unite(targetArtifact->transformer->outputs); + foreach (Artifact *artifact, artifactsToCheck) { + if (artifact->fileTags.matches(usingsFileTags)) + usingArtifacts.insert(artifact); + } + } + } + + m_transformer.clear(); + // create the output artifacts from the set of input artifacts + copyProperty(QLatin1String("product"), prepareScriptContext, scope()); + copyProperty(QLatin1String("project"), prepareScriptContext, scope()); + foreach (const RuleArtifactConstPtr &ruleArtifact, m_rule->artifacts) { + Artifact * const outputArtifact = createOutputArtifact(ruleArtifact, inputArtifacts); + outputArtifacts << outputArtifact; + ruleArtifactArtifactMap << qMakePair(ruleArtifact.data(), outputArtifact); + } + + foreach (Artifact *outputArtifact, outputArtifacts) { + // insert the output artifacts into the pool of artifacts + foreach (const FileTag &fileTag, outputArtifact->fileTags) + m_artifactsPerFileTag[fileTag].insert(outputArtifact); + + // connect artifacts that match the file tags in explicitlyDependsOn + foreach (const FileTag &fileTag, m_rule->explicitlyDependsOn) + foreach (Artifact *dependency, m_artifactsPerFileTag.value(fileTag)) + loggedConnect(outputArtifact, dependency, m_logger); + + // Transformer setup + for (ArtifactList::const_iterator it = usingArtifacts.constBegin(); + it != usingArtifacts.constEnd(); ++it) + { + Artifact *dep = *it; + loggedConnect(outputArtifact, dep, m_logger); + m_transformer->inputs.insert(dep); + } + m_transformer->outputs.insert(outputArtifact); + + m_product->topLevelProject()->buildData->artifactsThatMustGetNewTransformers + -= outputArtifact; + } + + m_transformer->setupInputs(engine(), prepareScriptContext); + + // change the transformer outputs according to the bindings in Artifact + QScriptValue scriptValue; + if (!ruleArtifactArtifactMap.isEmpty()) + engine()->currentContext()->pushScope(prepareScriptContext); + for (int i = ruleArtifactArtifactMap.count(); --i >= 0;) { + const RuleArtifact *ra = ruleArtifactArtifactMap.at(i).first; + if (ra->bindings.isEmpty()) + continue; + + // expose attributes of this artifact + Artifact *outputArtifact = ruleArtifactArtifactMap.at(i).second; + outputArtifact->properties = outputArtifact->properties->clone(); + + scope().setProperty("fileName", engine()->toScriptValue(outputArtifact->filePath())); + scope().setProperty("fileTags", + toScriptValue(engine(), outputArtifact->fileTags.toStringList())); + + QVariantMap artifactModulesCfg = outputArtifact->properties->value().value("modules").toMap(); + for (int i=0; i < ra->bindings.count(); ++i) { + const RuleArtifact::Binding &binding = ra->bindings.at(i); + scriptValue = engine()->evaluate(binding.code); + if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) { + QString msg = QLatin1String("evaluating rule binding '%1': %2"); + throw ErrorInfo(msg.arg(binding.name.join(QLatin1String(".")), scriptValue.toString()), binding.location); + } + setConfigProperty(artifactModulesCfg, binding.name, scriptValue.toVariant()); + } + QVariantMap outputArtifactConfig = outputArtifact->properties->value(); + outputArtifactConfig.insert("modules", artifactModulesCfg); + outputArtifact->properties->setValue(outputArtifactConfig); + } + if (!ruleArtifactArtifactMap.isEmpty()) + engine()->currentContext()->popScope(); + + m_transformer->setupOutputs(engine(), prepareScriptContext); + m_transformer->createCommands(m_rule->script, evalContext(), + ScriptEngine::argumentList(m_rule->script->argumentNames, prepareScriptContext)); + if (Q_UNLIKELY(m_transformer->commands.isEmpty())) + throw ErrorInfo(QString("There's a rule without commands: %1.").arg(m_rule->toString()), m_rule->script->location); +} + +void RulesApplicator::setupScriptEngineForArtifact(Artifact *artifact) +{ + QString inFileName = artifact->fileName(); + QString inBaseName = FileInfo::baseName(artifact->filePath()); + QString inCompleteBaseName = FileInfo::completeBaseName(artifact->filePath()); + + QString basedir; + if (artifact->artifactType == Artifact::SourceFile) { + QDir sourceDir(m_product->sourceDirectory); + basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath())); + } else { + QDir buildDir(m_product->topLevelProject()->buildDirectory); + basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath())); + } + + // expose per file properties we want to use in an Artifact within a Rule + QScriptValue scriptValue = engine()->newObject(); + ModuleProperties::init(scriptValue, artifact); + scriptValue.setProperty("fileName", inFileName); + scriptValue.setProperty("baseName", inBaseName); + scriptValue.setProperty("completeBaseName", inCompleteBaseName); + scriptValue.setProperty("baseDir", basedir); + + scope().setProperty("input", scriptValue); + Q_ASSERT_X(scriptValue.strictlyEquals(engine()->evaluate("input")), + "BG", "The input object is not in current scope."); +} + +Artifact *RulesApplicator::createOutputArtifact(const RuleArtifactConstPtr &ruleArtifact, + const ArtifactList &inputArtifacts) +{ + QScriptValue scriptValue = engine()->evaluate(ruleArtifact->fileName); + if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) + throw ErrorInfo("Error in Rule.Artifact fileName: " + scriptValue.toString()); + QString outputPath = scriptValue.toString(); + outputPath.replace("..", "dotdot"); // don't let the output artifact "escape" its build dir + outputPath = resolveOutPath(outputPath); + + Artifact *outputArtifact = lookupArtifact(m_product, outputPath); + if (outputArtifact) { + if (outputArtifact->transformer && outputArtifact->transformer != m_transformer) { + QBS_CHECK(!m_transformer); + + // This can happen when applying rules after scanning for additional file tags. + // We just regenerate the transformer. + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] regenerating transformer " + "for '%1'").arg(relativeArtifactFileName(outputArtifact)); + } + m_transformer = outputArtifact->transformer; + m_transformer->inputs.unite(inputArtifacts); + + if (Q_UNLIKELY(m_transformer->inputs.count() > 1 && !m_rule->multiplex)) { + QString th = "[" + outputArtifact->fileTags.toStringList().join(", ") + "]"; + QString e = Tr::tr("Conflicting rules for producing %1 %2 \n").arg(outputArtifact->filePath(), th); + th = "[" + m_rule->inputs.toStringList().join(", ") + + "] -> [" + outputArtifact->fileTags.toStringList().join(", ") + "]"; + + e += QString(" while trying to apply: %1:%2:%3 %4\n") + .arg(m_rule->script->location.fileName()) + .arg(m_rule->script->location.line()) + .arg(m_rule->script->location.column()) + .arg(th); + + e += QString(" was already defined in: %1:%2:%3 %4\n") + .arg(outputArtifact->transformer->rule->script->location.fileName()) + .arg(outputArtifact->transformer->rule->script->location.line()) + .arg(outputArtifact->transformer->rule->script->location.column()) + .arg(th); + + QStringList inputFilePaths; + foreach (const Artifact * const a, m_transformer->inputs) + inputFilePaths << a->filePath(); + e.append(Tr::tr("The input artifacts are: %1") + .arg(inputFilePaths.join(QLatin1String(", ")))); + throw ErrorInfo(e); + } + } + outputArtifact->fileTags += ruleArtifact->fileTags; + } else { + outputArtifact = new Artifact; + outputArtifact->artifactType = Artifact::Generated; + outputArtifact->setFilePath(outputPath); + outputArtifact->fileTags = ruleArtifact->fileTags; + outputArtifact->alwaysUpdated = ruleArtifact->alwaysUpdated; + outputArtifact->properties = m_product->properties; + insertArtifact(m_product, outputArtifact, m_logger); + } + + if (outputArtifact->fileTags.isEmpty()) + outputArtifact->fileTags = m_product->fileTagsForFileName(outputArtifact->fileName()); + + for (int i = 0; i < m_product->artifactProperties.count(); ++i) { + const ArtifactPropertiesConstPtr &props = m_product->artifactProperties.at(i); + if (outputArtifact->fileTags.matches(props->fileTagsFilter())) { + outputArtifact->properties = props->propertyMap(); + break; + } + } + + foreach (Artifact *inputArtifact, inputArtifacts) { + QBS_CHECK(outputArtifact != inputArtifact); + loggedConnect(outputArtifact, inputArtifact, m_logger); + } + + // create transformer if not already done so + if (!m_transformer) { + m_transformer = Transformer::create(); + m_transformer->rule = m_rule; + m_transformer->inputs = inputArtifacts; + } + outputArtifact->transformer = m_transformer; + + return outputArtifact; +} + +QString RulesApplicator::resolveOutPath(const QString &path) const +{ + QString buildDir = m_product->topLevelProject()->buildDirectory; + QString result = FileInfo::resolvePath(buildDir, path); + result = QDir::cleanPath(result); + return result; +} + +RulesEvaluationContextPtr RulesApplicator::evalContext() const +{ + return m_product->topLevelProject()->buildData->evaluationContext; +} + +ScriptEngine *RulesApplicator::engine() const +{ + return evalContext()->engine(); +} + +QScriptValue RulesApplicator::scope() const +{ + return evalContext()->scope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulesapplicator.h b/src/lib/corelib/buildgraph/rulesapplicator.h new file mode 100644 index 000000000..76664baac --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesapplicator.h @@ -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. +** +****************************************************************************/ +#ifndef QBS_RULESAPPLICATOR_H +#define QBS_RULESAPPLICATOR_H + +#include "artifactlist.h" +#include "forward_decls.h" +#include <language/filetags.h> +#include <language/forward_decls.h> +#include <logging/logger.h> + +#include <QMap> +#include <QScriptValue> +#include <QString> + +namespace qbs { +namespace Internal { +class ScriptEngine; + +typedef QMap<FileTag, ArtifactList> ArtifactsPerFileTagMap; + +class RulesApplicator +{ +public: + RulesApplicator(const ResolvedProductPtr &product, ArtifactsPerFileTagMap &artifactsPerFileTag, + const Logger &logger); + void applyAllRules(); + void applyRule(const RuleConstPtr &rule); + +private: + void doApply(const ArtifactList &inputArtifacts, QScriptValue &prepareScriptContext); + void setupScriptEngineForArtifact(Artifact *artifact); + Artifact *createOutputArtifact(const RuleArtifactConstPtr &ruleArtifact, + const ArtifactList &inputArtifacts); + QString resolveOutPath(const QString &path) const; + RulesEvaluationContextPtr evalContext() const; + ScriptEngine *engine() const; + QScriptValue scope() const; + + const ResolvedProductPtr m_product; + ArtifactsPerFileTagMap &m_artifactsPerFileTag; + + RuleConstPtr m_rule; + TransformerPtr m_transformer; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULESAPPLICATOR_H diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp new file mode 100644 index 000000000..5cabea9d7 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 "rulesevaluationcontext.h" + +#include "artifact.h" +#include "command.h" +#include "transformer.h" +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> + +#include <QVariant> + +namespace qbs { +namespace Internal { + +RulesEvaluationContext::RulesEvaluationContext(const Logger &logger) + : m_engine(new ScriptEngine(logger)), m_observer(0), m_initScopeCalls(0) +{ + m_prepareScriptScope = m_engine->newObject(); + ProcessCommand::setupForJavaScript(m_prepareScriptScope); + JavaScriptCommand::setupForJavaScript(m_prepareScriptScope); +} + +RulesEvaluationContext::~RulesEvaluationContext() +{ + delete m_engine; +} + +void RulesEvaluationContext::initializeObserver(const QString &description, int maximumProgress) +{ + if (m_observer) + m_observer->initialize(description, maximumProgress); +} + +void RulesEvaluationContext::incrementProgressValue() +{ + if (m_observer) + m_observer->incrementProgressValue(); +} + +void RulesEvaluationContext::checkForCancelation() +{ + if (Q_UNLIKELY(m_observer && m_observer->canceled())) + throw ErrorInfo(Tr::tr("Build canceled.")); +} + +void RulesEvaluationContext::initScope() +{ + if (m_initScopeCalls++ > 0) + return; + + m_engine->setProperty("lastSetupProject", QVariant()); + m_engine->setProperty("lastSetupProduct", QVariant()); + + m_engine->clearImportsCache(); + m_engine->pushContext(); + m_scope = m_engine->newObject(); + m_scope.setPrototype(m_prepareScriptScope); + m_engine->currentContext()->pushScope(m_scope); +} + +void RulesEvaluationContext::cleanupScope() +{ + QBS_CHECK(m_initScopeCalls > 0); + if (--m_initScopeCalls > 0) + return; + + m_scope = QScriptValue(); + m_engine->currentContext()->popScope(); + m_engine->popContext(); +} + +RulesEvaluationContext::Scope::Scope(RulesEvaluationContext *evalContext) + : m_evalContext(evalContext) +{ + evalContext->initScope(); +} + +RulesEvaluationContext::Scope::~Scope() +{ + m_evalContext->cleanupScope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.h b/src/lib/corelib/buildgraph/rulesevaluationcontext.h new file mode 100644 index 000000000..1682e858b --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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_RULESEVALUATIONCONTEXT_H +#define QBS_RULESEVALUATIONCONTEXT_H + +#include <language/forward_decls.h> + +#include <QHash> +#include <QScriptProgram> +#include <QScriptValue> +#include <QString> + +namespace qbs { +namespace Internal { +class Logger; +class ProgressObserver; +class ScriptEngine; + +class RulesEvaluationContext +{ +public: + RulesEvaluationContext(const Logger &logger); + ~RulesEvaluationContext(); + + class Scope + { + public: + Scope(RulesEvaluationContext *evalContext); + ~Scope(); + + private: + RulesEvaluationContext * const m_evalContext; + }; + + ScriptEngine *engine() const { return m_engine; } + QScriptValue scope() const { return m_scope; } + + void setObserver(ProgressObserver *observer) { m_observer = observer; } + ProgressObserver *observer() const { return m_observer; } + void initializeObserver(const QString &description, int maximumProgress); + void incrementProgressValue(); + void checkForCancelation(); + +private: + friend class Scope; + + void initScope(); + void cleanupScope(); + + ScriptEngine * const m_engine; + ProgressObserver *m_observer; + unsigned int m_initScopeCalls; + QScriptValue m_scope; + QScriptValue m_prepareScriptScope; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULESEVALUATIONCONTEXT_H diff --git a/src/lib/corelib/buildgraph/scanresultcache.cpp b/src/lib/corelib/buildgraph/scanresultcache.cpp new file mode 100644 index 000000000..98cfb7fba --- /dev/null +++ b/src/lib/corelib/buildgraph/scanresultcache.cpp @@ -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. +** +****************************************************************************/ + +#include "scanresultcache.h" +#include <tools/fileinfo.h> + +namespace qbs { +namespace Internal { + +ScanResultCache::Dependency::Dependency(const QString &filePath, bool isLocal) + : m_isLocal(isLocal) +{ + FileInfo::splitIntoDirectoryAndFileName(filePath, &m_dirPath, &m_fileName); + + m_isClean = !m_dirPath.contains(QLatin1Char('.')) + && !m_dirPath.contains(QLatin1String("//")); +} + +ScanResultCache::Result ScanResultCache::value(const QString &fileName) const +{ + return m_data.value(fileName); +} + +void ScanResultCache::insert(const QString &fileName, const ScanResultCache::Result &value) +{ + m_data.insert(fileName, value); +} + +void ScanResultCache::remove(const QString &filePath) +{ + m_data.remove(filePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/scanresultcache.h b/src/lib/corelib/buildgraph/scanresultcache.h new file mode 100644 index 000000000..289aa31e6 --- /dev/null +++ b/src/lib/corelib/buildgraph/scanresultcache.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_SCANRESULTCACHE_H +#define QBS_SCANRESULTCACHE_H + +#include <language/filetags.h> + +#include <QHash> +#include <QString> +#include <QVector> + +namespace qbs { +namespace Internal { + +class ScanResultCache +{ +public: + class Dependency + { + public: + Dependency() : m_isLocal(false), m_isClean(true) {} + Dependency(const QString &filePath, bool m_isLocal); + + QString filePath() const { return m_dirPath.isEmpty() ? m_fileName : m_dirPath + QLatin1Char('/') + m_fileName; } + const QString &dirPath() const { return m_dirPath; } + const QString &fileName() const { return m_fileName; } + bool isLocal() const { return m_isLocal; } + bool isClean() const { return m_isClean; } + + private: + QString m_dirPath; + QString m_fileName; + bool m_isLocal; + bool m_isClean; + }; + + class Result + { + public: + Result() + : valid(false) + {} + + QVector<Dependency> deps; + FileTags additionalFileTags; + bool valid; + }; + + Result value(const QString &fileName) const; + void insert(const QString &fileName, const Result &value); + void remove(const QString &filePath); + +private: + QHash<QString, Result> m_data; +}; + +} // namespace qbs +} // namespace qbs + +#endif // QBS_SCANRESULTCACHE_H diff --git a/src/lib/corelib/buildgraph/timestampsupdater.cpp b/src/lib/corelib/buildgraph/timestampsupdater.cpp new file mode 100644 index 000000000..c22e9f96c --- /dev/null +++ b/src/lib/corelib/buildgraph/timestampsupdater.cpp @@ -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. +** +****************************************************************************/ +#include "timestampsupdater.h" + +#include "artifact.h" +#include "artifactvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include <language/language.h> +#include <tools/filetime.h> +#include <tools/qbsassert.h> + +#include <QFile> + +namespace qbs { +namespace Internal { + +class TimestampsUpdateVisitor : public ArtifactVisitor +{ +public: + TimestampsUpdateVisitor() + : ArtifactVisitor(Artifact::Generated), m_now(FileTime::currentTime()) {} + + void visitProduct(const ResolvedProductConstPtr &product) + { + QBS_CHECK(product->buildData); + ArtifactVisitor::visitProduct(product); + + // For target artifacts, we have to update the on-disk timestamp, because + // the executor will look at it. + foreach (Artifact * const targetArtifact, product->buildData->targetArtifacts) { + if (FileInfo(targetArtifact->filePath()).exists()) + QFile(targetArtifact->filePath()).open(QIODevice::WriteOnly | QIODevice::Append); + } + } + +private: + void doVisit(Artifact *artifact) + { + if (FileInfo(artifact->filePath()).exists()) + artifact->setTimestamp(m_now); + } + + FileTime m_now; +}; + +void TimestampsUpdater::updateTimestamps(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const Logger &logger) +{ + TimestampsUpdateVisitor v; + foreach (const ResolvedProductPtr &product, products) + v.visitProduct(product); + project->buildData->isDirty = !products.isEmpty(); + project->store(logger); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/timestampsupdater.h b/src/lib/corelib/buildgraph/timestampsupdater.h new file mode 100644 index 000000000..c1f918837 --- /dev/null +++ b/src/lib/corelib/buildgraph/timestampsupdater.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 TIMESTAMPSUPDATER_H +#define TIMESTAMPSUPDATER_H + +#include <language/forward_decls.h> + +#include <QList> + +namespace qbs { +namespace Internal { +class Logger; + +class TimestampsUpdater +{ +public: + void updateTimestamps(const TopLevelProjectPtr &project, + const QList<ResolvedProductPtr> &products, const Logger &logger); +}; + +} // namespace Internal +} // namespace qbs + +#endif // TIMESTAMPSUPDATER_H diff --git a/src/lib/corelib/buildgraph/transformer.cpp b/src/lib/corelib/buildgraph/transformer.cpp new file mode 100644 index 000000000..5c095f9db --- /dev/null +++ b/src/lib/corelib/buildgraph/transformer.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** 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 "transformer.h" + +#include "artifact.h" +#include "command.h" +#include "rulesevaluationcontext.h" +#include <jsextensions/moduleproperties.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +Transformer::Transformer() +{ +} + +Transformer::~Transformer() +{ + qDeleteAll(commands); +} + +QScriptValue Transformer::translateFileConfig(QScriptEngine *scriptEngine, Artifact *artifact, const QString &defaultModuleName) +{ + QScriptValue artifactConfig = scriptEngine->newObject(); + ModuleProperties::init(artifactConfig, artifact); + artifactConfig.setProperty(QLatin1String("fileName"), artifact->filePath()); + const QStringList fileTags = artifact->fileTags.toStringList(); + artifactConfig.setProperty(QLatin1String("fileTags"), scriptEngine->toScriptValue(fileTags)); + if (!defaultModuleName.isEmpty()) + artifactConfig.setProperty(QLatin1String("moduleName"), defaultModuleName); + return artifactConfig; +} + +QScriptValue Transformer::translateInOutputs(QScriptEngine *scriptEngine, const ArtifactList &artifacts, const QString &defaultModuleName) +{ + typedef QMap<QString, QList<Artifact*> > TagArtifactsMap; + TagArtifactsMap tagArtifactsMap; + foreach (Artifact *artifact, artifacts) + foreach (const FileTag &fileTag, artifact->fileTags) + tagArtifactsMap[fileTag.toString()].append(artifact); + + QScriptValue jsTagFiles = scriptEngine->newObject(); + for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) { + const QList<Artifact*> &artifactList = tag.value(); + QScriptValue jsFileConfig = scriptEngine->newArray(artifactList.count()); + int i=0; + foreach (Artifact *artifact, artifactList) { + jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact, defaultModuleName)); + } + jsTagFiles.setProperty(tag.key(), jsFileConfig); + } + + return jsTagFiles; +} + +ResolvedProductPtr Transformer::product() const +{ + if (outputs.isEmpty()) + return ResolvedProductPtr(); + return (*outputs.begin())->product; +} + +void Transformer::setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue) +{ + const QString &defaultModuleName = rule->module->name; + QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName); + targetScriptValue.setProperty("inputs", scriptValue); + if (inputs.count() == 1) { + Artifact *input = *inputs.begin(); + const FileTags &fileTags = input->fileTags; + QBS_ASSERT(!fileTags.isEmpty(), return); + QScriptValue inputsForFileTag = scriptValue.property(fileTags.begin()->toString()); + QScriptValue inputScriptValue = inputsForFileTag.property(0); + targetScriptValue.setProperty("input", inputScriptValue); + } else { + targetScriptValue.setProperty(QLatin1String("input"), scriptEngine->undefinedValue()); + } +} + +void Transformer::setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue) +{ + const QString &defaultModuleName = rule->module->name; + QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName); + targetScriptValue.setProperty("outputs", scriptValue); + if (outputs.count() == 1) { + Artifact *output = *outputs.begin(); + const FileTags &fileTags = output->fileTags; + QBS_ASSERT(!fileTags.isEmpty(), return); + QScriptValue outputsForFileTag = scriptValue.property(fileTags.begin()->toString()); + QScriptValue outputScriptValue = outputsForFileTag.property(0); + targetScriptValue.setProperty("output", outputScriptValue); + } else { + targetScriptValue.setProperty(QLatin1String("output"), scriptEngine->undefinedValue()); + } +} + +static AbstractCommand *createCommandFromScriptValue(const QScriptValue &scriptValue, + const CodeLocation &codeLocation) +{ + if (scriptValue.isUndefined() || !scriptValue.isValid()) + return 0; + AbstractCommand *cmdBase = 0; + QString className = scriptValue.property("className").toString(); + if (className == "Command") + cmdBase = new ProcessCommand; + else if (className == "JavaScriptCommand") + cmdBase = new JavaScriptCommand; + if (cmdBase) + cmdBase->fillFromScriptValue(&scriptValue, codeLocation); + return cmdBase; +} + +void Transformer::createCommands(const ScriptFunctionConstPtr &script, + const RulesEvaluationContextPtr &evalContext, const QScriptValueList &args) +{ + ScriptEngine * const engine = evalContext->engine(); + if (!script->scriptFunction.isValid() || script->scriptFunction.engine() != engine) { + script->scriptFunction = engine->evaluate(script->sourceCode); + if (Q_UNLIKELY(!script->scriptFunction.isFunction())) + throw ErrorInfo(Tr::tr("Invalid prepare script."), script->location); + } + + QScriptValue scriptValue = script->scriptFunction.call(QScriptValue(), args); + propertiesRequestedInPrepareScript = engine->propertiesRequestedInScript(); + propertiesRequestedFromArtifactInPrepareScript = engine->propertiesRequestedFromArtifact(); + engine->clearRequestedProperties(); + if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) + throw ErrorInfo("evaluating prepare script: " + engine->uncaughtException().toString(), + CodeLocation(script->location.fileName(), + script->location.line() + engine->uncaughtExceptionLineNumber() - 1)); + + qDeleteAll(commands); + commands.clear(); + if (scriptValue.isArray()) { + const int count = scriptValue.property("length").toInt32(); + for (qint32 i = 0; i < count; ++i) { + QScriptValue item = scriptValue.property(i); + if (item.isValid() && !item.isUndefined()) { + AbstractCommand *cmd = createCommandFromScriptValue(item, script->location); + if (cmd) + commands += cmd; + } + } + } else { + AbstractCommand *cmd = createCommandFromScriptValue(scriptValue, script->location); + if (cmd) + commands += cmd; + } +} + +static void restorePropertyList(PersistentPool &pool, PropertyList &list) +{ + int count; + pool.stream() >> count; + list.reserve(count); + while (--count >= 0) { + Property p; + p.moduleName = pool.idLoadString(); + p.propertyName = pool.idLoadString(); + int k; + pool.stream() >> p.value >> k; + p.kind = static_cast<Property::Kind>(k); + list += p; + } +} + +void Transformer::load(PersistentPool &pool) +{ + rule = pool.idLoadS<Rule>(); + pool.loadContainer(inputs); + pool.loadContainer(outputs); + restorePropertyList(pool, propertiesRequestedInPrepareScript); + restorePropertyList(pool, propertiesRequestedInCommands); + int count; + pool.stream() >> count; + propertiesRequestedFromArtifactInPrepareScript.reserve(count); + while (--count >= 0) { + const QString artifactName = pool.idLoadString(); + int listCount; + pool.stream() >> listCount; + PropertyList list; + list.reserve(listCount); + while (--listCount >= 0) { + Property p; + p.moduleName = pool.idLoadString(); + p.propertyName = pool.idLoadString(); + pool.stream() >> p.value; + p.kind = Property::PropertyInModule; + list += p; + } + propertiesRequestedFromArtifactInPrepareScript.insert(artifactName, list); + } + int cmdType; + pool.stream() >> count; + commands.reserve(count); + while (--count >= 0) { + pool.stream() >> cmdType; + AbstractCommand *cmd = AbstractCommand::createByType(static_cast<AbstractCommand::CommandType>(cmdType)); + cmd->load(pool.stream()); + commands += cmd; + } +} + +static void storePropertyList(PersistentPool &pool, const PropertyList &list) +{ + pool.stream() << list.count(); + foreach (const Property &p, list) { + pool.storeString(p.moduleName); + pool.storeString(p.propertyName); + pool.stream() << p.value << static_cast<int>(p.kind); + } +} + +void Transformer::store(PersistentPool &pool) const +{ + pool.store(rule); + pool.storeContainer(inputs); + pool.storeContainer(outputs); + storePropertyList(pool, propertiesRequestedInPrepareScript); + storePropertyList(pool, propertiesRequestedInCommands); + pool.stream() << propertiesRequestedFromArtifactInPrepareScript.count(); + for (QHash<QString, PropertyList>::ConstIterator it = propertiesRequestedFromArtifactInPrepareScript.constBegin(); + it != propertiesRequestedFromArtifactInPrepareScript.constEnd(); ++it) { + pool.storeString(it.key()); + const PropertyList &properties = it.value(); + pool.stream() << properties.count(); + foreach (const Property &p, properties) { + pool.storeString(p.moduleName); + pool.storeString(p.propertyName); + pool.stream() << p.value; // kind is always PropertyInModule + } + } + pool.stream() << commands.count(); + foreach (AbstractCommand *cmd, commands) { + pool.stream() << int(cmd->type()); + cmd->store(pool.stream()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/transformer.h b/src/lib/corelib/buildgraph/transformer.h new file mode 100644 index 000000000..b6ef09561 --- /dev/null +++ b/src/lib/corelib/buildgraph/transformer.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_TRANSFORMER_H +#define QBS_TRANSFORMER_H + +#include "artifactlist.h" +#include "forward_decls.h" +#include <language/forward_decls.h> +#include <language/property.h> +#include <tools/persistentobject.h> + +#include <QHash> +#include <QScriptEngine> + +namespace qbs { +namespace Internal { +class Artifact; +class AbstractCommand; +class Rule; +class ScriptEngine; + +class Transformer : public PersistentObject +{ +public: + static TransformerPtr create() { return TransformerPtr(new Transformer); } + + ~Transformer(); + + ArtifactList inputs; // Subset of "children of all outputs". + ArtifactList outputs; + RuleConstPtr rule; + QList<AbstractCommand *> commands; + PropertyList propertiesRequestedInPrepareScript; + PropertyList propertiesRequestedInCommands; + QHash<QString, PropertyList> propertiesRequestedFromArtifactInPrepareScript; + + static QScriptValue translateFileConfig(QScriptEngine *scriptEngine, + Artifact *artifact, + const QString &defaultModuleName); + static QScriptValue translateInOutputs(QScriptEngine *scriptEngine, + const ArtifactList &artifacts, + const QString &defaultModuleName); + + ResolvedProductPtr product() const; + void setupInputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue); + void setupOutputs(QScriptEngine *scriptEngine, QScriptValue targetScriptValue); + void createCommands(const ScriptFunctionConstPtr &script, + const RulesEvaluationContextPtr &evalContext, const QScriptValueList &args); + +private: + Transformer(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_TRANSFORMER_H diff --git a/src/lib/corelib/buildgraph/tst_buildgraph.cpp b/src/lib/corelib/buildgraph/tst_buildgraph.cpp new file mode 100644 index 000000000..a159c2938 --- /dev/null +++ b/src/lib/corelib/buildgraph/tst_buildgraph.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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_buildgraph.h" + +#include <buildgraph/artifact.h> +#include <buildgraph/productbuilddata.h> +#include <buildgraph/cycledetector.h> +#include <language/language.h> +#include <logging/logger.h> +#include <tools/error.h> + +#include <QtTest> + +namespace qbs { +namespace Internal { + +TestBuildGraph::TestBuildGraph(ILogSink *logSink) : m_logSink(logSink) +{ +} + +void TestBuildGraph::initTestCase() +{ +} + +void TestBuildGraph::cleanupTestCase() +{ + qDeleteAll(m_artifacts); +} + + +bool TestBuildGraph::cycleDetected(const ResolvedProductConstPtr &product) +{ + try { + CycleDetector(Logger(m_logSink)).visitProduct(product); + return false; + } catch (const ErrorInfo &) { + return true; + } +} + +ResolvedProductConstPtr TestBuildGraph::productWithDirectCycle() +{ + Artifact * const root = new Artifact; + Artifact * const child = new Artifact; + m_artifacts << root << child; + root->children.insert(child); + child->children.insert(root); + const ResolvedProductPtr product = ResolvedProduct::create(); + product->buildData.reset(new ProductBuildData); + product->buildData->targetArtifacts.insert(root); + return product; +} + +ResolvedProductConstPtr TestBuildGraph::productWithLessDirectCycle() +{ + Artifact * const root = new Artifact; + Artifact * const child = new Artifact; + Artifact * const grandchild = new Artifact; + m_artifacts << root << child << grandchild; + root->children.insert(child); + child->children.insert(grandchild); + grandchild->children.insert(root); + const ResolvedProductPtr product = ResolvedProduct::create(); + product->buildData.reset(new ProductBuildData); + product->buildData->targetArtifacts << root; + return product; +} + +// root appears as a child, but in a different tree +ResolvedProductConstPtr TestBuildGraph::productWithNoCycle() +{ + Artifact * const root = new Artifact; + Artifact * const root2 = new Artifact; + m_artifacts << root << root2; + root2->children.insert(root); + const ResolvedProductPtr product = ResolvedProduct::create(); + product->buildData.reset(new ProductBuildData); + product->buildData->targetArtifacts << root << root2; + return product; +} + +void TestBuildGraph::testCycle() +{ + QVERIFY(cycleDetected(productWithDirectCycle())); + QVERIFY(cycleDetected(productWithLessDirectCycle())); + QVERIFY(!cycleDetected(productWithNoCycle())); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/tst_buildgraph.h b/src/lib/corelib/buildgraph/tst_buildgraph.h new file mode 100644 index 000000000..470300c4b --- /dev/null +++ b/src/lib/corelib/buildgraph/tst_buildgraph.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 TST_BUILDGRAPH_H +#define TST_BUILDGRAPH_H + +#include <buildgraph/forward_decls.h> +#include <language/forward_decls.h> +#include <logging/ilogsink.h> +#include <tools/qbs_export.h> + +#include <QList> +#include <QObject> + +namespace qbs { +namespace Internal { + +class QBS_EXPORT TestBuildGraph : public QObject +{ + Q_OBJECT +public: + TestBuildGraph(ILogSink *logSink); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testCycle(); + +private: + ResolvedProductConstPtr productWithDirectCycle(); + ResolvedProductConstPtr productWithLessDirectCycle(); + ResolvedProductConstPtr productWithNoCycle(); + bool cycleDetected(const ResolvedProductConstPtr &product); + + QList<Artifact *> m_artifacts; + ILogSink * const m_logSink; +}; + +} // namespace Internal +} // namespace qbs + +#endif // TST_BUILDGRAPH_H diff --git a/src/lib/corelib/corelib.pro b/src/lib/corelib/corelib.pro new file mode 100644 index 000000000..2eb02e7a3 --- /dev/null +++ b/src/lib/corelib/corelib.pro @@ -0,0 +1,31 @@ +TARGET = qbscore +include(../library.pri) + +QT += script gui +all_tests:QT += testlib + +INCLUDEPATH += $$PWD + +CONFIG += depend_includepath +DEFINES += QT_CREATOR QML_BUILD_STATIC_LIB # needed for QmlJS + +DEFINES += SRCDIR=\\\"$$PWD\\\" + +include(api/api.pri) +include(buildgraph/buildgraph.pri) +include(jsextensions/jsextensions.pri) +include(language/language.pri) +include(logging/logging.pri) +include(parser/parser.pri) +include(tools/tools.pri) + +HEADERS += \ + qbs.h + +!qbs_no_dev_install { + qbs_h.files = qbs.h + qbs_h.path = $${QBS_INSTALL_PREFIX}/include/qbs + use_pri.files = use_installed_corelib.pri ../../../qbs_version.pri + use_pri.path = $${qbs_h.path} + INSTALLS += qbs_h use_pri +} diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs new file mode 100644 index 000000000..e14ba956c --- /dev/null +++ b/src/lib/corelib/corelib.qbs @@ -0,0 +1,341 @@ +import qbs 1.0 +import "../Library.qbs" as QbsLibrary + +QbsLibrary { + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["gui", "script", "xml"] } + Depends { condition: project.enableUnitTests; name: "Qt.test" } + name: "qbscore" + cpp.includePaths: base.concat([ + ".", + "../.." // for the plugin headers + ]) + cpp.defines: base.concat([ + "QBS_VERSION=\"" + version + "\"", + "QT_CREATOR", "QML_BUILD_STATIC_LIB", // needed for QmlJS + "SRCDIR=\"" + path + "\"" + ]) + + Group { + name: product.name + files: ["qbs.h"] + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + } + Group { + name: "api" + prefix: name + '/' + files: [ + "changeset.cpp", + "changeset.h", + "internaljobs.cpp", + "internaljobs.h", + "jobs.cpp", + "project.cpp", + "projectdata.cpp", + "projectdata_p.h", + "projectfileupdater.cpp", + "projectfileupdater.h", + "qmljsrewriter.cpp", + "qmljsrewriter.h", + "propertymap_p.h", + "runenvironment.cpp", + ] + } + Group { + name: "public api headers" + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + "/api" + prefix: "api/" + files: [ + "jobs.h", + "project.h", + "projectdata.h", + "runenvironment.h" + ] + } + Group { + name: "buildgraph" + prefix: name + '/' + files: [ + "abstractcommandexecutor.cpp", + "abstractcommandexecutor.h", + "artifact.cpp", + "artifact.h", + "artifactcleaner.cpp", + "artifactcleaner.h", + "artifactlist.cpp", + "artifactlist.h", + "artifactvisitor.cpp", + "artifactvisitor.h", + "automoc.cpp", + "automoc.h", + "buildgraph.cpp", + "buildgraph.h", + "buildgraphloader.cpp", + "buildgraphloader.h", + "command.cpp", + "command.h", + "cycledetector.cpp", + "cycledetector.h", + "executor.cpp", + "executor.h", + "executorjob.cpp", + "executorjob.h", + "filedependency.cpp", + "filedependency.h", + "inputartifactscanner.cpp", + "inputartifactscanner.h", + "jscommandexecutor.cpp", + "jscommandexecutor.h", + "processcommandexecutor.cpp", + "processcommandexecutor.h", + "productbuilddata.cpp", + "productbuilddata.h", + "productinstaller.cpp", + "productinstaller.h", + "projectbuilddata.cpp", + "projectbuilddata.h", + "rulegraph.cpp", + "rulegraph.h", + "rulesapplicator.cpp", + "rulesapplicator.h", + "rulesevaluationcontext.cpp", + "rulesevaluationcontext.h", + "scanresultcache.cpp", + "scanresultcache.h", + "timestampsupdater.cpp", + "timestampsupdater.h", + "transformer.cpp", + "transformer.h" + ] + } + Group { + name: "public buildgraph headers" + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + "/buildgraph" + files: "buildgraph/forward_decls.h" + } + Group { + name: "jsextensions" + prefix: name + '/' + files: [ + "file.cpp", + "file.h", + "jsextensions.cpp", + "jsextensions.h", + "moduleproperties.cpp", + "moduleproperties.h", + "process.cpp", + "process.h", + "textfile.cpp", + "textfile.h", + "domxml.cpp", + "domxml.h" + ] + } + Group { + name: "language" + prefix: name + '/' + files: [ + "artifactproperties.cpp", + "artifactproperties.h", + "asttools.cpp", + "asttools.h", + "builtindeclarations.cpp", + "builtindeclarations.h", + "builtinvalue.cpp", + "builtinvalue.h", + "evaluationdata.h", + "evaluator.cpp", + "evaluator.h", + "evaluatorscriptclass.cpp", + "evaluatorscriptclass.h", + "filecontext.cpp", + "filecontext.h", + "filetags.cpp", + "filetags.h", + "functiondeclaration.h", + "identifiersearch.cpp", + "identifiersearch.h", + "importversion.cpp", + "importversion.h", + "item.cpp", + "item.h", + "itemdeclaration.cpp", + "itemdeclaration.h", + "itemobserver.h", + "itempool.cpp", + "itempool.h", + "itemreader.cpp", + "itemreader.h", + "itemreaderastvisitor.cpp", + "itemreaderastvisitor.h", + "jsimports.h", + "language.cpp", + "language.h", + "loader.cpp", + "loader.h", + "moduleloader.cpp", + "moduleloader.h", + "preparescriptobserver.cpp", + "preparescriptobserver.h", + "projectresolver.cpp", + "projectresolver.h", + "property.h", + "propertydeclaration.cpp", + "propertydeclaration.h", + "propertymapinternal.cpp", + "propertymapinternal.h", + "scriptengine.cpp", + "scriptengine.h", + "scriptpropertyobserver.h", + "value.cpp", + "value.h" + ] + } + Group { + name: "public language headers" + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + "/language" + files: "language/forward_decls.h" + } + Group { + name: "logging" + prefix: name + '/' + files: [ + "ilogsink.cpp", + "logger.cpp", + "logger.h", + "translator.h" + ] + } + Group { + name: "public logging headers" + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + "/logging" + files: "logging/ilogsink.h" + } + Group { + name: "parser" + prefix: name + '/' + files: [ + "qmlerror.cpp", + "qmlerror.h", + "qmljsast.cpp", + "qmljsast_p.h", + "qmljsastfwd_p.h", + "qmljsastvisitor.cpp", + "qmljsastvisitor_p.h", + "qmljsengine_p.cpp", + "qmljsengine_p.h", + "qmljsglobal_p.h", + "qmljsgrammar.cpp", + "qmljsgrammar_p.h", + "qmljskeywords_p.h", + "qmljslexer.cpp", + "qmljslexer_p.h", + "qmljsmemorypool_p.h", + "qmljsparser.cpp", + "qmljsparser_p.h" + ] + } + Group { + name: "tools" + prefix: name + '/' + files: [ + "buildoptions.cpp", + "cleanoptions.cpp", + "codelocation.cpp", + "error.cpp", + "fileinfo.cpp", + "fileinfo.h", + "filetime.h", + "hostosinfo.h", + "id.cpp", + "id.h", + "installoptions.cpp", + "persistence.cpp", + "persistence.h", + "persistentobject.h", + "preferences.cpp", + "processresult.cpp", + "processresult_p.h", + "profile.cpp", + "progressobserver.cpp", + "progressobserver.h", + "propertyfinder.cpp", + "propertyfinder.h", + "qbsassert.cpp", + "qbsassert.h", + "qttools.cpp", + "qttools.h", + "scannerpluginmanager.cpp", + "scannerpluginmanager.h", + "scripttools.cpp", + "scripttools.h", + "settings.cpp", + "setupprojectparameters.cpp", + "weakpointer.h" + ] + } + Group { + name: "public tools headers" + prefix: "tools/" + files: [ + "buildoptions.h", + "cleanoptions.h", + "codelocation.h", + "error.h", + "installoptions.h", + "preferences.h", + "processresult.h", + "profile.h", + "qbs_export.h", + "settings.h", + "setupprojectparameters.h", + ] + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + "/tools" + } + Group { + condition: qbs.targetOS.contains("windows") + name: "tools (Windows)" + prefix: "tools/" + files: [ + "filetime_win.cpp" + ] + } + Group { + condition: qbs.targetOS.contains("unix") + name: "tools (Unix)" + prefix: "tools/" + files: [ + "filetime_unix.cpp" + ] + } + Group { + name: "use_installed.pri" + files: [ + "use_installed_corelib.pri", + "../../../qbs_version.pri" + ] + qbs.install: project.installApiHeaders + qbs.installDir: headerInstallPrefix + } + Group { + condition: project.enableUnitTests + name: "tests" + files: [ + "buildgraph/tst_buildgraph.cpp", + "buildgraph/tst_buildgraph.h", + "language/tst_language.cpp", + "language/tst_language.h", + "tools/tst_tools.h", + "tools/tst_tools.cpp" + ] + } + Export { + Depends { name: "Qt"; submodules: ["script", "xml"] } + } +} diff --git a/src/lib/corelib/jsextensions/domxml.cpp b/src/lib/corelib/jsextensions/domxml.cpp new file mode 100644 index 000000000..a66f2b21b --- /dev/null +++ b/src/lib/corelib/jsextensions/domxml.cpp @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org> +** 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 "domxml.h" + +#include <QFile> +#include <QScriptEngine> + + +namespace qbs { +namespace Internal { + +void initializeJsExtensionXml(QScriptValue extensionObject) +{ + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&XmlDomDocument::staticMetaObject, engine->newFunction(&XmlDomDocument::ctor)); + extensionObject.setProperty("XmlDomDocument", obj); + obj = engine->newQMetaObject(&XmlDomNode::staticMetaObject, engine->newFunction(&XmlDomNode::ctor)); + extensionObject.setProperty("XmlDomElement", obj); +} + +QScriptValue XmlDomDocument::ctor(QScriptContext *context, QScriptEngine *engine) +{ + XmlDomDocument *xml = 0; + switch (context->argumentCount()) { + case 0: + xml = new XmlDomDocument(context); + break; + case 1: + xml = new XmlDomDocument(context, context->argument(0).toString()); + break; + default: + return context->throwError("DomXml(QString file = QLatin1String(\"\"))"); + } + QScriptValue obj = engine->newQObject(xml, QScriptEngine::ScriptOwnership); + return obj; +} + +QScriptValue XmlDomDocument::documentElement() +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.documentElement()), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createElement(const QString &tagName) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createCDATASection(const QString &value) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createCDATASection(value)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createTextNode(const QString &value) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createTextNode(value)), QScriptEngine::ScriptOwnership); +} + +bool XmlDomDocument::setContent(const QString &content) +{ + return m_domDocument.setContent(content); +} + +QString XmlDomDocument::toString(int indent) +{ + return m_domDocument.toString(indent); +} + +void XmlDomDocument::save(const QString &filePath, int indent) +{ + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly)) { + context()->throwError(QString::fromLatin1("unable to open '%1'") + .arg(filePath)); + return; + } + + QByteArray buff(m_domDocument.toByteArray(indent)); + if (buff.size() != f.write(buff)) + { + context()->throwError(f.errorString()); + f.close(); + return; + } + + f.close(); + if (f.error() != QFile::NoError) + context()->throwError(f.errorString()); +} + +void XmlDomDocument::load(const QString &filePath) +{ + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) { + context()->throwError(QString::fromLatin1("unable to open '%1'") + .arg(filePath)); + return; + } + + QString errorMsg; + if (!m_domDocument.setContent(&f, &errorMsg)) { + context()->throwError(errorMsg); + return; + } +} + +XmlDomDocument::XmlDomDocument(QScriptContext *context, const QString &name):m_domDocument(name) +{ + Q_UNUSED(context) + m_domNode = m_domDocument; +} + +QScriptValue XmlDomNode::ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context) + return engine->newQObject(new XmlDomNode(), QScriptEngine::ScriptOwnership); +} + +bool XmlDomNode::isElement() const +{ + return m_domNode.isElement(); +} + +bool XmlDomNode::isCDATASection() const +{ + return m_domNode.isCDATASection(); +} + +bool XmlDomNode::isText() const +{ + return m_domNode.isText(); +} + +QString XmlDomNode::attribute(const QString &name, const QString &defValue) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return defValue; + } + return el.attribute(name, defValue); +} + +void XmlDomNode::setAttribute(const QString &name, const QString &value) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return; + } + el.setAttribute(name, value); +} + +bool XmlDomNode::hasAttribute(const QString &name) const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return false; + } + return el.hasAttribute(name); +} + +QString XmlDomNode::tagName() const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return QString(); + } + return el.tagName(); +} + +void XmlDomNode::setTagName(const QString &name) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return; + } + el.setTagName(name); +} + +QString XmlDomNode::text() const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QString::fromLatin1("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return QString(); + } + return el.text(); +} + +QString XmlDomNode::data() const +{ + if (m_domNode.isText()) + return m_domNode.toText().data(); + if (m_domNode.isCDATASection()) + return m_domNode.toCDATASection().data(); + if (m_domNode.isCharacterData()) + return m_domNode.toCharacterData().data(); + context()->throwError(QString::fromLatin1("Node '%1' is not a character data node").arg(m_domNode.nodeName())); + return QString(); +} + +void XmlDomNode::setData(const QString &v) const +{ + if (m_domNode.isText()) + return m_domNode.toText().setData(v); + if (m_domNode.isCDATASection()) + return m_domNode.toCDATASection().setData(v); + if (m_domNode.isCharacterData()) + return m_domNode.toCharacterData().setData(v); + context()->throwError(QString::fromLatin1("Node '%1' is not a character data node").arg(m_domNode.nodeName())); + return; +} + +void XmlDomNode::clear() +{ + m_domNode.clear(); +} + +bool XmlDomNode::hasAttributes() const +{ + return m_domNode.hasAttributes(); +} + +bool XmlDomNode::hasChildNodes() const +{ + return m_domNode.hasChildNodes(); +} + +QScriptValue XmlDomNode::parentNode() const +{ + return engine()->newQObject(new XmlDomNode(m_domNode.parentNode()), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::firstChild(const QString &tagName) +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.firstChild()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.firstChildElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::lastChild(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.lastChild()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.lastChildElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::previousSibling(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.previousSibling()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.previousSiblingElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::nextSibling(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.nextSibling()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.nextSiblingElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::appendChild(QScriptValue newChild) +{ + XmlDomNode *newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); + if (!newNode) { + context()->throwError(QString::fromLatin1("First argument is not a XmlDomNode object")); + return QScriptValue(); + } + return engine()->newQObject(new XmlDomNode(m_domNode.appendChild(newNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::insertBefore(const QScriptValue &newChild, const QScriptValue &refChild) +{ + XmlDomNode *newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); + if (!newNode) { + context()->throwError(QString::fromLatin1("First argument is not a XmlDomNode object")); + return QScriptValue(); + } + + XmlDomNode *refNode = qobject_cast<XmlDomNode*>(refChild.toQObject()); + if (!refNode) { + context()->throwError(QString::fromLatin1("Second argument is not a XmlDomNode object")); + return QScriptValue(); + } + + return engine()->newQObject(new XmlDomNode(m_domNode.insertBefore(newNode->m_domNode, refNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::insertAfter(const QScriptValue &newChild, const QScriptValue &refChild) +{ + XmlDomNode *newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); + if (!newNode) { + context()->throwError(QString::fromLatin1("First argument is not a XmlDomNode object")); + return QScriptValue(); + } + + XmlDomNode *refNode = qobject_cast<XmlDomNode*>(refChild.toQObject()); + if (!refNode) { + context()->throwError(QString::fromLatin1("Second argument is not a XmlDomNode object")); + return QScriptValue(); + } + + return engine()->newQObject(new XmlDomNode(m_domNode.insertAfter(newNode->m_domNode, refNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::replaceChild(const QScriptValue &newChild, const QScriptValue &oldChild) +{ + XmlDomNode *newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); + if (!newNode) { + context()->throwError(QString::fromLatin1("First argument is not a XmlDomNode object")); + return QScriptValue(); + } + + XmlDomNode *oldNode = qobject_cast<XmlDomNode*>(oldChild.toQObject()); + if (!oldNode) { + context()->throwError(QString::fromLatin1("Second argument is not a XmlDomNode object")); + return QScriptValue(); + } + + return engine()->newQObject(new XmlDomNode(m_domNode.replaceChild(newNode->m_domNode, oldNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::removeChild(const QScriptValue &oldChild) +{ + XmlDomNode *oldNode = qobject_cast<XmlDomNode*>(oldChild.toQObject()); + if (!oldNode) { + context()->throwError(QString::fromLatin1("First argument is not a XmlDomNode object")); + return QScriptValue(); + } + + return engine()->newQObject(new XmlDomNode(m_domNode.removeChild(oldNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +XmlDomNode::XmlDomNode(const QDomNode &other) +{ + m_domNode = other; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/domxml.h b/src/lib/corelib/jsextensions/domxml.h new file mode 100644 index 000000000..7df603328 --- /dev/null +++ b/src/lib/corelib/jsextensions/domxml.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org> +** 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 DOMXML_H +#define DOMXML_H + +#include <QDomDocument> +#include <QDomNode> +#include <QObject> +#include <QScriptValue> +#include <QScriptable> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class XmlDomDocument; + +void initializeJsExtensionXml(QScriptValue extensionObject); + +class XmlDomNode: public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + + Q_INVOKABLE bool isElement() const; + Q_INVOKABLE bool isCDATASection() const; + Q_INVOKABLE bool isText() const; + + Q_INVOKABLE QString attribute(const QString & name, const QString & defValue = QString()); + Q_INVOKABLE void setAttribute(const QString & name, const QString & value); + Q_INVOKABLE bool hasAttribute(const QString & name) const; + Q_INVOKABLE QString tagName() const; + Q_INVOKABLE void setTagName(const QString & name); + + Q_INVOKABLE QString text() const; + + Q_INVOKABLE QString data() const; + Q_INVOKABLE void setData(const QString &v) const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE bool hasAttributes() const; + Q_INVOKABLE bool hasChildNodes() const; + Q_INVOKABLE QScriptValue parentNode() const; + Q_INVOKABLE QScriptValue firstChild(const QString & tagName = QString()); + Q_INVOKABLE QScriptValue lastChild(const QString & tagName = QString()) const; + Q_INVOKABLE QScriptValue previousSibling(const QString & tagName = QString()) const; + Q_INVOKABLE QScriptValue nextSibling(const QString & tagName = QString()) const; + + Q_INVOKABLE QScriptValue appendChild(QScriptValue newChild); + Q_INVOKABLE QScriptValue insertBefore(const QScriptValue& newChild, const QScriptValue& refChild); + Q_INVOKABLE QScriptValue insertAfter(const QScriptValue& newChild, const QScriptValue& refChild); + Q_INVOKABLE QScriptValue replaceChild(const QScriptValue& newChild, const QScriptValue& oldChild); + Q_INVOKABLE QScriptValue removeChild(const QScriptValue& oldChild); + +protected: + friend class XmlDomDocument; + XmlDomNode(const QDomNode &other = QDomNode()); + QDomNode m_domNode; +}; + +class XmlDomDocument: public XmlDomNode +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + Q_INVOKABLE QScriptValue documentElement(); + Q_INVOKABLE QScriptValue createElement(const QString & tagName); + Q_INVOKABLE QScriptValue createCDATASection(const QString & value); + Q_INVOKABLE QScriptValue createTextNode(const QString & value); + + Q_INVOKABLE bool setContent(const QString & content); + Q_INVOKABLE QString toString(int indent = 1); + + Q_INVOKABLE void save(const QString & filePath, int indent = 1); + Q_INVOKABLE void load(const QString & filePath); + +protected: + XmlDomDocument(QScriptContext *context, const QString &name = QString()); + +private: + QDomDocument m_domDocument; +}; + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::XmlDomDocument *) +Q_DECLARE_METATYPE(qbs::Internal::XmlDomNode *) + +#endif // DOMXML_H diff --git a/src/lib/corelib/jsextensions/file.cpp b/src/lib/corelib/jsextensions/file.cpp new file mode 100644 index 000000000..1e6f2947c --- /dev/null +++ b/src/lib/corelib/jsextensions/file.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 "file.h" + +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> + +#include <QFileInfo> +#include <QScriptEngine> + +namespace qbs { +namespace Internal { + +class File +{ + friend void initializeJsExtensionFile(QScriptValue extensionObject); + +private: + static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_copy(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_exists(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_lastModified(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_remove(QScriptContext *context, QScriptEngine *engine); +}; + + +void initializeJsExtensionFile(QScriptValue extensionObject) +{ + QScriptEngine *engine = extensionObject.engine(); + QScriptValue fileObj = engine->newFunction(File::js_ctor); + fileObj.setProperty("copy", engine->newFunction(File::js_copy)); + fileObj.setProperty("exists", engine->newFunction(File::js_exists)); + fileObj.setProperty("lastModified", engine->newFunction(File::js_lastModified)); + fileObj.setProperty("remove", engine->newFunction(File::js_remove)); + extensionObject.setProperty("File", fileObj); +} + +QScriptValue File::js_ctor(QScriptContext *context, QScriptEngine *engine) +{ + // Add instance variables here etc., if needed. + Q_UNUSED(context); + Q_UNUSED(engine); + return true; +} + +QScriptValue File::js_copy(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("copy expects 2 arguments")); + } + + const QString sourceFile = context->argument(0).toString(); + const QString targetFile = context->argument(1).toString(); + QString errorMessage; + if (Q_UNLIKELY(!copyFileRecursion(sourceFile, targetFile, false, &errorMessage))) + return context->throwError(errorMessage); + return true; +} + +QScriptValue File::js_exists(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("exist expects 1 argument")); + } + const QString filePath = context->argument(0).toString(); + const bool exists = FileInfo::exists(filePath); + ScriptEngine * const se = static_cast<ScriptEngine *>(engine); + se->addFileExistsResult(filePath, exists); + return exists; +} + +QScriptValue File::js_remove(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("remove expects 1 argument")); + } + QString fileName = context->argument(0).toString(); + + QString errorMessage; + if (Q_UNLIKELY(!removeFileRecursion(QFileInfo(fileName), &errorMessage))) + return context->throwError(errorMessage); + return true; +} + +QScriptValue File::js_lastModified(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("File.lastModified() expects an argument")); + } + const QString filePath = context->argument(0).toString(); + const FileTime timestamp = FileInfo(filePath).lastModified(); + ScriptEngine * const se = static_cast<ScriptEngine *>(engine); + se->addFileLastModifiedResult(filePath, timestamp); + return static_cast<qsreal>(timestamp.m_fileTime); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/file.h b/src/lib/corelib/jsextensions/file.h new file mode 100644 index 000000000..874c40c71 --- /dev/null +++ b/src/lib/corelib/jsextensions/file.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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_FILE_H +#define QBS_FILE_H + +#include <QScriptContext> +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +void initializeJsExtensionFile(QScriptValue extensionObject); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILE_H diff --git a/src/lib/corelib/jsextensions/jsextensions.cpp b/src/lib/corelib/jsextensions/jsextensions.cpp new file mode 100644 index 000000000..b6b5e099b --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.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 "jsextensions.h" + +#include "domxml.h" +#include "file.h" +#include "process.h" +#include "textfile.h" + +namespace qbs { +namespace Internal { + +void JsExtensions::setupExtensions(const QStringList &names, QScriptValue scope) +{ + foreach (const QString &name, names) + initializers().value(name)(scope); +} + +bool JsExtensions::hasExtension(const QString &name) +{ + return initializers().contains(name); +} + +JsExtensions::InitializerMap JsExtensions::initializers() +{ + if (m_initializers.isEmpty()) { + m_initializers.insert(QLatin1String("File"), &initializeJsExtensionFile); + m_initializers.insert(QLatin1String("Process"), &initializeJsExtensionProcess); + m_initializers.insert(QLatin1String("Xml"), &initializeJsExtensionXml); + m_initializers.insert(QLatin1String("TextFile"), &initializeJsExtensionTextFile); + } + return m_initializers; +} + +JsExtensions::InitializerMap JsExtensions::m_initializers; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/jsextensions.h b/src/lib/corelib/jsextensions/jsextensions.h new file mode 100644 index 000000000..e268ec215 --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.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_JSEXTENSIONS_H +#define QBS_JSEXTENSIONS_H + +#include <QHash> +#include <QStringList> + +QT_BEGIN_NAMESPACE +class QScriptValue; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class JsExtensions +{ +public: + static void setupExtensions(const QStringList &names, QScriptValue scope); + static bool hasExtension(const QString &name); + +private: + typedef QHash<QString, void (*)(QScriptValue)> InitializerMap; + static InitializerMap initializers(); + + static InitializerMap m_initializers; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/jsextensions/jsextensions.pri b/src/lib/corelib/jsextensions/jsextensions.pri new file mode 100644 index 000000000..21c97ef64 --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.pri @@ -0,0 +1,17 @@ +QT += xml + +HEADERS += \ + $$PWD/file.h \ + $$PWD/textfile.h \ + $$PWD/process.h \ + $$PWD/moduleproperties.h \ + $$PWD/domxml.h \ + $$PWD/jsextensions.h + +SOURCES += \ + $$PWD/file.cpp \ + $$PWD/textfile.cpp \ + $$PWD/process.cpp \ + $$PWD/moduleproperties.cpp \ + $$PWD/domxml.cpp \ + $$PWD/jsextensions.cpp diff --git a/src/lib/corelib/jsextensions/moduleproperties.cpp b/src/lib/corelib/jsextensions/moduleproperties.cpp new file mode 100644 index 000000000..83d92e776 --- /dev/null +++ b/src/lib/corelib/jsextensions/moduleproperties.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 "moduleproperties.h" + +#include <buildgraph/artifact.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/propertyfinder.h> + +#include <QScriptEngine> + +namespace qbs { +namespace Internal { + +static QString ptrKey() { return QLatin1String("__internalPtr"); } +static QString typeKey() { return QLatin1String("__type"); } +static QString productType() { return QLatin1String("product"); } +static QString artifactType() { return QLatin1String("artifact"); } + +void ModuleProperties::init(QScriptValue productObject, + const ResolvedProductConstPtr &product) +{ + init(productObject, product.data(), productType()); +} + +void ModuleProperties::init(QScriptValue artifactObject, const Artifact *artifact) +{ + init(artifactObject, artifact, artifactType()); +} + +void ModuleProperties::init(QScriptValue objectWithProperties, const void *ptr, + const QString &type) +{ + QScriptEngine * const engine = objectWithProperties.engine(); + objectWithProperties.setProperty("moduleProperties", + engine->newFunction(ModuleProperties::js_moduleProperties, 2)); + objectWithProperties.setProperty("moduleProperty", + engine->newFunction(ModuleProperties::js_moduleProperty, 2)); + objectWithProperties.setProperty(ptrKey(), engine->toScriptValue(quintptr(ptr))); + objectWithProperties.setProperty(typeKey(), type); +} + +QScriptValue ModuleProperties::js_moduleProperties(QScriptContext *context, QScriptEngine *engine) +{ + try { + return moduleProperties(context, engine, false); + } catch (const ErrorInfo &e) { + return context->throwError(e.toString()); + } +} + +QScriptValue ModuleProperties::js_moduleProperty(QScriptContext *context, QScriptEngine *engine) +{ + try { + return moduleProperties(context, engine, true); + } catch (const ErrorInfo &e) { + return context->throwError(e.toString()); + } +} + +QScriptValue ModuleProperties::moduleProperties(QScriptContext *context, QScriptEngine *engine, + bool oneValue) +{ + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("Function moduleProperties() expects 2 arguments")); + } + + const QScriptValue objectWithProperties = context->thisObject(); + const QScriptValue typeScriptValue = objectWithProperties.property(typeKey()); + if (Q_UNLIKELY(!typeScriptValue.isString())) { + return context->throwError(QScriptContext::TypeError, + QLatin1String("Internal error: __type not set up")); + } + const QScriptValue ptrScriptValue = objectWithProperties.property(ptrKey()); + if (Q_UNLIKELY(!ptrScriptValue.isNumber())) { + return context->throwError(QScriptContext::TypeError, + QLatin1String("Internal error: __internalPtr not set up")); + } + + const void *ptr = reinterpret_cast<const void *>(qscriptvalue_cast<quintptr>(ptrScriptValue)); + PropertyMapConstPtr properties; + const Artifact *artifact = 0; + if (typeScriptValue.toString() == productType()) { + properties = static_cast<const ResolvedProduct *>(ptr)->properties; + } else if (typeScriptValue.toString() == artifactType()) { + artifact = static_cast<const Artifact *>(ptr); + properties = artifact->properties; + } else { + return context->throwError(QScriptContext::TypeError, + QLatin1String("Internal error: invalid type")); + } + + ScriptEngine * const qbsEngine = static_cast<ScriptEngine *>(engine); + const QString moduleName = internalModuleName(context->argument(0).toString()); + const QString propertyName = context->argument(1).toString(); + + QVariant value = qbsEngine->retrieveFromPropertyCache(moduleName, propertyName, properties); + if (!value.isValid()) { + if (oneValue) + value = PropertyFinder().propertyValue(properties->value(), moduleName, propertyName); + else + value = PropertyFinder().propertyValues(properties->value(), moduleName, propertyName); + const Property p(moduleName, propertyName, value); + if (artifact) + qbsEngine->addPropertyRequestedFromArtifact(artifact, p); + else + qbsEngine->addPropertyRequestedInScript(p); + + // Cache the variant value. We must not cache the QScriptValue here, because it's a + // reference and the user might change the actual object. + qbsEngine->addToPropertyCache(moduleName, propertyName, properties, value); + } + return engine->toScriptValue(value); +} + +QString ModuleProperties::internalModuleName(const QString &name) +{ + QString result = name; + result.replace(QLatin1Char('.'), QLatin1Char('/')); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/moduleproperties.h b/src/lib/corelib/jsextensions/moduleproperties.h new file mode 100644 index 000000000..81f27f6ec --- /dev/null +++ b/src/lib/corelib/jsextensions/moduleproperties.h @@ -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. +** +****************************************************************************/ + +#ifndef QBS_MODULEPROPERTIES_H +#define QBS_MODULEPROPERTIES_H + +#include <buildgraph/forward_decls.h> +#include <language/forward_decls.h> + +#include <QScriptContext> +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class ModuleProperties +{ +public: + static void init(QScriptValue productObject, const ResolvedProductConstPtr &product); + static void init(QScriptValue artifactObject, const Artifact *artifact); + +private: + static void init(QScriptValue objectWithProperties, const void *ptr, const QString &type); + + static QScriptValue js_moduleProperties(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_moduleProperty(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue moduleProperties(QScriptContext *context, QScriptEngine *engine, + bool oneValue); + static QString internalModuleName(const QString &name); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_MODULEPROPERTIES_H diff --git a/src/lib/corelib/jsextensions/process.cpp b/src/lib/corelib/jsextensions/process.cpp new file mode 100644 index 000000000..3cf74ab31 --- /dev/null +++ b/src/lib/corelib/jsextensions/process.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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 "process.h" + +#include <logging/translator.h> +#include <tools/hostosinfo.h> + +#include <QProcess> +#include <QScriptEngine> +#include <QScriptValue> +#include <QTextCodec> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +void initializeJsExtensionProcess(QScriptValue extensionObject) +{ + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&Process::staticMetaObject, engine->newFunction(&Process::ctor)); + extensionObject.setProperty("Process", obj); +} + +QScriptValue Process::ctor(QScriptContext *context, QScriptEngine *engine) +{ + Process *t; + switch (context->argumentCount()) { + case 0: + t = new Process(context); + break; + default: + return context->throwError("Process()"); + } + + QScriptValue obj = engine->newQObject(t, QScriptEngine::ScriptOwnership); + + // Get environment + QVariant v = engine->property("_qbs_procenv"); + if (!v.isNull()) + t->m_environment + = QProcessEnvironment(*reinterpret_cast<QProcessEnvironment*>(v.value<void*>())); + + return obj; +} + +Process::~Process() +{ + delete m_textStream; + delete m_qProcess; +} + +Process::Process(QScriptContext *context) +{ + Q_UNUSED(context); + Q_ASSERT(thisObject().engine() == engine()); + + m_qProcess = new QProcess; + m_textStream = new QTextStream(m_qProcess); +} + +QString Process::getEnv(const QString &name) +{ + Q_ASSERT(thisObject().engine() == engine()); + return m_environment.value(name); +} + +void Process::setEnv(const QString &name, const QString &value) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_environment.insert(name, value); +} + +QString Process::workingDirectory() +{ + Q_ASSERT(thisObject().engine() == engine()); + return m_workingDirectory; +} + +void Process::setWorkingDirectory(const QString &dir) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_workingDirectory = dir; +} + +bool Process::start(const QString &program, const QStringList &arguments) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (!m_workingDirectory.isEmpty()) + m_qProcess->setWorkingDirectory(m_workingDirectory); + + m_qProcess->setProcessEnvironment(m_environment); + m_qProcess->start(program, arguments); + return m_qProcess->waitForStarted(); +} + +int Process::exec(const QString &program, const QStringList &arguments, bool throwOnError) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (!start(program, arguments)) { + if (throwOnError) { + context()->throwError(Tr::tr("Error running '%1': %2") + .arg(program, m_qProcess->errorString())); + } + return -1; + } + m_qProcess->closeWriteChannel(); + m_qProcess->waitForFinished(-1); + if (throwOnError) { + if (m_qProcess->error() != QProcess::UnknownError) { + context()->throwError(Tr::tr("Error running '%1': %2") + .arg(program, m_qProcess->errorString())); + } else if (m_qProcess->exitCode() != 0) { + QString errorMessage = Tr::tr("Process '%1' finished with exit code %2.") + .arg(program).arg(m_qProcess->exitCode()); + const QString stdErr = readStdErr(); + if (!stdErr.isEmpty()) + errorMessage.append(Tr::tr(" The standard error output was:\n")).append(stdErr); + context()->throwError(errorMessage); + } + } + if (m_qProcess->error() != QProcess::UnknownError) + return -1; + return m_qProcess->exitCode(); +} + +void Process::close() +{ + Q_ASSERT(thisObject().engine() == engine()); + delete m_qProcess; + m_qProcess = 0; + delete m_textStream; + m_textStream = 0; +} + +bool Process::waitForFinished(int msecs) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (m_qProcess->state() == QProcess::NotRunning) + return true; + return m_qProcess->waitForFinished(msecs); +} + +void Process::terminate() +{ + m_qProcess->terminate(); +} + +void Process::kill() +{ + m_qProcess->kill(); +} + +void Process::setCodec(const QString &codec) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_textStream->setCodec(qPrintable(codec)); +} + +QString Process::readLine() +{ + return m_textStream->readLine(); +} + +QString Process::readStdOut() +{ + return m_textStream->readAll(); +} + +QString Process::readStdErr() +{ + return m_textStream->codec()->toUnicode(m_qProcess->readAllStandardError()); +} + +int Process::exitCode() const +{ + return m_qProcess->exitCode(); +} + +void Process::write(const QString &str) +{ + (*m_textStream) << str; +} + +void Process::writeLine(const QString &str) +{ + (*m_textStream) << str; + if (HostOsInfo::isWindowsHost()) + (*m_textStream) << '\r'; + (*m_textStream) << '\n'; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/process.h b/src/lib/corelib/jsextensions/process.h new file mode 100644 index 000000000..1dea021a3 --- /dev/null +++ b/src/lib/corelib/jsextensions/process.h @@ -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. +** +****************************************************************************/ + +#ifndef QBS_PROCESS_H +#define QBS_PROCESS_H + +#include <QObject> +#include <QProcessEnvironment> +#include <QScriptable> +#include <QVariant> + +QT_BEGIN_NAMESPACE +class QProcess; +class QTextStream; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +void initializeJsExtensionProcess(QScriptValue extensionObject); + +class Process : public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + Process(QScriptContext *context); + ~Process(); + + Q_INVOKABLE QString getEnv(const QString &name); + Q_INVOKABLE void setEnv(const QString &name, const QString &value); + Q_INVOKABLE void setCodec(const QString &codec); + + Q_INVOKABLE QString workingDirectory(); + Q_INVOKABLE void setWorkingDirectory(const QString &dir); + + Q_INVOKABLE bool start(const QString &program, const QStringList &arguments); + Q_INVOKABLE int exec(const QString &program, const QStringList &arguments, + bool throwOnError = false); + Q_INVOKABLE void close(); + Q_INVOKABLE bool waitForFinished(int msecs = 30000); + Q_INVOKABLE void terminate(); + Q_INVOKABLE void kill(); + + Q_INVOKABLE QString readLine(); + Q_INVOKABLE QString readStdOut(); + Q_INVOKABLE QString readStdErr(); + + Q_INVOKABLE void write(const QString &str); + Q_INVOKABLE void writeLine(const QString &str); + + Q_INVOKABLE int exitCode() const; + +private: + QProcess *m_qProcess; + QProcessEnvironment m_environment; + QString m_workingDirectory; + QTextStream *m_textStream; +}; + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::Process *) + +#endif // QBS_PROCESS_H diff --git a/src/lib/corelib/jsextensions/textfile.cpp b/src/lib/corelib/jsextensions/textfile.cpp new file mode 100644 index 000000000..7129ee955 --- /dev/null +++ b/src/lib/corelib/jsextensions/textfile.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** 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 "textfile.h" + +#include <tools/hostosinfo.h> + +#include <QFile> +#include <QScriptEngine> +#include <QScriptValue> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +void initializeJsExtensionTextFile(QScriptValue extensionObject) +{ + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&TextFile::staticMetaObject, engine->newFunction(&TextFile::ctor)); + extensionObject.setProperty("TextFile", obj); +} + +QScriptValue TextFile::ctor(QScriptContext *context, QScriptEngine *engine) +{ + TextFile *t; + switch (context->argumentCount()) { + case 1: + t = new TextFile(context, + context->argument(0).toString()); + break; + case 2: + t = new TextFile(context, + context->argument(0).toString(), + static_cast<OpenMode>(context->argument(1).toInt32()) + ); + break; + case 3: + t = new TextFile(context, + context->argument(0).toString(), + static_cast<OpenMode>(context->argument(1).toInt32()), + context->argument(2).toString() + ); + break; + default: + return context->throwError("TextFile(QString file, OpenMode mode = ReadOnly, QString codec = QLatin1String(\"UTF8\"))"); + } + + QScriptValue obj = engine->newQObject(t, QScriptEngine::ScriptOwnership); +// obj.setProperty("d", engine->newQObject(new FileImplementation(t), +// QScriptEngine::QScriptEngine::QtOwnership)); + return obj; +} + +TextFile::~TextFile() +{ + delete qstream; + delete qfile; +} + +TextFile::TextFile(QScriptContext *context, const QString &file, OpenMode mode, const QString &codec) +{ + Q_UNUSED(codec) + Q_ASSERT(thisObject().engine() == engine()); + TextFile *t = this; + + t->qfile = new QFile(file); + QIODevice::OpenMode m = QIODevice::ReadOnly; + if (mode == ReadWrite) + m = QIODevice::ReadWrite; + else if (mode == ReadOnly) + m = QIODevice::ReadOnly; + else if (mode == WriteOnly) + m = QIODevice::WriteOnly; + if (Q_UNLIKELY(!t->qfile->open(m))) { + delete t->qfile; + t->qfile = 0; + context->throwError(QString::fromLatin1("unable to open '%1'") + .arg(file) + ); + } + + t->qstream = new QTextStream(t->qfile); +} + +void TextFile::close() +{ + Q_ASSERT(thisObject().engine() == engine()); + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (t->qfile) + t->qfile->close(); + delete t->qfile; + t->qfile = 0; + delete t->qstream; + t->qstream = 0; +} + +void TextFile::setCodec(const QString &codec) +{ + Q_ASSERT(thisObject().engine() == engine()); + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qstream) + return; + t->qstream->setCodec(qPrintable(codec)); +} + +QString TextFile::readLine() +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qfile) + return QString(); + return t->qstream->readLine(); +} + +QString TextFile::readAll() +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qfile) + return QString(); + return t->qstream->readAll(); +} + +bool TextFile::atEof() const +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qstream) + return true; + return t->qstream->atEnd(); +} + +void TextFile::truncate() +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qstream) + return; + t->qfile->resize(0); + t->qstream->reset(); +} + +void TextFile::write(const QString &str) +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qstream) + return; + (*t->qstream) << str; +} + +void TextFile::writeLine(const QString &str) +{ + TextFile *t = qscriptvalue_cast<TextFile*>(thisObject()); + if (!t->qstream) + return; + (*t->qstream) << str; + if (HostOsInfo::isWindowsHost()) + (*t->qstream) << '\r'; + (*t->qstream) << '\n'; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/textfile.h b/src/lib/corelib/jsextensions/textfile.h new file mode 100644 index 000000000..ff8aaf43b --- /dev/null +++ b/src/lib/corelib/jsextensions/textfile.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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_TEXTFILE_H +#define QBS_TEXTFILE_H + +#include <QObject> +#include <QScriptable> +#include <QVariant> + +QT_BEGIN_NAMESPACE +class QFile; +class QTextStream; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +void initializeJsExtensionTextFile(QScriptValue extensionObject); + +class TextFile : public QObject, public QScriptable +{ + Q_OBJECT + Q_ENUMS(OpenMode) +public: + enum OpenMode { ReadOnly, WriteOnly, ReadWrite }; + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + TextFile(QScriptContext *context, const QString &file, OpenMode mode = ReadOnly, const QString &codec = QLatin1String("UTF8")); + ~TextFile(); + Q_INVOKABLE void close(); + Q_INVOKABLE void setCodec(const QString &codec); + Q_INVOKABLE QString readLine(); + Q_INVOKABLE QString readAll(); + Q_INVOKABLE bool atEof() const; + Q_INVOKABLE void truncate(); + Q_INVOKABLE void write(const QString &str); + Q_INVOKABLE void writeLine(const QString &str); +private: + QFile *qfile; + QTextStream *qstream; +}; + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::TextFile *) + +#endif // QBS_TEXTFILE_H diff --git a/src/lib/corelib/language/artifactproperties.cpp b/src/lib/corelib/language/artifactproperties.cpp new file mode 100644 index 000000000..470163004 --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "artifactproperties.h" +#include <language/propertymapinternal.h> +#include <tools/persistence.h> + +namespace qbs { +namespace Internal { + +ArtifactPropertiesPtr ArtifactProperties::create() +{ + return ArtifactPropertiesPtr(new ArtifactProperties); +} + +ArtifactProperties::ArtifactProperties() +{ +} + +void ArtifactProperties::load(PersistentPool &pool) +{ + pool.stream() >> m_fileTagsFilter; + m_propertyMap = pool.idLoadS<PropertyMapInternal>(); +} + +void ArtifactProperties::store(PersistentPool &pool) const +{ + pool.stream() << m_fileTagsFilter; + pool.store(m_propertyMap); +} + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2) +{ + return ap1.fileTagsFilter() == ap2.fileTagsFilter() + && ap1.propertyMap()->value() == ap2.propertyMap()->value(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/artifactproperties.h b/src/lib/corelib/language/artifactproperties.h new file mode 100644 index 000000000..8c688da20 --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ARTIFACTPROPERTIES_H +#define QBS_ARTIFACTPROPERTIES_H + +#include "language.h" +#include "forward_decls.h" +#include <tools/persistentobject.h> + +namespace qbs { +namespace Internal { + +class ArtifactProperties : public PersistentObject +{ +public: + static ArtifactPropertiesPtr create(); + + void setFileTagsFilter(const FileTags &filter) { m_fileTagsFilter = filter; } + FileTags fileTagsFilter() const { return m_fileTagsFilter; } + + PropertyMapPtr propertyMap() const { return m_propertyMap; } + void setPropertyMapInternal(const PropertyMapPtr &pmap) { m_propertyMap = pmap; } + +private: + ArtifactProperties(); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + FileTags m_fileTagsFilter; + PropertyMapPtr m_propertyMap; +}; + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2); +inline bool operator!=(const ArtifactProperties &ap1, const ArtifactProperties &ap2) { + return !(ap1 == ap2); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTPROPERTIES_H diff --git a/src/lib/corelib/language/asttools.cpp b/src/lib/corelib/language/asttools.cpp new file mode 100644 index 000000000..c394fc187 --- /dev/null +++ b/src/lib/corelib/language/asttools.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "asttools.h" +#include <parser/qmljsast_p.h> + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid) +{ + QStringList result; + for (; qid; qid = qid->next) + result.append(qid->name.toString()); + return result; +} + +CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location) +{ + return CodeLocation(filePath, location.startLine, location.startColumn); +} + +QString textOf(const QString &source, QbsQmlJS::AST::Node *node) +{ + if (!node) + return QString(); + return source.mid(node->firstSourceLocation().begin(), + node->lastSourceLocation().end() - node->firstSourceLocation().begin()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/asttools.h b/src/lib/corelib/language/asttools.h new file mode 100644 index 000000000..cb0f0b852 --- /dev/null +++ b/src/lib/corelib/language/asttools.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ASTTOOLS_H +#define QBS_ASTTOOLS_H + +#include <parser/qmljsastfwd_p.h> +#include <tools/codelocation.h> +#include <QStringList> + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid); +CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location); +QString textOf(const QString &source, QbsQmlJS::AST::Node *node); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ASTTOOLS_H diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp new file mode 100644 index 000000000..4bbc02579 --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "builtindeclarations.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +const char QBS_LANGUAGE_VERSION[] = "1.0"; + +BuiltinDeclarations::BuiltinDeclarations() + : m_languageVersion(QLatin1String(QBS_LANGUAGE_VERSION)) +{ + addArtifactItem(); + addDependsItem(); + addExportItem(); + addFileTaggerItem(); + addGroupItem(); + addModuleItem(); + addProbeItem(); + addProductItem(); + addProjectItem(); + addPropertiesItem(); + addPropertyOptionsItem(); + addRuleItem(); + addSubprojectItem(); + addTransformerItem(); +} + +QString BuiltinDeclarations::languageVersion() const +{ + return m_languageVersion; +} + +QByteArray BuiltinDeclarations::qmlTypeInfo() const +{ + // Header: + QByteArray result; + result.append("import QtQuick.tooling 1.0\n\n"); + result.append("// This file describes the plugin-supplied types contained in the library.\n"); + result.append("// It is used for QML tooling purposes only.\n\n"); + result.append("Module {\n"); + + // Individual Components: + foreach (const QString &component, m_builtins.keys()) { + QByteArray componentName = component.toUtf8(); + result.append(" Component {\n"); + result.append(QByteArray(" name: \"") + componentName + QByteArray("\"\n")); + result.append(" exports: [ \"qbs/"); + result.append(componentName); + result.append(" "); + result.append(QBS_LANGUAGE_VERSION); + result.append("\" ]\n"); + result.append(" prototype: \"QQuickItem\"\n"); + + ItemDeclaration itemDecl = m_builtins.value(component); + foreach (const PropertyDeclaration &property, itemDecl.properties()) { + result.append(" Property { name=\""); + result.append(property.name.toUtf8()); + result.append("\"; "); + switch (property.type) { + case qbs::Internal::PropertyDeclaration::UnknownType: + result.append("type=\"unknown\""); + break; + case qbs::Internal::PropertyDeclaration::Boolean: + result.append("type=\"bool\""); + break; + case qbs::Internal::PropertyDeclaration::Integer: + result.append("type=\"int\""); + break; + case qbs::Internal::PropertyDeclaration::Path: + result.append("type=\"string\""); + break; + case qbs::Internal::PropertyDeclaration::PathList: + result.append("type=\"string\"; isList=true"); + break; + case qbs::Internal::PropertyDeclaration::String: + result.append("type=\"string\""); + break; + case qbs::Internal::PropertyDeclaration::StringList: + result.append("type=\"string\"; isList=true"); + break; + case qbs::Internal::PropertyDeclaration::Variant: + result.append("type=\"QVariant\""); + break; + case qbs::Internal::PropertyDeclaration::Verbatim: + result.append("type=\"string\""); + break; + } + result.append(" }\n"); // Property + } + + result.append(" }\n"); // Component + } + + // Footer: + result.append("}\n"); // Module + return result; +} + +bool BuiltinDeclarations::containsType(const QString &typeName) const +{ + return m_builtins.contains(typeName); +} + +ItemDeclaration BuiltinDeclarations::declarationsForType(const QString &typeName) const +{ + return m_builtins.value(typeName); +} + +void BuiltinDeclarations::setupItemForBuiltinType(Item *item) const +{ + foreach (const PropertyDeclaration &pd, declarationsForType(item->typeName()).properties()) { + item->m_propertyDeclarations.insert(pd.name, pd); + ValuePtr &value = item->m_properties[pd.name]; + if (!value) { + JSSourceValuePtr sourceValue = JSSourceValue::create(); + sourceValue->setFile(item->file()); + sourceValue->setSourceCode(pd.initialValueSource.isEmpty() ? + "undefined" : pd.initialValueSource); + value = sourceValue; + } + } +} + +void BuiltinDeclarations::insert(const ItemDeclaration &decl) +{ + m_builtins.insert(decl.typeName(), decl); +} + +static PropertyDeclaration conditionProperty() +{ + PropertyDeclaration decl(QLatin1String("condition"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("true"); + return decl; +} + +static PropertyDeclaration nameProperty() +{ + return PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String); +} + +static PropertyDeclaration prepareScriptProperty() +{ + PropertyDeclaration decl(QLatin1String("prepare"), PropertyDeclaration::Verbatim); + decl.functionArgumentNames + << QLatin1String("project") << QLatin1String("product") + << QLatin1String("inputs") << QLatin1String("outputs") + << QLatin1String("input") << QLatin1String("output"); + return decl; +} + +void BuiltinDeclarations::addArtifactItem() +{ + ItemDeclaration item(QLatin1String("Artifact")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("fileName"), PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant); + PropertyDeclaration decl(QLatin1String("alwaysUpdated"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("true"); + item << decl; + insert(item); +} + +void BuiltinDeclarations::addDependsItem() +{ + ItemDeclaration item(QLatin1String("Depends")); + item << conditionProperty(); + item << nameProperty(); + item << PropertyDeclaration(QLatin1String("submodules"), PropertyDeclaration::Variant); + PropertyDeclaration requiredDecl(QLatin1String("required"), PropertyDeclaration::Boolean); + requiredDecl.initialValueSource = QLatin1String("true"); + item << requiredDecl; + insert(item); +} + +void BuiltinDeclarations::addExportItem() +{ + ItemDeclaration item(QLatin1String("Export")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Depends") + << QLatin1String("Module") // needed, because we're adding module instances internally + ); + insert(item); +} + +void BuiltinDeclarations::addFileTaggerItem() +{ + ItemDeclaration item(QLatin1String("FileTagger")); + + // TODO: Remove in 1.3 + item << PropertyDeclaration(QLatin1String("pattern"), PropertyDeclaration::StringList); + + item << PropertyDeclaration(QLatin1String("patterns"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant); + insert(item); +} + +void BuiltinDeclarations::addGroupItem() +{ + ItemDeclaration item(QLatin1String("Group")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("files"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("fileTagsFilter"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("excludeFiles"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("prefix"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + PropertyDeclaration declaration; + declaration.name = QLatin1String("overrideTags"); + declaration.type = PropertyDeclaration::Boolean; + declaration.flags = PropertyDeclaration::PropertyNotAvailableInConfig; + declaration.initialValueSource = QLatin1String("true"); + item << declaration; + insert(item); +} + +void BuiltinDeclarations::addModuleItem() +{ + ItemDeclaration item(QLatin1String("Module")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") // needed, because we're adding module instances internally + ); + item << nameProperty(); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("setupBuildEnvironment"), + PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("setupRunEnvironment"), + PropertyDeclaration::Verbatim); + item << PropertyDeclaration(QLatin1String("validate"), + PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("additionalProductFileTags"), + PropertyDeclaration::Variant); + PropertyDeclaration presentDecl(QLatin1String("present"), PropertyDeclaration::Boolean); + presentDecl.initialValueSource = QLatin1String("true"); + item << presentDecl; + insert(item); +} + +void BuiltinDeclarations::addProbeItem() +{ + ItemDeclaration item(QLatin1String("Probe")); + item << conditionProperty(); + PropertyDeclaration foundProperty(QLatin1String("found"), PropertyDeclaration::Boolean); + foundProperty.initialValueSource = QLatin1String("false"); + item << foundProperty; + item << PropertyDeclaration(QLatin1String("configure"), PropertyDeclaration::Verbatim); + insert(item); +} + +void BuiltinDeclarations::addProductItem() +{ + ItemDeclaration item(QLatin1String("Product")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") + << QLatin1String("Depends") + << QLatin1String("Transformer") + << QLatin1String("Group") + << QLatin1String("FileTagger") + << QLatin1String("Export") + << QLatin1String("Probe") + << QLatin1String("Rule")); + item << conditionProperty(); + PropertyDeclaration decl(QLatin1String("type"), PropertyDeclaration::StringList); + decl.initialValueSource = QLatin1String("[]"); + item << decl; + item << nameProperty(); + decl = PropertyDeclaration("targetName", PropertyDeclaration::String); + decl.initialValueSource = QLatin1String("name"); + item << decl; + decl = PropertyDeclaration(QLatin1String("destinationDirectory"), PropertyDeclaration::String); + decl.initialValueSource = QLatin1String("'.'"); + item << decl; + item << PropertyDeclaration(QLatin1String("consoleApplication"), + PropertyDeclaration::Boolean); + item << PropertyDeclaration(QLatin1String("files"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("excludeFiles"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("qbsSearchPaths"), + PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("version"), PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addProjectItem() +{ + ItemDeclaration item(QLatin1String("Project")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Module") + << QLatin1String("Project") + << QLatin1String("SubProject") + << QLatin1String("Product") + << QLatin1String("FileTagger") + << QLatin1String("Rule")); + item << nameProperty(); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("references"), PropertyDeclaration::Variant, + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(QLatin1String("qbsSearchPaths"), + PropertyDeclaration::StringList, PropertyDeclaration::PropertyNotAvailableInConfig); + insert(item); +} + +void BuiltinDeclarations::addPropertiesItem() +{ + insert(ItemDeclaration(QLatin1String("Properties"))); +} + +void BuiltinDeclarations::addPropertyOptionsItem() +{ + ItemDeclaration item(QLatin1String("PropertyOptions")); + item << nameProperty(); + item << PropertyDeclaration(QLatin1String("allowedValues"), PropertyDeclaration::Variant); + item << PropertyDeclaration(QLatin1String("description"), PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addRuleItem() +{ + ItemDeclaration item(QLatin1String("Rule")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Artifact")); + item << conditionProperty(); + PropertyDeclaration decl(QLatin1String("multiplex"), PropertyDeclaration::Boolean); + decl.initialValueSource = QLatin1String("false"); + item << decl; + item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("usings"), PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("auxiliaryInputs"), + PropertyDeclaration::StringList); + item << PropertyDeclaration(QLatin1String("explicitlyDependsOn"), + PropertyDeclaration::StringList); + item << prepareScriptProperty(); + insert(item); +} + +void BuiltinDeclarations::addSubprojectItem() +{ + ItemDeclaration item(QLatin1String("SubProject")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Project") // needed, because we're adding this internally + << QLatin1String("Properties")); + item << PropertyDeclaration(QLatin1String("filePath"), PropertyDeclaration::Path); + PropertyDeclaration inheritProperty; + inheritProperty.name = QLatin1String("inheritProperties"); + inheritProperty.type = PropertyDeclaration::Boolean; + inheritProperty.initialValueSource = QLatin1String("true"); + item << inheritProperty; + insert(item); +} + +void BuiltinDeclarations::addTransformerItem() +{ + ItemDeclaration item(QLatin1String("Transformer")); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << QLatin1String("Artifact")); + item << conditionProperty(); + item << PropertyDeclaration(QLatin1String("inputs"), PropertyDeclaration::Variant); + item << prepareScriptProperty(); + item << PropertyDeclaration(QLatin1String("explicitlyDependsOn"), + PropertyDeclaration::StringList); + insert(item); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h new file mode 100644 index 000000000..b793c5aae --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_BUILTINDECLARATIONS_H +#define QBS_BUILTINDECLARATIONS_H + +#include "itemdeclaration.h" + +#include <QByteArray> +#include <QMap> + +namespace qbs { +namespace Internal { + +class Item; + +class BuiltinDeclarations +{ +public: + BuiltinDeclarations(); + + QString languageVersion() const; + QByteArray qmlTypeInfo() const; + bool containsType(const QString &typeName) const; + ItemDeclaration declarationsForType(const QString &typeName) const; + void setupItemForBuiltinType(qbs::Internal::Item *item) const; + +private: + void insert(const ItemDeclaration &decl); + void addArtifactItem(); + void addDependsItem(); + void addExportItem(); + void addFileTaggerItem(); + void addGroupItem(); + void addModuleItem(); + void addProbeItem(); + void addProductItem(); + void addProjectItem(); + void addPropertiesItem(); + void addPropertyOptionsItem(); + void addRuleItem(); + void addSubprojectItem(); + void addTransformerItem(); + + QString m_languageVersion; + QMap<QString, ItemDeclaration> m_builtins; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILTINDECLARATIONS_H diff --git a/src/lib/corelib/language/builtinvalue.cpp b/src/lib/corelib/language/builtinvalue.cpp new file mode 100644 index 000000000..d7f98083d --- /dev/null +++ b/src/lib/corelib/language/builtinvalue.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "builtinvalue.h" + +namespace qbs { +namespace Internal { + +BuiltinValue::BuiltinValue(Builtin builtin) + : Value(Value::BuiltinValueType) + , m_builtin(builtin) +{ +} + +BuiltinValuePtr BuiltinValue::create(Builtin builtin) +{ + return BuiltinValuePtr(new BuiltinValue(builtin)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/builtinvalue.h b/src/lib/corelib/language/builtinvalue.h new file mode 100644 index 000000000..77c6fd14a --- /dev/null +++ b/src/lib/corelib/language/builtinvalue.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_BUILTINVALUE_H +#define QBS_BUILTINVALUE_H + +#include "value.h" + +namespace qbs { +namespace Internal { + +class BuiltinValue : public Value +{ +public: + enum Builtin + { + GetNativeSettingFunction, + GetEnvFunction, + GetHostOSFunction, + CanonicalArchitectureFunction + }; + + static BuiltinValuePtr create(Builtin builtin); + + void apply(ValueHandler *handler) { handler->handle(this); } + + Builtin builtin() const { return m_builtin; } + void setBuiltin(const Builtin &builtin) { m_builtin = builtin; } + +private: + BuiltinValue(Builtin builtin); + + Builtin m_builtin; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILTINVALUE_H diff --git a/src/lib/corelib/language/evaluationdata.h b/src/lib/corelib/language/evaluationdata.h new file mode 100644 index 000000000..930ac1f0c --- /dev/null +++ b/src/lib/corelib/language/evaluationdata.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_EVALUATIONDATA_H +#define QBS_EVALUATIONDATA_H + +#include <QHash> +#include <QScriptEngine> +#include <QScriptValue> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class Evaluator; +class Item; + +class EvaluationData +{ +public: + Evaluator *evaluator; + const Item *item; + mutable QHash<QScriptString, QScriptValue> valueCache; + + void attachTo(QScriptValue &); + static EvaluationData *get(const QScriptValue &); +}; + +inline void EvaluationData::attachTo(QScriptValue &scriptValue) +{ + QVariant v; + v.setValue<quintptr>(reinterpret_cast<quintptr>(this)); + scriptValue.setData(scriptValue.engine()->newVariant(v)); +} + +inline EvaluationData *EvaluationData::get(const QScriptValue &scriptValue) +{ + const quintptr ptr = scriptValue.data().toVariant().value<quintptr>(); + return reinterpret_cast<EvaluationData *>(ptr); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATIONDATA_H diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp new file mode 100644 index 000000000..29b3837fd --- /dev/null +++ b/src/lib/corelib/language/evaluator.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "evaluator.h" +#include "evaluationdata.h" +#include "evaluatorscriptclass.h" +#include "filecontext.h" +#include "filetags.h" +#include "item.h" +#include <jsextensions/jsextensions.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> +#include <QDebug> +#include <QScriptEngine> + +namespace qbs { +namespace Internal { + + +Evaluator::Evaluator(ScriptEngine *scriptEngine, const Logger &logger) + : m_scriptEngine(scriptEngine) + , m_scriptClass(new EvaluatorScriptClass(scriptEngine, logger)) +{ +} + +Evaluator::~Evaluator() +{ + QHash<const Item *, QScriptValue>::iterator it = m_scriptValueMap.begin(); + for (; it != m_scriptValueMap.end(); ++it) { + EvaluationData *data = EvaluationData::get(*it); + if (data) { + if (data->item) + data->item->setPropertyObserver(0); + delete data; + } + } + delete m_scriptClass; +} + +QScriptValue Evaluator::property(const Item *item, const QString &name) +{ + return scriptValue(item).property(name); +} + +QScriptValue Evaluator::property(const Item *item, const QStringList &nameParts) +{ + const Item *targetItem = item; + const int c = nameParts.count() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->properties().value(nameParts.at(i)); + if (!v) + return QScriptValue(); + QBS_ASSERT(v->type() == Value::ItemValueType, return QScriptValue()); + targetItem = v.staticCast<ItemValue>()->item(); + QBS_ASSERT(targetItem, return QScriptValue()); + } + return property(targetItem, nameParts.last()); +} + +bool Evaluator::boolValue(const Item *item, const QString &name, bool defaultValue, + bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + handleEvaluationError(item, name, v); + if (!v.isValid() || v.isUndefined()) { + if (propertyWasSet) + *propertyWasSet = false; + return defaultValue; + } + if (propertyWasSet) + *propertyWasSet = true; + return v.toBool(); +} + +FileTags Evaluator::fileTagsValue(const Item *item, const QString &name) +{ + return FileTags::fromStringList(stringListValue(item, name)); +} + +QString Evaluator::stringValue(const Item *item, const QString &name, + const QString &defaultValue, bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + handleEvaluationError(item, name, v); + if (!v.isValid() || v.isUndefined()) { + if (propertyWasSet) + *propertyWasSet = false; + return defaultValue; + } + if (propertyWasSet) + *propertyWasSet = true; + return v.toString(); +} + +static QStringList toStringList(const QScriptValue &scriptValue, + const Item *item, const QString &propertyName) +{ + if (scriptValue.isString()) { + return QStringList(scriptValue.toString()); + } else if (scriptValue.isArray()) { + QStringList lst; + int i = 0; + forever { + QScriptValue elem = scriptValue.property(i++); + if (!elem.isValid()) + break; + if (elem.isArray() || elem.isObject()) { + // Let's assume all other JS types are convertible to string. + throw ErrorInfo(Tr::tr("Expected array element of type String at index %1.") + .arg(i - 1), + item->property(propertyName)->location()); + } + lst.append(elem.toString()); + } + return lst; + } + return QStringList(); +} + +QStringList Evaluator::stringListValue(const Item *item, const QString &name, bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + if (propertyWasSet) + *propertyWasSet = v.isValid() && !v.isUndefined(); + handleEvaluationError(item, name, v); + return toStringList(v, item, name); +} + +QScriptValue Evaluator::scriptValue(const Item *item) +{ + QScriptValue &scriptValue = m_scriptValueMap[item]; + if (scriptValue.isObject()) { + // already initialized + return scriptValue; + } + + EvaluationData *edata = new EvaluationData; + edata->evaluator = this; + edata->item = item; + edata->item->setPropertyObserver(this); + + scriptValue = m_scriptEngine->newObject(m_scriptClass); + edata->attachTo(scriptValue); + return scriptValue; +} + +void Evaluator::onItemPropertyChanged(Item *item) +{ + EvaluationData *data = EvaluationData::get(m_scriptValueMap.value(item)); + if (data) + data->valueCache.clear(); +} + +void Evaluator::onItemDestroyed(Item *item) +{ + delete EvaluationData::get(m_scriptValueMap.value(item)); + m_scriptValueMap.remove(item); +} + +void Evaluator::handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue) +{ + if (Q_LIKELY(!m_scriptEngine->hasErrorOrException(scriptValue))) + return; + const ValueConstPtr value = item->property(name); + CodeLocation location = value ? value->location() : CodeLocation(); + if (m_scriptEngine->hasUncaughtException()) { + throw ErrorInfo(m_scriptEngine->uncaughtException().toString(), + CodeLocation(location.fileName(), m_scriptEngine->uncaughtExceptionLineNumber())); + } + throw ErrorInfo(scriptValue.toString(), location); +} + +QScriptValue Evaluator::fileScope(const FileContextConstPtr &file) +{ + QScriptValue &result = m_fileScopeMap[file]; + if (result.isObject()) { + // already initialized + return result; + } + + if (file->idScope()) + result = scriptValue(file->idScope()); + else + result = m_scriptEngine->newObject(); + result.setProperty(QLatin1String("filePath"), file->filePath()); + result.setProperty(QLatin1String("path"), file->dirPath()); + m_scriptEngine->import(file->jsImports(), result, result); + JsExtensions::setupExtensions(file->jsExtensions(), result); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h new file mode 100644 index 000000000..a17605af1 --- /dev/null +++ b/src/lib/corelib/language/evaluator.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_EVALUATOR_H +#define QBS_EVALUATOR_H + +#include "forward_decls.h" +#include "itemobserver.h" +#include <language/scriptengine.h> + +#include <QHash> +#include <QScriptValue> + +namespace qbs { +namespace Internal { +class FileTags; + +class EvaluatorScriptClass; + +class Evaluator : private ItemObserver +{ + friend class SVConverter; + +public: + Evaluator(ScriptEngine *scriptEngine, const Logger &logger); + virtual ~Evaluator(); + + ScriptEngine *engine() const; + QScriptValue property(const Item *item, const QString &name); + QScriptValue property(const Item *item, const QStringList &nameParts); + + bool boolValue(const Item *item, const QString &name, bool defaultValue = false, + bool *propertyWasSet = 0); + FileTags fileTagsValue(const Item *item, const QString &name); + QString stringValue(const Item *item, const QString &name, + const QString &defaultValue = QString(), bool *propertyWasSet = 0); + QStringList stringListValue(const Item *item, const QString &name, bool *propertyWasSet = 0); + + QScriptValue scriptValue(const Item *item); + QScriptValue fileScope(const FileContextConstPtr &file); + +private: + void onItemPropertyChanged(Item *item); + void onItemDestroyed(Item *item); + void handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue); + + ScriptEngine *m_scriptEngine; + EvaluatorScriptClass *m_scriptClass; + mutable QHash<const Item *, QScriptValue> m_scriptValueMap; + mutable QHash<FileContextConstPtr, QScriptValue> m_fileScopeMap; +}; + +inline ScriptEngine *Evaluator::engine() const +{ + return m_scriptEngine; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATOR_H diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp new file mode 100644 index 000000000..fb89f53e6 --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.cpp @@ -0,0 +1,577 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "evaluatorscriptclass.h" + +#include "builtinvalue.h" +#include "evaluationdata.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "scriptengine.h" +#include "propertydeclaration.h" +#include <tools/hostosinfo.h> +#include <tools/qbsassert.h> +#include <tools/scripttools.h> + +#include <QScriptString> +#include <QScriptValue> +#include <QDebug> +#include <QSettings> + +namespace qbs { +namespace Internal { + +class SVConverter : ValueHandler +{ + EvaluatorScriptClass *const scriptClass; + ScriptEngine *const engine; + QScriptContext *const scriptContext; + const QScriptValue *object; + const ValuePtr &valuePtr; + const bool inPrototype; + char pushedScopesCount; + +public: + const QScriptString *propertyName; + const EvaluationData *data; + QScriptValue *result; + + SVConverter(EvaluatorScriptClass *esc, const QScriptValue *obj, const ValuePtr &v, + bool _inPrototype) + : scriptClass(esc) + , engine(static_cast<ScriptEngine *>(esc->engine())) + , scriptContext(esc->engine()->currentContext()) + , object(obj) + , valuePtr(v) + , inPrototype(_inPrototype) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + void setupConvenienceProperty(const QString &conveniencePropertyName, QScriptValue *extraScope, + const QScriptValue &scriptValue) + { + if (!extraScope->isObject()) + *extraScope = scriptClass->engine()->newObject(); + if (!scriptValue.isValid() || scriptValue.isUndefined()) { + // If there's no such value, use an empty array to have the convenience property + // still available. + extraScope->setProperty(conveniencePropertyName, engine->newArray()); + } else { + extraScope->setProperty(conveniencePropertyName, scriptValue); + } + } + + void pushScope(const QScriptValue &scope) + { + if (scope.isObject()) { + scriptContext->pushScope(scope); + ++pushedScopesCount; + } + } + + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scriptContext->popScope(); + } + + void handle(JSSourceValue *value) + { + const Item *conditionScopeItem = 0; + QScriptValue conditionScope; + QScriptValue conditionFileScope; + Item *outerItem = data->item->outerItem(); + for (int i = 0; i < value->alternatives().count(); ++i) { + const JSSourceValue::Alternative *alternative = 0; + alternative = &value->alternatives().at(i); + if (conditionScopeItem != alternative->conditionScopeItem) { + conditionScopeItem = alternative->conditionScopeItem; + conditionScope = data->evaluator->scriptValue(alternative->conditionScopeItem); + QBS_ASSERT(conditionScope.isObject(), return); + conditionFileScope = data->evaluator->fileScope(conditionScopeItem->file()); + } + engine->currentContext()->pushScope(conditionFileScope); + pushItemScopes(conditionScopeItem); + engine->currentContext()->pushScope(conditionScope); + const QScriptValue cr = engine->evaluate(alternative->condition); + engine->currentContext()->popScope(); + engine->currentContext()->popScope(); + popScopes(); + if (engine->hasErrorOrException(cr)) { + *result = cr; + return; + } + if (cr.toBool()) { + // condition is true, let's use the value of this alternative + if (alternative->value->sourceUsesOuter()) { + // Clone value but without alternatives. + JSSourceValuePtr outerValue = JSSourceValue::create(); + outerValue->setFile(value->file()); + outerValue->setSourceCode(value->sourceCode()); + outerValue->setBaseValue(value->baseValue()); + outerValue->setLocation(value->location()); + outerItem = Item::create(data->item->pool()); + outerItem->setProperty(propertyName->toString(), outerValue); + } + value = alternative->value.data(); + break; + } + } + + QScriptValue extraScope; + if (value->sourceUsesBase()) { + QScriptValue baseValue; + if (value->baseValue()) { + SVConverter converter(scriptClass, object, value->baseValue(), inPrototype); + converter.propertyName = propertyName; + converter.data = data; + converter.result = &baseValue; + converter.start(); + } + setupConvenienceProperty(QLatin1String("base"), &extraScope, baseValue); + } + if (value->sourceUsesOuter() && outerItem) + setupConvenienceProperty(QLatin1String("outer"), &extraScope, + data->evaluator->property(outerItem, *propertyName)); + + pushScope(data->evaluator->fileScope(value->file())); + pushItemScopes(data->item); + if (inPrototype || !data->item->isModuleInstance()) { + // Own properties of module instances must not have the instance itself in the scope. + pushScope(*object); + } + pushScope(extraScope); + const CodeLocation valueLocation = value->location(); + *result = engine->evaluate(value->sourceCode(), valueLocation.fileName(), + valueLocation.line()); + popScopes(); + } + + void handle(ItemValue *value) + { + Item *item = value->item(); + if (!item) + qDebug() << "SVConverter got null item" << propertyName->toString(); + *result = data->evaluator->scriptValue(item); + if (!result->isValid()) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) + { + *result = scriptClass->engine()->toScriptValue(variantValue->value()); + } + + void handle(BuiltinValue *builtinValue) + { + *result = scriptClass->scriptValueForBuiltin(builtinValue->builtin()); + } +}; + +bool debugProperties = false; + +enum QueryPropertyType +{ + QPTDefault, + QPTParentProperty +}; + +EvaluatorScriptClass::EvaluatorScriptClass(ScriptEngine *scriptEngine, const Logger &logger) + : QScriptClass(scriptEngine) + , m_logger(logger) +{ + m_getNativeSettingBuiltin = scriptEngine->newFunction(js_getNativeSetting, 3); + m_getEnvBuiltin = scriptEngine->newFunction(js_getEnv, 1); + m_getHostOSBuiltin = scriptEngine->newFunction(js_getHostOS, 1); + m_canonicalArchitectureBuiltin = scriptEngine->newFunction(js_canonicalArchitecture, 1); +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryProperty(const QScriptValue &object, + const QScriptString &name, + QScriptClass::QueryFlags flags, + uint *id) +{ + Q_UNUSED(flags); + + // We assume that it's safe to save the result of the query in a member of the scriptclass. + // It must be cleared in the property method before doing any further lookup. + QBS_ASSERT(m_queryResult.isNull(), return QueryFlags()); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty " << object.objectId() << " " << name; + + EvaluationData *const data = EvaluationData::get(object); + const QString nameString = name.toString(); + if (nameString == QLatin1String("parent")) { + *id = QPTParentProperty; + m_queryResult.data = data; + return QScriptClass::HandlesReadAccess; + } + + *id = QPTDefault; + if (!data) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: no data attached"; + return QScriptClass::QueryFlags(); + } + + return queryItemProperty(data, nameString); +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryItemProperty(const EvaluationData *data, + const QString &name, + bool ignoreParent) +{ + for (const Item *item = data->item; item; item = item->prototype()) { + m_queryResult.value = item->properties().value(name); + if (!m_queryResult.value.isNull()) { + m_queryResult.data = data; + if (data->item != item) + m_queryResult.inPrototype = true; + return HandlesReadAccess; + } + } + + if (!ignoreParent && data->item->parent()) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: query parent"; + EvaluationData parentdata = *data; + parentdata.item = data->item->parent(); + const QueryFlags qf = queryItemProperty(&parentdata, name, true); + if (qf.testFlag(HandlesReadAccess)) { + m_queryResult.data = data; + return qf; + } + } + + if (debugProperties) + m_logger.qbsTrace() << "[SC] queryProperty: no such property"; + return QScriptClass::QueryFlags(); +} + +QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue) +{ + return (scriptValue.isObject() + ? QLatin1String("[Object: ") + + QString::number(scriptValue.objectId(), 16) + QLatin1Char(']') + : scriptValue.toVariant().toString()); +} + +Item *EvaluatorScriptClass::findParentOfType(const Item *item, const QString &typeName) +{ + for (Item *it = item->parent(); it; it = it->parent()) { + if (it->typeName() == typeName) + return it; + } + return 0; +} + +inline void convertToPropertyType(const PropertyDeclaration::Type t, QScriptValue &v) +{ + if (v.isUndefined()) + return; + switch (t) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + case PropertyDeclaration::Verbatim: + break; + case PropertyDeclaration::Boolean: + if (!v.isBool()) + v = v.toBool(); + break; + case PropertyDeclaration::Integer: + if (!v.isNumber()) + v = v.toNumber(); + break; + case PropertyDeclaration::Path: + case PropertyDeclaration::String: + if (!v.isString()) + v = v.toString(); + break; + case PropertyDeclaration::PathList: + case PropertyDeclaration::StringList: + if (!v.isArray()) { + QScriptValue x = v.engine()->newArray(1); + x.setProperty(0, v.isString() ? v : v.toString()); + v = x; + } + break; + } +} + +QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QScriptString &name, + uint id) +{ + const EvaluationData *data = m_queryResult.data; + const bool inPrototype = m_queryResult.inPrototype; + m_queryResult.data = 0; + m_queryResult.inPrototype = false; + QBS_ASSERT(data, return QScriptValue()); + + const QueryPropertyType qpt = static_cast<QueryPropertyType>(id); + if (qpt == QPTParentProperty) { + return data->item->parent() + ? data->evaluator->scriptValue(data->item->parent()) + : engine()->undefinedValue(); + } + + ValuePtr value; + m_queryResult.value.swap(value); + QBS_ASSERT(value, return QScriptValue()); + QBS_ASSERT(m_queryResult.isNull(), return QScriptValue()); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] property " << name; + + QScriptValue result = data->valueCache.value(name); + if (result.isValid()) { + if (debugProperties) + m_logger.qbsTrace() << "[SC] cache hit " << name << ": " << resultToString(result); + return result; + } + + SVConverter converter(this, &object, value, inPrototype); + converter.propertyName = &name; + converter.data = data; + converter.result = &result; + converter.start(); + + const PropertyDeclaration decl = data->item->propertyDeclarations().value(name.toString()); + convertToPropertyType(decl.type, result); + + if (debugProperties) + m_logger.qbsTrace() << "[SC] cache miss " << name << ": " << resultToString(result); + data->valueCache.insert(name, result); + return result; +} + +QScriptValue EvaluatorScriptClass::scriptValueForBuiltin(BuiltinValue::Builtin builtin) const +{ + switch (builtin) { + case BuiltinValue::GetNativeSettingFunction: + return m_getNativeSettingBuiltin; + case BuiltinValue::GetEnvFunction: + return m_getEnvBuiltin; + case BuiltinValue::GetHostOSFunction: + return m_getHostOSBuiltin; + case BuiltinValue::CanonicalArchitectureFunction: + return m_canonicalArchitectureBuiltin; + } + QBS_ASSERT(!"unhandled builtin", ;); + return QScriptValue(); +} + +QScriptValue EvaluatorScriptClass::js_getNativeSetting(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1 || context->argumentCount() > 3)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getNativeSetting expects between 1 and 3 arguments")); + } + + QString key = context->argumentCount() > 1 ? context->argument(1).toString() : QString(); + + // We'll let empty string represent the default registry value + if (HostOsInfo::isWindowsHost() && key.isEmpty()) + key = QLatin1String("."); + + QVariant defaultValue = context->argumentCount() > 2 ? context->argument(2).toVariant() : QVariant(); + + QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); + QVariant value = settings.value(key, defaultValue); + return value.isNull() ? engine->undefinedValue() : engine->toScriptValue(value); +} + +QScriptValue EvaluatorScriptClass::js_getEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getEnv expects 1 argument")); + } + const QString name = context->argument(0).toString(); + ScriptEngine * const e = static_cast<ScriptEngine *>(engine); + const QString value = e->environment().value(name); + e->addEnvironmentVariable(name, value); + return value.isNull() ? engine->undefinedValue() : value; +} + +QScriptValue EvaluatorScriptClass::js_getHostOS(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context); + QStringList hostSystem; + +#if defined(Q_OS_AIX) + hostSystem << "aix"; +#endif +#if defined(Q_OS_ANDROID) + hostSystem << "android"; +#endif +#if defined(Q_OS_BLACKBERRY) + hostSystem << "blackberry"; +#endif +#if defined(Q_OS_BSD4) + hostSystem << "bsd" << "bsd4"; +#endif +#if defined(Q_OS_BSDI) + hostSystem << "bsdi"; +#endif +#if defined(Q_OS_CYGWIN) + hostSystem << "cygwin"; +#endif +#if defined(Q_OS_DARWIN) + hostSystem << "darwin"; +#endif +#if defined(Q_OS_DGUX) + hostSystem << "dgux"; +#endif +#if defined(Q_OS_DYNIX) + hostSystem << "dynix"; +#endif +#if defined(Q_OS_FREEBSD) + hostSystem << "freebsd"; +#endif +#if defined(Q_OS_HPUX) + hostSystem << "hpux"; +#endif +#if defined(Q_OS_HURD) + hostSystem << "hurd"; +#endif +#if defined(Q_OS_INTEGRITY) + hostSystem << "integrity"; +#endif +#if defined(Q_OS_IOS) + hostSystem << "ios"; +#endif +#if defined(Q_OS_IRIX) + hostSystem << "irix"; +#endif +#if defined(Q_OS_LINUX) + hostSystem << "linux"; +#endif +#if defined(Q_OS_LYNX) + hostSystem << "lynx"; +#endif +#if defined(Q_OS_MACX) + hostSystem << "osx"; +#endif +#if defined(Q_OS_MSDOS) + hostSystem << "msdos"; +#endif +#if defined(Q_OS_NACL) + hostSystem << "nacl"; +#endif +#if defined(Q_OS_NETBSD) + hostSystem << "netbsd"; +#endif +#if defined(Q_OS_OPENBSD) + hostSystem << "openbsd"; +#endif +#if defined(Q_OS_OS2) + hostSystem << "os2"; +#endif +#if defined(Q_OS_OS2EMX) + hostSystem << "os2emx"; +#endif +#if defined(Q_OS_OSF) + hostSystem << "osf"; +#endif +#if defined(Q_OS_QNX) + hostSystem << "qnx"; +#endif +#if defined(Q_OS_ONX6) + hostSystem << "qnx6"; +#endif +#if defined(Q_OS_RELIANT) + hostSystem << "reliant"; +#endif +#if defined(Q_OS_SCO) + hostSystem << "sco"; +#endif +#if defined(Q_OS_SOLARIS) + hostSystem << "solaris"; +#endif +#if defined(Q_OS_SYMBIAN) + hostSystem << "symbian"; +#endif +#if defined(Q_OS_ULTRIX) + hostSystem << "ultrix"; +#endif +#if defined(Q_OS_UNIX) + hostSystem << "unix"; +#endif +#if defined(Q_OS_UNIXWARE) + hostSystem << "unixware"; +#endif +#if defined(Q_OS_VXWORKS) + hostSystem << "vxworks"; +#endif +#if defined(Q_OS_WIN32) + hostSystem << "windows"; +#endif +#if defined(Q_OS_WINCE) + hostSystem << "windowsce"; +#endif +#if defined(Q_OS_WINPHONE) + hostSystem << "windowsphone"; +#endif +#if defined(Q_OS_WINRT) + hostSystem << "winrt"; +#endif + + return engine->toScriptValue(hostSystem); +} + +QScriptValue EvaluatorScriptClass::js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("canonicalArchitecture expects 1 argument")); + } + const QString architecture = context->argument(0).toString(); + return engine->toScriptValue(HostOsInfo::canonicalArchitecture(architecture)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h new file mode 100644 index 000000000..4803a21b9 --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_EVALUATORSCRIPTCLASS_H +#define QBS_EVALUATORSCRIPTCLASS_H + +#include "value.h" +#include "builtinvalue.h" +#include <logging/logger.h> + +#include <QScriptClass> + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class EvaluationData; +class ScriptEngine; + +class EvaluatorScriptClass : public QScriptClass +{ +public: + EvaluatorScriptClass(ScriptEngine *scriptEngine, const Logger &logger); + + QueryFlags queryProperty(const QScriptValue &object, + const QScriptString &name, + QueryFlags flags, uint *id); + QScriptValue property(const QScriptValue &object, + const QScriptString &name, uint id); + + QScriptValue scriptValueForBuiltin(BuiltinValue::Builtin builtin) const; + +private: + QueryFlags queryItemProperty(const EvaluationData *data, + const QString &name, + bool ignoreParent = false); + static QString resultToString(const QScriptValue &scriptValue); + static Item *findParentOfType(const Item *item, const QString &typeName); + static QScriptValue js_getNativeSetting(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getHostOS(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine); + + struct QueryResult + { + QueryResult() + : data(0), inPrototype(false) + {} + + bool isNull() const + { + return !data; + } + + const EvaluationData *data; + bool inPrototype; + ValuePtr value; + }; + QueryResult m_queryResult; + Logger m_logger; + QScriptValue m_getNativeSettingBuiltin; + QScriptValue m_getEnvBuiltin; + QScriptValue m_getHostOSBuiltin; + QScriptValue m_canonicalArchitectureBuiltin; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATORSCRIPTCLASS_H diff --git a/src/lib/corelib/language/filecontext.cpp b/src/lib/corelib/language/filecontext.cpp new file mode 100644 index 000000000..570b40c66 --- /dev/null +++ b/src/lib/corelib/language/filecontext.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "filecontext.h" +#include <tools/fileinfo.h> + +namespace qbs { +namespace Internal { + +FileContext::FileContext() + : m_idScope(0) +{ +} + +FileContextPtr FileContext::create() +{ + return FileContextPtr(new FileContext); +} + +QString FileContext::dirPath() const +{ + return FileInfo::path(m_filePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filecontext.h b/src/lib/corelib/language/filecontext.h new file mode 100644 index 000000000..75f93f04c --- /dev/null +++ b/src/lib/corelib/language/filecontext.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_FILECONTEXT_H +#define QBS_FILECONTEXT_H + +#include "item.h" +#include "jsimports.h" + +#include <QStringList> + +namespace qbs { +namespace Internal { + +class FileContext +{ + friend class ItemReaderASTVisitor; + + FileContext(); + +public: + static FileContextPtr create(); + + QString filePath() const; + QString dirPath() const; + JsImports jsImports() const; + QStringList jsExtensions() const; + + Item *idScope() const; + +private: + QString m_filePath; + JsImports m_jsImports; + QStringList m_jsExtensions; + Item *m_idScope; +}; + +inline QString FileContext::filePath() const +{ + return m_filePath; +} + +inline JsImports FileContext::jsImports() const +{ + return m_jsImports; +} + +inline QStringList FileContext::jsExtensions() const +{ + return m_jsExtensions; +} + +inline Item *FileContext::idScope() const +{ + return m_idScope; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILECONTEXT_H diff --git a/src/lib/corelib/language/filetags.cpp b/src/lib/corelib/language/filetags.cpp new file mode 100644 index 000000000..a5af1f0b2 --- /dev/null +++ b/src/lib/corelib/language/filetags.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "filetags.h" +#include <QStringList> + +namespace qbs { +namespace Internal { + +void FileTag::clear() +{ + Id::operator=(Id()); +} + +QStringList FileTags::toStringList() const +{ + QStringList strlst; + foreach (const FileTag &tag, *this) + strlst += tag.toString(); + return strlst; +} + +FileTags FileTags::fromStringList(const QStringList &strings) +{ + FileTags result; + foreach (const QString &str, strings) + result += FileTag(str.toLocal8Bit()); + return result; +} + +/*! + * \return \c{true} if this file tags set has file tags in common with \c{other}. + */ +bool FileTags::matches(const FileTags &other) const +{ + for (FileTags::const_iterator it = other.begin(); it != other.end(); ++it) + if (contains(*it)) + return true; + return false; +} + +LogWriter operator <<(LogWriter w, const FileTags &tags) +{ + bool firstLoop = true; + w.write('('); + foreach (const FileTag &tag, tags) { + if (firstLoop) + firstLoop = false; + else + w.write(QLatin1String(", ")); + w.write(tag.toString()); + } + w.write(')'); + return w; +} + +QDataStream &operator >>(QDataStream &s, FileTags &tags) +{ + int i; + s >> i; + tags.clear(); + tags.reserve(i); + QVariant v; + while (--i >= 0) { + s >> v; + tags += FileTag::fromSetting(v); + } + return s; +} + +QDataStream &operator <<(QDataStream &s, const FileTags &tags) +{ + s << tags.count(); + foreach (const FileTag &ft, tags) + s << ft.toSetting(); + return s; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filetags.h b/src/lib/corelib/language/filetags.h new file mode 100644 index 000000000..44effc509 --- /dev/null +++ b/src/lib/corelib/language/filetags.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_FILETAGS_H +#define QBS_FILETAGS_H + +#include <logging/logger.h> +#include <tools/id.h> +#include <QDataStream> +#include <QSet> + +namespace qbs { +namespace Internal { + +class FileTag : public Id +{ +public: + FileTag() + : Id() + {} + + FileTag(const Id &other) + : Id(other) + {} + + FileTag(const char *str) + : Id(str) + {} + + explicit FileTag(const QByteArray &ba) + : Id(ba) + {} + + void clear(); +}; + +class FileTags : public QSet<FileTag> +{ +public: + QStringList toStringList() const; + static FileTags fromStringList(const QStringList &strings); + bool matches(const FileTags &other) const; +}; + +LogWriter operator <<(LogWriter w, const FileTags &tags); +QDataStream &operator >>(QDataStream &s, FileTags & tags); +QDataStream &operator <<(QDataStream &s, const FileTags &tags); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILETAGS_H + diff --git a/src/lib/corelib/language/forward_decls.h b/src/lib/corelib/language/forward_decls.h new file mode 100644 index 000000000..dc7c572df --- /dev/null +++ b/src/lib/corelib/language/forward_decls.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#ifndef QBS_LANG_FORWARD_DECLS_H +#define QBS_LANG_FORWARD_DECLS_H + +#include <QSharedPointer> + +namespace qbs { +namespace Internal { + +class Value; +typedef QSharedPointer<Value> ValuePtr; +typedef QSharedPointer<const Value> ValueConstPtr; + +class ItemValue; +typedef QSharedPointer<ItemValue> ItemValuePtr; +typedef QSharedPointer<const ItemValue> ItemValueConstPtr; + +class JSSourceValue; +typedef QSharedPointer<JSSourceValue> JSSourceValuePtr; +typedef QSharedPointer<const JSSourceValue> JSSourceValueConstPtr; + +class VariantValue; +typedef QSharedPointer<VariantValue> VariantValuePtr; +typedef QSharedPointer<const VariantValue> VariantValueConstPtr; + +class BuiltinValue; +typedef QSharedPointer<BuiltinValue> BuiltinValuePtr; +typedef QSharedPointer<const BuiltinValue> BuiltinValueConstPtr; + +class FileContext; +typedef QSharedPointer<FileContext> FileContextPtr; +typedef QSharedPointer<const FileContext> FileContextConstPtr; + +class PropertyMapInternal; +typedef QSharedPointer<PropertyMapInternal> PropertyMapPtr; +typedef QSharedPointer<const PropertyMapInternal> PropertyMapConstPtr; + +class FileTagger; +typedef QSharedPointer<FileTagger> FileTaggerPtr; +typedef QSharedPointer<const FileTagger> FileTaggerConstPtr; + +class ResolvedProduct; +typedef QSharedPointer<ResolvedProduct> ResolvedProductPtr; +typedef QSharedPointer<const ResolvedProduct> ResolvedProductConstPtr; + +class ResolvedProject; +typedef QSharedPointer<ResolvedProject> ResolvedProjectPtr; +typedef QSharedPointer<const ResolvedProject> ResolvedProjectConstPtr; + +class TopLevelProject; +typedef QSharedPointer<TopLevelProject> TopLevelProjectPtr; +typedef QSharedPointer<const TopLevelProject> TopLevelProjectConstPtr; + +class ResolvedFileContext; +typedef QSharedPointer<ResolvedFileContext> ResolvedFileContextPtr; +typedef QSharedPointer<const ResolvedFileContext> ResolvedFileContextConstPtr; + +class Rule; +typedef QSharedPointer<Rule> RulePtr; +typedef QSharedPointer<const Rule> RuleConstPtr; + +class SourceArtifact; +typedef QSharedPointer<SourceArtifact> SourceArtifactPtr; +typedef QSharedPointer<const SourceArtifact> SourceArtifactConstPtr; + +class ScriptFunction; +typedef QSharedPointer<ScriptFunction> ScriptFunctionPtr; +typedef QSharedPointer<const ScriptFunction> ScriptFunctionConstPtr; + +class RuleArtifact; +typedef QSharedPointer<RuleArtifact> RuleArtifactPtr; +typedef QSharedPointer<const RuleArtifact> RuleArtifactConstPtr; + +class ResolvedModule; +typedef QSharedPointer<ResolvedModule> ResolvedModulePtr; +typedef QSharedPointer<const ResolvedModule> ResolvedModuleConstPtr; + +class ResolvedGroup; +typedef QSharedPointer<ResolvedGroup> GroupPtr; +typedef QSharedPointer<const ResolvedGroup> GroupConstPtr; + +class ResolvedTransformer; +typedef QSharedPointer<ResolvedTransformer> ResolvedTransformerPtr; +typedef QSharedPointer<const ResolvedTransformer> ResolvedTransformerConstPtr; + +class ArtifactProperties; +typedef QSharedPointer<ArtifactProperties> ArtifactPropertiesPtr; +typedef QSharedPointer<const ArtifactProperties> ArtifactPropertiesConstPtr; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LANG_FORWARD_DECLS_H diff --git a/src/lib/corelib/language/functiondeclaration.h b/src/lib/corelib/language/functiondeclaration.h new file mode 100644 index 000000000..656997e7a --- /dev/null +++ b/src/lib/corelib/language/functiondeclaration.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_FUNCTIONDECLARATION_H +#define QBS_FUNCTIONDECLARATION_H + +#include <tools/codelocation.h> + +namespace qbs { +namespace Internal { + +class FunctionDeclaration +{ +public: + FunctionDeclaration() {} + + void setName(const QString &name) { m_name = name; } + const QString &name() const { return m_name; } + + void setSourceCode(const QString &code) { m_sourceCode = code; } + const QString &sourceCode() const { return m_sourceCode; } + + void setLocation(const CodeLocation &location) { m_location = location; } + const CodeLocation &location() const { return m_location; } + +private: + QString m_name; + QString m_sourceCode; + CodeLocation m_location; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FUNCTIONDECLARATION_H diff --git a/src/lib/corelib/language/identifiersearch.cpp b/src/lib/corelib/language/identifiersearch.cpp new file mode 100644 index 000000000..813e87922 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "identifiersearch.h" +#include <parser/qmljsast_p.h> + +namespace qbs { +namespace Internal { + +IdentifierSearch::IdentifierSearch() +{ +} + +void IdentifierSearch::start(QbsQmlJS::AST::Node *node) +{ + foreach (bool *found, m_requests) + *found = false; + m_numberOfFoundIds = 0; + node->accept(this); +} + +void IdentifierSearch::add(const QString &name, bool *found) +{ + m_requests.insert(name, found); +} + +bool IdentifierSearch::preVisit(QbsQmlJS::AST::Node *) +{ + return m_numberOfFoundIds < m_requests.count(); +} + +bool IdentifierSearch::visit(QbsQmlJS::AST::IdentifierExpression *e) +{ + bool *found = m_requests.value(e->name.toString()); + if (found && !*found) { + *found = true; + m_numberOfFoundIds++; + } + return m_numberOfFoundIds < m_requests.count(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/identifiersearch.h b/src/lib/corelib/language/identifiersearch.h new file mode 100644 index 000000000..f82ee3262 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_IDENTIFIERSEARCHVISITOR_H +#define QBS_IDENTIFIERSEARCHVISITOR_H + +#include <parser/qmljsastfwd_p.h> +#include <parser/qmljsastvisitor_p.h> +#include <QMap> +#include <QString> + +namespace qbs { +namespace Internal { + +class IdentifierSearch : private QbsQmlJS::AST::Visitor +{ +public: + IdentifierSearch(); + void start(QbsQmlJS::AST::Node *node); + void add(const QString &name, bool *found); + +private: + bool preVisit(QbsQmlJS::AST::Node *); + bool visit(QbsQmlJS::AST::IdentifierExpression *e); + + QMap<QString, bool *> m_requests; + int m_numberOfFoundIds; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IDENTIFIERSEARCHVISITOR_H diff --git a/src/lib/corelib/language/importversion.cpp b/src/lib/corelib/language/importversion.cpp new file mode 100644 index 000000000..848775016 --- /dev/null +++ b/src/lib/corelib/language/importversion.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "importversion.h" +#include <logging/translator.h> +#include <tools/error.h> +#include <QStringList> + +namespace qbs { +namespace Internal { + +ImportVersion::ImportVersion() + : m_major(0), m_minor(0) +{ +} + +ImportVersion ImportVersion::fromString(const QString &str, const CodeLocation &location) +{ + QStringList lst = str.split(QLatin1Char('.')); + if (Q_UNLIKELY(lst.count() < 1 || lst.count() > 2)) + throw ErrorInfo(Tr::tr("Wrong number of components in import version."), location); + ImportVersion v; + int *parts[] = {&v.m_major, &v.m_minor, 0}; + for (int i = 0; i < lst.count(); ++i) { + if (!parts[i]) + break; + bool ok; + *parts[i] = lst.at(i).toInt(&ok); + if (Q_UNLIKELY(!ok)) + throw ErrorInfo(Tr::tr("Cannot parse import version."), location); + } + return v; +} + +bool ImportVersion::operator <(const ImportVersion &rhs) const +{ + return m_major < rhs.m_major || (m_major == rhs.m_major && m_minor < rhs.m_minor); +} + +bool ImportVersion::operator ==(const ImportVersion &rhs) const +{ + return m_major == rhs.m_major && m_minor == rhs.m_minor; +} + +bool ImportVersion::operator !=(const ImportVersion &rhs) const +{ + return !operator ==(rhs); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/importversion.h b/src/lib/corelib/language/importversion.h new file mode 100644 index 000000000..034258793 --- /dev/null +++ b/src/lib/corelib/language/importversion.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_IMPORTVERSION_H +#define QBS_IMPORTVERSION_H + +#include <tools/codelocation.h> + +namespace qbs { +namespace Internal { + +class ImportVersion +{ +public: + ImportVersion(); + + static ImportVersion fromString(const QString &str, + const CodeLocation &location = CodeLocation()); + + bool operator <(const ImportVersion &rhs) const; + bool operator ==(const ImportVersion &rhs) const; + bool operator !=(const ImportVersion &rhs) const; + +private: + int m_major; + int m_minor; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IMPORTVERSION_H diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp new file mode 100644 index 000000000..7b42df5e6 --- /dev/null +++ b/src/lib/corelib/language/item.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "item.h" +#include "itempool.h" +#include "filecontext.h" +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +namespace qbs { +namespace Internal { + +Item::Item(ItemPool *pool) + : m_pool(pool) + , m_propertyObserver(0) + , m_moduleInstance(false) + , m_prototype(0) + , m_scope(0) + , m_outerItem(0) + , m_parent(0) +{ +} + +Item::~Item() +{ + if (m_propertyObserver) + m_propertyObserver->onItemDestroyed(this); +} + +Item *Item::create(ItemPool *pool) +{ + return pool->allocateItem(); +} + +Item *Item::clone(ItemPool *pool) const +{ + Item *dup = create(pool); + dup->m_id = m_id; + dup->m_typeName = m_typeName; + dup->m_location = m_location; + dup->m_prototype = m_prototype; + dup->m_scope = m_scope; + dup->m_outerItem = m_outerItem; + dup->m_parent = m_parent; + dup->m_children = m_children; + dup->m_file = m_file; + dup->m_properties = m_properties; + dup->m_propertyDeclarations = m_propertyDeclarations; + dup->m_functions = m_functions; + dup->m_modules = m_modules; + return dup; +} + +bool Item::hasProperty(const QString &name) const +{ + for (const Item *item = this; item; item = item->m_prototype) + if (item->m_properties.contains(name)) + return true; + + return false; +} + +bool Item::hasOwnProperty(const QString &name) const +{ + return m_properties.contains(name); +} + +ValuePtr Item::property(const QString &name) const +{ + ValuePtr value; + for (const Item *item = this; item; item = item->m_prototype) + if ((value = item->m_properties.value(name))) + break; + return value; +} + +ItemValuePtr Item::itemProperty(const QString &name, bool create) +{ + ItemValuePtr result; + ValuePtr v = property(name); + if (v && v->type() == Value::ItemValueType) { + result = v.staticCast<ItemValue>(); + } else if (create) { + result = ItemValue::create(Item::create(m_pool)); + setProperty(name, result); + } + return result; +} + +JSSourceValuePtr Item::sourceProperty(const QString &name) const +{ + ValuePtr v = property(name); + if (!v || v->type() != Value::JSSourceValueType) + return JSSourceValuePtr(); + return v.staticCast<JSSourceValue>(); +} + +const PropertyDeclaration Item::propertyDeclaration(const QString &name) const +{ + const PropertyDeclaration decl = m_propertyDeclarations.value(name); + return (!decl.isValid() && m_prototype) ? m_prototype->propertyDeclaration(name) : decl; +} + +void Item::setPropertyObserver(ItemObserver *observer) const +{ + QBS_ASSERT(!observer || !m_propertyObserver, return); // warn if accidentally overwritten + m_propertyObserver = observer; +} + +Item *Item::child(const QString &type, bool checkForMultiple) const +{ + Item *child = 0; + foreach (Item * const currentChild, children()) { + if (currentChild->typeName() == type) { + if (!checkForMultiple) + return currentChild; + if (child) { + ErrorInfo error(Tr::tr("Multiple instances of item '%1' found where at most one " + "is allowed.").arg(type)); + error.append(Tr::tr("First item"), child->location()); + error.append(Tr::tr("Second item"), currentChild->location()); + throw error; + } + child = currentChild; + } + } + return child; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h new file mode 100644 index 000000000..866927a0e --- /dev/null +++ b/src/lib/corelib/language/item.h @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEM_H +#define QBS_ITEM_H + +#include "forward_decls.h" +#include "itemobserver.h" +#include "value.h" +#include "functiondeclaration.h" +#include "propertydeclaration.h" +#include <parser/qmljsmemorypool_p.h> +#include <tools/codelocation.h> +#include <tools/error.h> +#include <tools/weakpointer.h> + +#include <QList> +#include <QMap> +#include <QSharedPointer> +#include <QStringList> + +namespace qbs { +namespace Internal { + +class ItemPool; +class ProjectFile; + +class Item : public QbsQmlJS::Managed +{ + friend class BuiltinDeclarations; + friend class ItemPool; + friend class ItemReaderASTVisitor; + Q_DISABLE_COPY(Item) + Item(ItemPool *pool); + +public: + ~Item(); + + struct Module + { + Module() + : item(0) + {} + + QStringList name; + Item *item; + }; + typedef QList<Module> Modules; + typedef QMap<QString, PropertyDeclaration> PropertyDeclarationMap; + typedef QMap<QString, ValuePtr> PropertyMap; + + static Item *create(ItemPool *pool); + Item *clone(ItemPool *pool) const; + ItemPool *pool() const; + + const QString &id() const; + const QString &typeName() const; + const CodeLocation &location() const; + Item *prototype() const; + Item *scope() const; + bool isModuleInstance() const; + Item *outerItem() const; + Item *parent() const; + const FileContextPtr file() const; + QList<Item *> children() const; + Item *child(const QString &type, bool checkForMultiple = true) const; + const PropertyMap &properties() const; + const PropertyDeclarationMap &propertyDeclarations() const; + const PropertyDeclaration propertyDeclaration(const QString &name) const; + const Modules &modules() const; + Modules &modules(); + const ErrorInfo &error() const { return m_error; } + + bool hasProperty(const QString &name) const; + bool hasOwnProperty(const QString &name) const; + ValuePtr property(const QString &name) const; + ItemValuePtr itemProperty(const QString &name, bool create = false); + JSSourceValuePtr sourceProperty(const QString &name) const; + void setPropertyObserver(ItemObserver *observer) const; + void setProperty(const QString &name, const ValuePtr &value); + void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); + void setTypeName(const QString &name); + void setLocation(const CodeLocation &location); + void setPrototype(Item *prototype); + void setFile(const FileContextPtr &file); + void setScope(Item *item); + void setModuleInstanceFlag(bool b); + void setOuterItem(Item *item); + void setChildren(const QList<Item *> &children); + void setParent(Item *item); + static void addChild(Item *parent, Item *child); + void setError(const ErrorInfo &error) { m_error = error; } + +private: + ItemPool *m_pool; + mutable ItemObserver *m_propertyObserver; + QString m_id; + QString m_typeName; + CodeLocation m_location; + bool m_moduleInstance; + Item *m_prototype; + Item *m_scope; + Item *m_outerItem; + Item *m_parent; + QList<Item *> m_children; + FileContextPtr m_file; + PropertyMap m_properties; + PropertyDeclarationMap m_propertyDeclarations; + QList<FunctionDeclaration> m_functions; + Modules m_modules; + ErrorInfo m_error; // For SubProject items. May or may not be reported depending on their condition. +}; + +inline ItemPool *Item::pool() const +{ + return m_pool; +} + +inline const QString &Item::id() const +{ + return m_id; +} + +inline const QString &Item::typeName() const +{ + return m_typeName; +} + +inline const CodeLocation &Item::location() const +{ + return m_location; +} + +inline Item *Item::prototype() const +{ + return m_prototype; +} + +inline Item *Item::scope() const +{ + return m_scope; +} + +inline bool Item::isModuleInstance() const +{ + return m_moduleInstance; +} + +inline Item *Item::outerItem() const +{ + return m_outerItem; +} + +inline Item *Item::parent() const +{ + return m_parent; +} + +inline const FileContextPtr Item::file() const +{ + return m_file; +} + +inline QList<Item *> Item::children() const +{ + return m_children; +} + +inline const Item::PropertyMap &Item::properties() const +{ + return m_properties; +} + +inline const Item::PropertyDeclarationMap &Item::propertyDeclarations() const +{ + return m_propertyDeclarations; +} + +inline void Item::setProperty(const QString &name, const ValuePtr &value) +{ + m_properties.insert(name, value); + if (m_propertyObserver) + m_propertyObserver->onItemPropertyChanged(this); +} + +inline void Item::setPropertyDeclaration(const QString &name, + const PropertyDeclaration &declaration) +{ + m_propertyDeclarations.insert(name, declaration); +} + +inline void Item::setTypeName(const QString &name) +{ + m_typeName = name; +} + +inline void Item::setLocation(const CodeLocation &location) +{ + m_location = location; +} + +inline void Item::setPrototype(Item *prototype) +{ + m_prototype = prototype; +} + +inline void Item::setFile(const FileContextPtr &file) +{ + m_file = file; +} + +inline void Item::setScope(Item *item) +{ + m_scope = item; +} + +inline void Item::setModuleInstanceFlag(bool b) +{ + m_moduleInstance = b; +} + +inline void Item::setOuterItem(Item *item) +{ + m_outerItem = item; +} + +inline void Item::setChildren(const QList<Item *> &children) +{ + m_children = children; +} + +inline void Item::setParent(Item *item) +{ + m_parent = item; +} + +inline void Item::addChild(Item *parent, Item *child) +{ + parent->m_children.append(child); + child->setParent(parent); +} + +inline const Item::Modules &Item::modules() const +{ + return m_modules; +} + +inline Item::Modules &Item::modules() +{ + return m_modules; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEM_H diff --git a/src/lib/corelib/language/itemdeclaration.cpp b/src/lib/corelib/language/itemdeclaration.cpp new file mode 100644 index 000000000..e2fdb9330 --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "itemdeclaration.h" + +namespace qbs { +namespace Internal { + +ItemDeclaration::ItemDeclaration(const QString &typeName) + : m_typeName(typeName) +{ +} + +ItemDeclaration::ItemDeclaration(const qbs::Internal::ItemDeclaration &other) + : m_typeName(other.m_typeName) + , m_properties(other.m_properties) + , m_allowedChildTypes(other.m_allowedChildTypes) +{ +} + +ItemDeclaration &ItemDeclaration::operator<<(const PropertyDeclaration &decl) +{ + m_properties.append(decl); + return *this; +} + +bool ItemDeclaration::isChildTypeAllowed(const QString &typeName) const +{ + return m_allowedChildTypes.contains(typeName); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.h b/src/lib/corelib/language/itemdeclaration.h new file mode 100644 index 000000000..25a0e3fd3 --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEMDECLARATION_H +#define QBS_ITEMDECLARATION_H + +#include "propertydeclaration.h" +#include <QSet> + +namespace qbs { +namespace Internal { + +class ItemDeclaration +{ +public: + ItemDeclaration(const QString &typeName = QString()); + ItemDeclaration(const ItemDeclaration &other); + + const QString &typeName() const { return m_typeName; } + + typedef QList<PropertyDeclaration> Properties; + void setProperties(const Properties &props) { m_properties = props; } + const Properties &properties() const { return m_properties; } + + ItemDeclaration &operator<<(const PropertyDeclaration &decl); + + typedef QSet<QString> TypeNames; + void setAllowedChildTypes(const TypeNames &typeNames) { m_allowedChildTypes = typeNames; } + const TypeNames &allowedChildTypes() const { return m_allowedChildTypes; } + bool isChildTypeAllowed(const QString &typeName) const; + +private: + QString m_typeName; + Properties m_properties; + TypeNames m_allowedChildTypes; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMDECLARATION_H diff --git a/src/lib/corelib/language/itemobserver.h b/src/lib/corelib/language/itemobserver.h new file mode 100644 index 000000000..684b33d14 --- /dev/null +++ b/src/lib/corelib/language/itemobserver.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEMOBSERVER_H +#define QBS_ITEMOBSERVER_H + +#include <QString> + +namespace qbs { +namespace Internal { + +class Item; + +class ItemObserver +{ +public: + virtual void onItemPropertyChanged(Item *item) = 0; + virtual void onItemDestroyed(Item *item) = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMOBSERVER_H diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp new file mode 100644 index 000000000..a07022c3f --- /dev/null +++ b/src/lib/corelib/language/itempool.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "itempool.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +ItemPool::ItemPool() +{ +} + +ItemPool::~ItemPool() +{ + for (ItemVector::const_iterator it = m_items.constBegin(); it != m_items.constEnd(); ++it) + (*it)->~Item(); +} + +Item *ItemPool::allocateItem() +{ + Item *item = new (&m_pool) Item(this); + m_items.push_back(item); + return item; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itempool.h b/src/lib/corelib/language/itempool.h new file mode 100644 index 000000000..04d7bbb40 --- /dev/null +++ b/src/lib/corelib/language/itempool.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEMPOOL_H +#define QBS_ITEMPOOL_H + +#include <parser/qmljsmemorypool_p.h> + +#include <QList> + +namespace qbs { +namespace Internal { + +class Item; + +class ItemPool +{ + Q_DISABLE_COPY(ItemPool) +public: + ItemPool(); + ~ItemPool(); + + Item *allocateItem(); + +private: + QbsQmlJS::MemoryPool m_pool; + typedef QList<Item *> ItemVector; + ItemVector m_items; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMPOOL_H diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp new file mode 100644 index 000000000..ba4859899 --- /dev/null +++ b/src/lib/corelib/language/itemreader.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "itemreader.h" +#include "asttools.h" +#include "itemreaderastvisitor.h" +#include <logging/translator.h> +#include <parser/qmljsengine_p.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/error.h> +#include <QExplicitlySharedDataPointer> +#include <QFile> +#include <QFileInfo> +#include <QSharedData> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +class ASTCacheValueData : public QSharedData +{ + Q_DISABLE_COPY(ASTCacheValueData) +public: + ASTCacheValueData() + : ast(0) + , processing(false) + { + } + + QString code; + QbsQmlJS::Engine engine; + QbsQmlJS::AST::UiProgram *ast; + bool processing; +}; + +class ASTCacheValue +{ +public: + ASTCacheValue() + : d(new ASTCacheValueData) + { + } + + ASTCacheValue(const ASTCacheValue &other) + : d(other.d) + { + } + + void setProcessingFlag(bool b) { d->processing = b; } + bool isProcessing() const { return d->processing; } + + void setCode(const QString &code) { d->code = code; } + QString code() const { return d->code; } + + QbsQmlJS::Engine *engine() const { return &d->engine; } + + void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; } + QbsQmlJS::AST::UiProgram *ast() const { return d->ast; } + bool isValid() const { return d->ast; } + +private: + QExplicitlySharedDataPointer<ASTCacheValueData> d; +}; + +class ItemReader::ASTCache : public QHash<QString, ASTCacheValue> {}; + + +ItemReader::ItemReader(BuiltinDeclarations *builtins, const Logger &logger) + : m_pool(0) + , m_builtins(builtins) + , m_logger(logger) + , m_astCache(new ASTCache) +{ +} + +ItemReader::~ItemReader() +{ + delete m_astCache; +} + +void ItemReader::setSearchPaths(const QStringList &searchPaths) +{ + m_searchPaths = searchPaths; +} + +void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) +{ + m_extraSearchPaths.push(extraSearchPaths); +} + +void ItemReader::popExtraSearchPaths() +{ + m_extraSearchPaths.pop(); +} + +QStringList ItemReader::searchPaths() const +{ + QStringList paths = m_searchPaths; + if (!m_extraSearchPaths.isEmpty()) + paths += m_extraSearchPaths.top(); + return paths; +} + +void ItemReader::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) +{ + m_directoryEntries.insert(dirPath, entries); +} + +bool ItemReader::findDirectoryEntries(const QString &dirPath, QStringList *entries) const +{ + const QHash<QString, QStringList>::ConstIterator it = m_directoryEntries.constFind(dirPath); + if (it == m_directoryEntries.constEnd()) + return false; + *entries = it.value(); + return true; +} + +Item *ItemReader::readFile(const QString &filePath) +{ + Item * const item = internalReadFile(filePath).rootItem; + return item; +} + +QSet<QString> ItemReader::filesRead() const +{ + return m_filesRead; +} + +ItemReaderResult ItemReader::internalReadFile(const QString &filePath) +{ + ASTCacheValue &cacheValue = (*m_astCache)[filePath]; + if (cacheValue.isValid()) { + if (Q_UNLIKELY(cacheValue.isProcessing())) + throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); + } else { + QFile file(filePath); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(Tr::tr("Couldn't open '%1'.").arg(filePath)); + + m_filesRead.insert(filePath); + const QString code = QTextStream(&file).readAll(); + QbsQmlJS::Lexer lexer(cacheValue.engine()); + lexer.setCode(code, 1); + QbsQmlJS::Parser parser(cacheValue.engine()); + + file.close(); + if (!parser.parse()) { + QList<QbsQmlJS::DiagnosticMessage> parserMessages = parser.diagnosticMessages(); + if (Q_UNLIKELY(!parserMessages.isEmpty())) { + ErrorInfo err; + foreach (const QbsQmlJS::DiagnosticMessage &msg, parserMessages) + err.append(msg.message, toCodeLocation(filePath, msg.loc)); + throw err; + } + } + + cacheValue.setCode(code); + cacheValue.setAst(parser.ast()); + } + + ItemReaderResult result; + ItemReaderASTVisitor itemReader(this, &result); + itemReader.setFilePath(QFileInfo(filePath).absoluteFilePath()); + itemReader.setSourceCode(cacheValue.code()); + cacheValue.setProcessingFlag(true); + cacheValue.ast()->accept(&itemReader); + cacheValue.setProcessingFlag(false); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h new file mode 100644 index 000000000..266c30f93 --- /dev/null +++ b/src/lib/corelib/language/itemreader.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEMREADER_H +#define QBS_ITEMREADER_H + +#include "forward_decls.h" +#include <logging/logger.h> + +#include <QHash> +#include <QSet> +#include <QStack> +#include <QStringList> + +namespace qbs { +namespace Internal { + +class BuiltinDeclarations; +class Item; +class ItemPool; + +struct ItemReaderResult +{ + ItemReaderResult() + : rootItem(0) + {} + + Item *rootItem; + typedef QHash<const Item *, QSet<JSSourceValuePtr> > SourceValuesPerItem; + SourceValuesPerItem conditionalValuesPerScopeItem; +}; + +/* + * Reads a qbs file and creates a tree of Item objects. + * + * In this stage the following steps are performed: + * - The QML/JS parser creates the AST. + * - The AST is converted to a tree of Item objects. + * + * This class is also responsible for the QMLish inheritance semantics. + */ +class ItemReader +{ + friend class ItemReaderASTVisitor; +public: + ItemReader(BuiltinDeclarations *builtins, const Logger &logger); + ~ItemReader(); + + BuiltinDeclarations *builtins() const { return m_builtins; } + Logger logger() const { return m_logger; } + + void setPool(ItemPool *pool) { m_pool = pool; } + void setSearchPaths(const QStringList &searchPaths); + void pushExtraSearchPaths(const QStringList &extraSearchPaths); + void popExtraSearchPaths(); + QStringList searchPaths() const; + + Item *readFile(const QString &filePath); + + QSet<QString> filesRead() const; + +private: + ItemReaderResult internalReadFile(const QString &filePath); + + void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); + bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; + + ItemPool *m_pool; + BuiltinDeclarations *m_builtins; + Logger m_logger; + QStringList m_searchPaths; + QStack<QStringList> m_extraSearchPaths; + QHash<const Item *, QSet<JSSourceValuePtr> > m_conditionalValuesPerScopeItem; + + class ASTCache; + ASTCache *m_astCache; + QSet<QString> m_filesRead; + QHash<QString, QStringList> m_directoryEntries; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADER_H diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp new file mode 100644 index 000000000..a08a73648 --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.cpp @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "itemreaderastvisitor.h" +#include "asttools.h" +#include "builtindeclarations.h" +#include "identifiersearch.h" +#include "itemreader.h" +#include <jsextensions/jsextensions.h> +#include <parser/qmljsast_p.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> +#include <logging/translator.h> + +#include <QDirIterator> +#include <QFileInfo> +#include <QStringList> + +using namespace QbsQmlJS; + +namespace qbs { +namespace Internal { + +ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReader *reader, ItemReaderResult *result) + : m_reader(reader) + , m_readerResult(result) + , m_languageVersion(ImportVersion::fromString(reader->builtins()->languageVersion())) + , m_item(0) + , m_sourceValue(0) +{ +} + +ItemReaderASTVisitor::~ItemReaderASTVisitor() +{ +} + +bool ItemReaderASTVisitor::visit(AST::UiProgram *ast) +{ + Q_UNUSED(ast); + m_sourceValue.clear(); + m_file = FileContext::create(); + m_file->m_filePath = m_filePath; + + if (Q_UNLIKELY(!ast->members->member)) + throw ErrorInfo(Tr::tr("No root item found in %1.").arg(m_filePath)); + + return true; +} + +bool ItemReaderASTVisitor::addPrototype(const QString &fileName, const QString &filePath, + const QString &as, bool needsCheck) +{ + if (needsCheck && fileName.size() <= 4) + return false; + + const QString componentName = fileName.left(fileName.size() - 4); + // ### validate componentName + + if (needsCheck && !componentName.at(0).isUpper()) + return false; + + QStringList prototypeName; + if (!as.isEmpty()) + prototypeName.append(as); + prototypeName.append(componentName); + m_typeNameToFile.insert(prototypeName, filePath); + return true; +} + +void ItemReaderASTVisitor::collectPrototypes(const QString &path, const QString &as) +{ + QStringList fileNames; // Yes, file *names*. + if (m_reader->findDirectoryEntries(path, &fileNames)) { + foreach (const QString &fileName, fileNames) + addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); + return; + } + + QDirIterator dirIter(path, QStringList("*.qbs")); + while (dirIter.hasNext()) { + const QString filePath = dirIter.next(); + const QString fileName = dirIter.fileName(); + if (addPrototype(fileName, filePath, as, true)) + fileNames << fileName; + } + m_reader->cacheDirectoryEntries(path, fileNames); +} + +bool ItemReaderASTVisitor::visit(AST::UiImportList *uiImportList) +{ + foreach (const QString &searchPath, m_reader->searchPaths()) + collectPrototypes(searchPath + QLatin1String("/imports"), QString()); + + const QString path = FileInfo::path(m_filePath); + + // files in the same directory are available as prototypes + collectPrototypes(path, QString()); + + QSet<QString> importAsNames; + QHash<QString, JsImport> jsImports; + + for (const AST::UiImportList *it = uiImportList; it; it = it->next) { + const AST::UiImport *const import = it->import; + + QStringList importUri; + bool isBase = false; + if (import->importUri) { + importUri = toStringList(import->importUri); + isBase = (importUri.size() == 1 && importUri.first() == QLatin1String("qbs")) + || (importUri.size() == 2 && importUri.first() == QLatin1String("qbs") + && importUri.last() == QLatin1String("base")); + if (isBase) + checkImportVersion(import->versionToken); + else if (import->versionToken.length) + m_reader->logger().printWarning(ErrorInfo(Tr::tr("Superfluous version specification."), + toCodeLocation(import->versionToken))); + } + + QString as; + if (isBase) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of qbs.base must have no 'as <Name>'"), + toCodeLocation(import->importIdToken)); + } + } else { + if (importUri.count() == 2 && importUri.first() == QLatin1String("qbs")) { + const QString extensionName = importUri.last(); + if (JsExtensions::hasExtension(extensionName)) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of built-in extension '%1' " + "must not have 'as' specifier.").arg(extensionName)); + } + if (Q_UNLIKELY(m_file->m_jsExtensions.contains(extensionName))) { + m_reader->logger().printWarning(Tr::tr("Built-in extension '%1' already " + "imported.").arg(extensionName)); + } else { + m_file->m_jsExtensions << extensionName; + } + continue; + } + } + + if (import->importId.isNull()) { + if (!import->fileName.isNull()) { + throw ErrorInfo(Tr::tr("File imports require 'as <Name>'"), + toCodeLocation(import->importToken)); + } + if (importUri.isEmpty()) { + throw ErrorInfo(Tr::tr("Invalid import URI."), + toCodeLocation(import->importToken)); + } + as = importUri.last(); + } else { + as = import->importId.toString(); + } + + if (Q_UNLIKELY(importAsNames.contains(as))) { + throw ErrorInfo(Tr::tr("Can't import into the same name more than once."), + toCodeLocation(import->importIdToken)); + } + if (Q_UNLIKELY(JsExtensions::hasExtension(as))) { + throw ErrorInfo(Tr::tr("Cannot reuse the name of built-in extension '%1'.") + .arg(as)); + } + importAsNames.insert(as); + } + + if (!import->fileName.isNull()) { + QString name = FileInfo::resolvePath(path, import->fileName.toString()); + + QFileInfo fi(name); + if (Q_UNLIKELY(!fi.exists())) + throw ErrorInfo(Tr::tr("Can't find imported file %0.").arg(name), + CodeLocation(m_filePath, import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + name = fi.canonicalFilePath(); + if (fi.isDir()) { + collectPrototypes(name, as); + } else { + if (name.endsWith(".js", Qt::CaseInsensitive)) { + JsImport &jsImport = jsImports[as]; + jsImport.scopeName = as; + jsImport.fileNames.append(name); + jsImport.location = toCodeLocation(import->firstSourceLocation()); + } else if (name.endsWith(".qbs", Qt::CaseInsensitive)) { + m_typeNameToFile.insert(QStringList(as), name); + } else { + throw ErrorInfo(Tr::tr("Can only import .qbs and .js files"), + CodeLocation(m_filePath, import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + } + } + } else if (!importUri.isEmpty()) { + const QString importPath = isBase + ? QLatin1String("qbs/base") : importUri.join(QDir::separator()); + bool found = m_typeNameToFile.contains(importUri); + if (!found) { + foreach (const QString &searchPath, m_reader->searchPaths()) { + const QFileInfo fi(FileInfo::resolvePath( + FileInfo::resolvePath(searchPath, "imports"), importPath)); + if (fi.isDir()) { + // ### versioning, qbsdir file, etc. + const QString &resultPath = fi.absoluteFilePath(); + collectPrototypes(resultPath, as); + + QDirIterator dirIter(resultPath, QStringList("*.js")); + while (dirIter.hasNext()) { + dirIter.next(); + JsImport &jsImport = jsImports[as]; + if (jsImport.scopeName.isNull()) { + jsImport.scopeName = as; + jsImport.location = toCodeLocation(import->firstSourceLocation()); + } + jsImport.fileNames.append(dirIter.filePath()); + } + found = true; + break; + } + } + } + if (Q_UNLIKELY(!found)) { + throw ErrorInfo(Tr::tr("import %1 not found").arg(importUri.join(".")), + toCodeLocation(import->fileNameToken)); + } + } + } + + for (QHash<QString, JsImport>::const_iterator it = jsImports.constBegin(); + it != jsImports.constEnd(); ++it) + { + m_file->m_jsImports += it.value(); + } + + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) +{ + const QString typeName = ast->qualifiedTypeNameId->name.toString(); + + Item *item = Item::create(m_reader->m_pool); + item->m_file = m_file; + item->m_parent = m_item; + item->m_typeName = typeName; + item->m_location = ::qbs::Internal::toCodeLocation(m_file->filePath(), + ast->qualifiedTypeNameId->identifierToken); + + if (m_item) { + // Add this item to the children of the parent item. + m_item->m_children += item; + } else { + // This is the root item. + m_item = item; + m_readerResult->rootItem = item; + } + + if (ast->initializer) { + qSwap(m_item, item); + ast->initializer->accept(this); + qSwap(m_item, item); + } + + m_reader->m_builtins->setupItemForBuiltinType(item); + + if (item->typeName() != QLatin1String("Properties") + && item->typeName() != QLatin1String("SubProject")) { + setupAlternatives(item); + } + + // resolve inheritance + const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId); + const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName); + if (!baseTypeFileName.isEmpty()) { + const ItemReaderResult baseFile = m_reader->internalReadFile(baseTypeFileName); + mergeItem(item, baseFile.rootItem, baseFile); + if (baseFile.rootItem->m_file->m_idScope) { + // Make ids from the derived file visible in the base file. + // ### Do we want to turn off this feature? It's QMLish but kind of strange. + ensureIdScope(item->m_file); + baseFile.rootItem->m_file->m_idScope->setPrototype(item->m_file->m_idScope); + } + } + + return false; +} + +void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName, + const AST::SourceLocation &sourceLocation) +{ + if (Q_UNLIKELY(item->properties().contains(bindingName.last()))) { + QString msg = Tr::tr("Duplicate binding for '%1'"); + throw ErrorInfo(msg.arg(bindingName.join(".")), + qbs::Internal::toCodeLocation(m_file->filePath(), sourceLocation)); + } +} + +bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast) +{ + PropertyDeclaration p; + if (Q_UNLIKELY(ast->name.isEmpty())) + throw ErrorInfo(Tr::tr("public member without name")); + if (Q_UNLIKELY(ast->memberType.isEmpty())) + throw ErrorInfo(Tr::tr("public member without type")); + if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal)) + throw ErrorInfo(Tr::tr("public member with signal type not supported")); + p.name = ast->name.toString(); + p.type = PropertyDeclaration::propertyTypeFromString(ast->memberType.toString()); + if (p.type == PropertyDeclaration::UnknownType) + throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.") + .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken)); + if (ast->typeModifier.compare(QLatin1String("list"))) + p.flags |= PropertyDeclaration::ListProperty; + else if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) + throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg( + ast->typeModifier.toString())); + + m_item->m_propertyDeclarations.insert(p.name, p); + + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + if (ast->statement) { + m_sourceValue.swap(value); + visitStatement(ast->statement); + m_sourceValue.swap(value); + const QStringList bindingName(p.name); + checkDuplicateBinding(m_item, bindingName, ast->colonToken); + } + + m_item->m_properties.insert(p.name, value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) +{ + QBS_CHECK(ast->qualifiedId); + QBS_CHECK(!ast->qualifiedId->name.isEmpty()); + + const QStringList bindingName = toStringList(ast->qualifiedId); + + if (bindingName.length() == 1 && bindingName.first() == QLatin1String("id")) { + AST::ExpressionStatement *expStmt = + AST::cast<AST::ExpressionStatement *>(ast->statement); + if (Q_UNLIKELY(!expStmt)) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + AST::IdentifierExpression *idExp = + AST::cast<AST::IdentifierExpression *>(expStmt->expression); + if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + m_item->m_id = idExp->name.toString(); + ensureIdScope(m_file); + m_file->m_idScope->m_properties[m_item->m_id] = ItemValue::create(m_item); + return false; + } + + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + m_sourceValue.swap(value); + visitStatement(ast->statement); + m_sourceValue.swap(value); + + Item *targetItem = targetItemForBinding(m_item, bindingName, value->location()); + checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken); + targetItem->m_properties.insert(bindingName.last(), value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::FunctionDeclaration *ast) +{ + FunctionDeclaration f; + if (Q_UNLIKELY(ast->name.isNull())) + throw ErrorInfo(Tr::tr("function decl without name")); + f.setName(ast->name.toString()); + + // remove the name + QString funcNoName = textOf(m_sourceCode, ast); + funcNoName.replace(QRegExp("^(\\s*function\\s*)\\w*"), "(\\1"); + funcNoName.append(")"); + f.setSourceCode(funcNoName); + + f.setLocation(toCodeLocation(ast->firstSourceLocation())); + m_item->m_functions += f; + return false; +} + +bool ItemReaderASTVisitor::visitStatement(AST::Statement *statement) +{ + QBS_CHECK(statement); + QBS_CHECK(m_sourceValue); + + QString sourceCode = textOf(m_sourceCode, statement); + if (AST::cast<AST::Block *>(statement)) { + // rewrite blocks to be able to use return statements in property assignments + sourceCode.prepend("(function()"); + sourceCode.append(")()"); + m_sourceValue->m_hasFunctionForm = true; + } + + m_sourceValue->setSourceCode(sourceCode); + m_sourceValue->setLocation(toCodeLocation(statement->firstSourceLocation())); + + IdentifierSearch idsearch; + idsearch.add(QLatin1String("base"), &m_sourceValue->m_sourceUsesBase); + idsearch.add(QLatin1String("outer"), &m_sourceValue->m_sourceUsesOuter); + idsearch.start(statement); + return false; +} + +CodeLocation ItemReaderASTVisitor::toCodeLocation(AST::SourceLocation location) const +{ + return CodeLocation(m_filePath, location.startLine, location.startColumn); +} + +Item *ItemReaderASTVisitor::targetItemForBinding(Item *item, + const QStringList &bindingName, + const CodeLocation &bindingLocation) +{ + Item *targetItem = item; + const int c = bindingName.count() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->m_properties.value(bindingName.at(i)); + if (!v) { + Item *newItem = Item::create(m_reader->m_pool); + v = ItemValue::create(newItem); + targetItem->m_properties.insert(bindingName.at(i), v); + } + if (Q_UNLIKELY(v->type() != Value::ItemValueType)) { + QString msg = Tr::tr("Binding to non-item property."); + throw ErrorInfo(msg, bindingLocation); + } + ItemValuePtr jsv = v.staticCast<ItemValue>(); + targetItem = jsv->item(); + } + return targetItem; +} + +void ItemReaderASTVisitor::checkImportVersion(const AST::SourceLocation &versionToken) const +{ + if (!versionToken.length) + return; + const QString importVersionString = m_sourceCode.mid(versionToken.offset, versionToken.length); + const ImportVersion importVersion + = ImportVersion::fromString(importVersionString, toCodeLocation(versionToken)); + if (Q_UNLIKELY(importVersion != m_languageVersion)) + throw ErrorInfo(Tr::tr("Incompatible qbs version %1. This is qbs %2.").arg( + importVersionString, m_reader->builtins()->languageVersion()), + toCodeLocation(versionToken)); +} + +void ItemReaderASTVisitor::mergeItem(Item *dst, const Item *src, + const ItemReaderResult &baseFile) +{ + if (!src->typeName().isEmpty()) + dst->setTypeName(src->typeName()); + + int insertPos = 0; + for (int i = 0; i < src->m_children.count(); ++i) { + Item *child = src->m_children.at(i); + dst->m_children.insert(insertPos++, child); + child->m_parent = dst; + } + + for (QMap<QString, ValuePtr>::const_iterator it = src->m_properties.constBegin(); + it != src->m_properties.constEnd(); ++it) + { + ValuePtr &v = dst->m_properties[it.key()]; + if (v) { + if (v->type() == it.value()->type()) { + if (v->type() == Value::JSSourceValueType) { + JSSourceValuePtr sv = v.staticCast<JSSourceValue>(); + while (sv->baseValue()) + sv = sv->baseValue(); + const JSSourceValuePtr baseValue = it.value().staticCast<JSSourceValue>(); + sv->setBaseValue(baseValue); + for (QList<JSSourceValue::Alternative>::iterator it + = sv->m_alternatives.begin(); it != sv->m_alternatives.end(); ++it) { + JSSourceValue::Alternative &alternative = *it; + alternative.value->setBaseValue(baseValue); + } + } else if (v->type() == Value::ItemValueType) { + QBS_CHECK(v.staticCast<ItemValue>()->item()); + QBS_CHECK(it.value().staticCast<const ItemValue>()->item()); + mergeItem(v.staticCast<ItemValue>()->item(), + it.value().staticCast<const ItemValue>()->item(), + baseFile); + } else { + QBS_CHECK(!"unexpected value type"); + } + } + } else { + v = it.value(); + } + } + + for (QMap<QString, PropertyDeclaration>::const_iterator it + = src->m_propertyDeclarations.constBegin(); + it != src->m_propertyDeclarations.constEnd(); ++it) { + dst->m_propertyDeclarations[it.key()] = it.value(); + } + foreach (const JSSourceValuePtr &valueWithAlternatives, + baseFile.conditionalValuesPerScopeItem.value(src)) { + replaceConditionScopes(valueWithAlternatives, dst); + } +} + +void ItemReaderASTVisitor::ensureIdScope(const FileContextPtr &file) +{ + if (!file->m_idScope) { + file->m_idScope = Item::create(m_reader->m_pool); + file->m_idScope->m_typeName = QLatin1String("IdScope"); + } +} + +void ItemReaderASTVisitor::setupAlternatives(Item *item) +{ + QList<Item *>::iterator it = item->m_children.begin(); + while (it != item->m_children.end()) { + Item *child = *it; + if (child->typeName() == QLatin1String("Properties")) { + handlePropertiesBlock(item, child); + it = item->m_children.erase(it); + } else { + ++it; + } + } +} + +void ItemReaderASTVisitor::replaceConditionScopes(const JSSourceValuePtr &value, + Item *newScope) +{ + for (QList<JSSourceValue::Alternative>::iterator it + = value->m_alternatives.begin(); it != value->m_alternatives.end(); ++it) + it->conditionScopeItem = newScope; +} + +class PropertiesBlockConverter +{ +public: + PropertiesBlockConverter(const QString &condition, Item *propertiesBlockContainer, + const Item *propertiesBlock, + QSet<JSSourceValuePtr> *valuesWithAlternatives) + : m_propertiesBlockContainer(propertiesBlockContainer) + , m_propertiesBlock(propertiesBlock) + , m_valuesWithAlternatives(valuesWithAlternatives) + { + m_alternative.condition = condition; + m_alternative.conditionScopeItem = propertiesBlockContainer; + } + + void operator()() + { + apply(m_propertiesBlockContainer, m_propertiesBlock); + } + +private: + JSSourceValue::Alternative m_alternative; + Item *m_propertiesBlockContainer; + const Item *m_propertiesBlock; + QSet<JSSourceValuePtr> *m_valuesWithAlternatives; + + void apply(Item *a, const Item *b) + { + for (QMap<QString, ValuePtr>::const_iterator it = b->properties().constBegin(); + it != b->properties().constEnd(); ++it) { + if (b == m_propertiesBlock && it.key() == QLatin1String("condition")) + continue; + if (it.value()->type() == Value::ItemValueType) { + apply(a->itemProperty(it.key(), true)->item(), + it.value().staticCast<ItemValue>()->item()); + } else if (it.value()->type() == Value::JSSourceValueType) { + ValuePtr aval = a->property(it.key()); + if (Q_UNLIKELY(aval && aval->type() != Value::JSSourceValueType)) + throw ErrorInfo(Tr::tr("Incompatible value type in unconditional value at %1.").arg( + aval->location().toString())); + apply(it.key(), a, aval.staticCast<JSSourceValue>(), + it.value().staticCast<JSSourceValue>()); + } else { + QBS_CHECK(!"Unexpected value type in conditional value."); + } + } + } + + void apply(const QString &propertyName, Item *item, JSSourceValuePtr value, + const JSSourceValuePtr &conditionalValue) + { + QBS_ASSERT(!value || value->file() == conditionalValue->file(), return); + if (!value) { + value = JSSourceValue::create(); + value->setFile(conditionalValue->file()); + item->setProperty(propertyName, value); + value->setSourceCode(QLatin1String("undefined")); + } + m_alternative.value = conditionalValue; + value->addAlternative(m_alternative); + m_valuesWithAlternatives->insert(value); + } +}; + +void ItemReaderASTVisitor::handlePropertiesBlock(Item *item, const Item *block) +{ + ValuePtr value = block->property(QLatin1String("condition")); + if (Q_UNLIKELY(!value)) + throw ErrorInfo(Tr::tr("Properties.condition must be provided."), + block->location()); + if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) + throw ErrorInfo(Tr::tr("Properties.condition must be a value binding."), + block->location()); + JSSourceValuePtr srcval = value.staticCast<JSSourceValue>(); + const QString condition = srcval->sourceCode(); + PropertiesBlockConverter convertBlock(condition, item, block, + &m_readerResult->conditionalValuesPerScopeItem[item]); + convertBlock(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h new file mode 100644 index 000000000..409b5104e --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_ITEMREADERASTVISITOR_H +#define QBS_ITEMREADERASTVISITOR_H + +#include "importversion.h" +#include "item.h" +#include "filecontext.h" +#include <parser/qmljsastvisitor_p.h> +#include <QHash> + +namespace qbs { +namespace Internal { + +class ItemReader; +struct ItemReaderResult; + +class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor +{ +public: + ItemReaderASTVisitor(ItemReader *reader, ItemReaderResult *result); + ~ItemReaderASTVisitor(); + + void setFilePath(const QString &filePath) { m_filePath = filePath; } + void setSourceCode(const QString &sourceCode) { m_sourceCode = sourceCode; } + + bool visit(QbsQmlJS::AST::UiProgram *ast); + bool visit(QbsQmlJS::AST::UiImportList *uiImportList); + bool visit(QbsQmlJS::AST::UiObjectDefinition *ast); + bool visit(QbsQmlJS::AST::UiPublicMember *ast); + bool visit(QbsQmlJS::AST::UiScriptBinding *ast); + bool visit(QbsQmlJS::AST::FunctionDeclaration *ast); + +private: + bool visitStatement(QbsQmlJS::AST::Statement *statement); + CodeLocation toCodeLocation(QbsQmlJS::AST::SourceLocation location) const; + void checkDuplicateBinding(Item *item, const QStringList &bindingName, + const QbsQmlJS::AST::SourceLocation &sourceLocation); + Item *targetItemForBinding(Item *item, const QStringList &binding, + const CodeLocation &bindingLocation); + void checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const; + static void mergeItem(Item *dst, const Item *src, + const ItemReaderResult &baseFile); + void ensureIdScope(const FileContextPtr &file); + void setupAlternatives(Item *item); + static void replaceConditionScopes(const JSSourceValuePtr &value, Item *newScope); + void handlePropertiesBlock(Item *item, const Item *block); + void collectPrototypes(const QString &path, const QString &as); + bool addPrototype(const QString &fileName, const QString &filePath, const QString &as, + bool needsCheck); + + ItemReader *m_reader; + ItemReaderResult *m_readerResult; + const ImportVersion m_languageVersion; + QString m_filePath; + QString m_sourceCode; + FileContextPtr m_file; + QHash<QStringList, QString> m_typeNameToFile; + Item *m_item; + JSSourceValuePtr m_sourceValue; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADERASTVISITOR_H diff --git a/src/lib/corelib/language/jsimports.h b/src/lib/corelib/language/jsimports.h new file mode 100644 index 000000000..4e0ef9130 --- /dev/null +++ b/src/lib/corelib/language/jsimports.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_JSIMPORTS_H +#define QBS_JSIMPORTS_H + +#include <tools/codelocation.h> +#include <QSet> +#include <QStringList> + +namespace qbs { +namespace Internal { + +/** + * Represents JavaScript import of the form + * import 'fileOrDirectory' as scopeName + * + * There can be several filenames per scope + * if we import a whole directory. + */ +class JsImport +{ +public: + QString scopeName; + QStringList fileNames; + CodeLocation location; +}; + +typedef QList<JsImport> JsImports; + +inline bool operator==(const JsImport &jsi1, const JsImport &jsi2) +{ + return jsi1.scopeName == jsi2.scopeName && jsi1.fileNames.toSet() == jsi2.fileNames.toSet(); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_JSIMPORTS_H diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp new file mode 100644 index 000000000..92622787f --- /dev/null +++ b/src/lib/corelib/language/language.cpp @@ -0,0 +1,1124 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "language.h" + +#include "artifactproperties.h" +#include "scriptengine.h" +#include <buildgraph/artifact.h> +#include <buildgraph/productbuilddata.h> +#include <buildgraph/projectbuilddata.h> +#include <buildgraph/rulegraph.h> // TODO: Move to language? +#include <jsextensions/jsextensions.h> +#include <logging/translator.h> +#include <tools/hostosinfo.h> +#include <tools/error.h> +#include <tools/propertyfinder.h> +#include <tools/persistence.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QDirIterator> +#include <QMap> +#include <QMutexLocker> +#include <QScriptValue> + +QT_BEGIN_NAMESPACE +inline QDataStream& operator>>(QDataStream &stream, qbs::Internal::JsImport &jsImport) +{ + stream >> jsImport.scopeName + >> jsImport.fileNames + >> jsImport.location; + return stream; +} + +inline QDataStream& operator<<(QDataStream &stream, const qbs::Internal::JsImport &jsImport) +{ + return stream << jsImport.scopeName + << jsImport.fileNames + << jsImport.location; +} +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +FileTagger::FileTagger(const QStringList &patterns, const FileTags &fileTags) + : m_fileTags(fileTags) +{ + setPatterns(patterns); +} + +void FileTagger::setPatterns(const QStringList &patterns) +{ + m_patterns.clear(); + foreach (const QString &pattern, patterns) { + QBS_CHECK(!pattern.isEmpty()); + m_patterns << QRegExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + } +} + +/*! + * \class FileTagger + * \brief The \c FileTagger class maps 1:1 to the respective item in a qbs source file. + */ +void FileTagger::load(PersistentPool &pool) +{ + setPatterns(pool.idLoadStringList()); + pool.stream() >> m_fileTags; +} + +void FileTagger::store(PersistentPool &pool) const +{ + QStringList patterns; + foreach (const QRegExp ®Exp, m_patterns) + patterns << regExp.pattern(); + pool.storeStringList(patterns); + pool.stream() << m_fileTags; +} + +/*! + * \class SourceArtifact + * \brief The \c SourceArtifact class represents a source file. + * Everything except the file path is inherited from the surrounding \c ResolvedGroup. + * (TODO: Not quite true. Artifacts in transformers will be generated by the transformer, but are + * still represented as source artifacts. We may or may not want to change this; if we do, + * SourceArtifact could simply have a back pointer to the group in addition to the file path.) + * \sa ResolvedGroup + */ +void SourceArtifact::load(PersistentPool &pool) +{ + pool.stream() >> absoluteFilePath; + pool.stream() >> fileTags; + pool.stream() >> overrideFileTags; + properties = pool.idLoadS<PropertyMapInternal>(); +} + +void SourceArtifact::store(PersistentPool &pool) const +{ + pool.stream() << absoluteFilePath; + pool.stream() << fileTags; + pool.stream() << overrideFileTags; + pool.store(properties); +} + +void SourceWildCards::load(PersistentPool &pool) +{ + prefix = pool.idLoadString(); + patterns = pool.idLoadStringList(); + excludePatterns = pool.idLoadStringList(); + pool.loadContainerS(files); +} + +void SourceWildCards::store(PersistentPool &pool) const +{ + pool.storeString(prefix); + pool.storeStringList(patterns); + pool.storeStringList(excludePatterns); + pool.storeContainer(files); +} + +/*! + * \class ResolvedGroup + * \brief The \c ResolvedGroup class corresponds to the Group item in a qbs source file. + */ + + /*! + * \variable ResolvedGroup::files + * \brief The files listed in the group item's "files" binding. + * Note that these do not include expanded wildcards. + */ + +/*! + * \variable ResolvedGroup::wildcards + * \brief Represents the wildcard elements in this group's "files" binding. + * If no wildcards are specified there, this variable is null. + * \sa SourceWildCards + */ + +/*! + * \brief Returns all files specified in the group item as source artifacts. + * This includes the expanded list of wildcards. + */ +QList<SourceArtifactPtr> ResolvedGroup::allFiles() const +{ + QList<SourceArtifactPtr> lst = files; + if (wildcards) + lst.append(wildcards->files); + return lst; +} + +void ResolvedGroup::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + pool.stream() + >> enabled + >> location; + prefix = pool.idLoadString(); + pool.loadContainerS(files); + wildcards = pool.idLoadS<SourceWildCards>(); + properties = pool.idLoadS<PropertyMapInternal>(); + pool.stream() + >> fileTags + >> overrideTags; +} + +void ResolvedGroup::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.stream() + << enabled + << location; + pool.storeString(prefix); + pool.storeContainer(files); + pool.store(wildcards); + pool.store(properties); + pool.stream() + << fileTags + << overrideTags; +} + +/*! + * \class RuleArtifact + * \brief The \c RuleArtifact class represents an Artifact item encountered in the context + * of a Rule item. + * When applying the rule, one \c Artifact object will be constructed from each \c RuleArtifact + * object. During that process, the \c RuleArtifact's bindings are evaluated and the results + * are inserted into the corresponding \c Artifact's properties. + * \sa Rule + */ +void RuleArtifact::load(PersistentPool &pool) +{ + Q_UNUSED(pool); + pool.stream() + >> fileName + >> fileTags + >> alwaysUpdated; + + int i; + pool.stream() >> i; + bindings.clear(); + bindings.reserve(i); + Binding binding; + for (; --i >= 0;) { + pool.stream() >> binding.name >> binding.code >> binding.location; + bindings += binding; + } +} + +void RuleArtifact::store(PersistentPool &pool) const +{ + Q_UNUSED(pool); + pool.stream() + << fileName + << fileTags + << alwaysUpdated; + + pool.stream() << bindings.count(); + for (int i = bindings.count(); --i >= 0;) { + const Binding &binding = bindings.at(i); + pool.stream() << binding.name << binding.code << binding.location; + } +} + +void ResolvedFileContext::load(PersistentPool &pool) +{ + filePath = pool.idLoadString(); + jsExtensions = pool.idLoadStringList(); + pool.stream() >> jsImports; +} + +void ResolvedFileContext::store(PersistentPool &pool) const +{ + pool.storeString(filePath); + pool.storeStringList(jsExtensions); + pool.stream() << jsImports; +} + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ + if (&a == &b) + return true; + if (!!&a != !!&b) + return false; + return a.filePath == b.filePath + && a.jsExtensions == b.jsExtensions + && a.jsImports == b.jsImports; +} + + +/*! + * \class ScriptFunction + * \brief The \c ScriptFunction class represents the JavaScript code found in the "prepare" binding + * of a \c Rule or \c Transformer item in a qbs file. + * \sa Rule + * \sa ResolvedTransformer + */ + + /*! + * \variable ScriptFunction::script + * \brief The actual Javascript code, taken verbatim from the qbs source file. + */ + + /*! + * \variable ScriptFunction::location + * \brief The exact location of the script in the qbs source file. + * This is mostly needed for diagnostics. + */ + +void ScriptFunction::load(PersistentPool &pool) +{ + pool.stream() + >> sourceCode + >> argumentNames + >> location; + fileContext = pool.idLoadS<ResolvedFileContext>(); +} + +void ScriptFunction::store(PersistentPool &pool) const +{ + pool.stream() + << sourceCode + << argumentNames + << location; + pool.store(fileContext); +} + +bool operator==(const ScriptFunction &a, const ScriptFunction &b) +{ + if (&a == &b) + return true; + if (!!&a != !!&b) + return false; + return a.sourceCode == b.sourceCode + && a.location == b.location + && *a.fileContext == *b.fileContext; +} + +void ResolvedModule::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + moduleDependencies = pool.idLoadStringList(); + setupBuildEnvironmentScript = pool.idLoadS<ScriptFunction>(); + setupRunEnvironmentScript = pool.idLoadS<ScriptFunction>(); +} + +void ResolvedModule::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.storeStringList(moduleDependencies); + pool.store(setupBuildEnvironmentScript); + pool.store(setupRunEnvironmentScript); +} + +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2) +{ + if (&m1 == &m2) + return true; + if (!!&m1 != !!&m2) + return false; + return m1.name == m2.name + && m1.moduleDependencies.toSet() == m2.moduleDependencies.toSet() + && *m1.setupBuildEnvironmentScript == *m2.setupBuildEnvironmentScript + && *m1.setupRunEnvironmentScript == *m2.setupRunEnvironmentScript; +} + +static bool modulesAreEqual(const ResolvedModuleConstPtr &m1, const ResolvedModuleConstPtr &m2) +{ + return *m1 == *m2; +} + +QString Rule::toString() const +{ + return QLatin1Char('[') + inputs.toStringList().join(QLatin1String(",")) + QLatin1String(" -> ") + + outputFileTags().toStringList().join(QLatin1String(",")) + QLatin1Char(']'); +} + +FileTags Rule::outputFileTags() const +{ + FileTags result; + foreach (const RuleArtifactConstPtr &artifact, artifacts) + result.unite(artifact->fileTags); + return result; +} + +void Rule::load(PersistentPool &pool) +{ + script = pool.idLoadS<ScriptFunction>(); + module = pool.idLoadS<ResolvedModule>(); + pool.stream() + >> inputs + >> auxiliaryInputs + >> usings + >> explicitlyDependsOn + >> multiplex; + + pool.loadContainerS(artifacts); +} + +void Rule::store(PersistentPool &pool) const +{ + pool.store(script); + pool.store(module); + pool.stream() + << inputs + << auxiliaryInputs + << usings + << explicitlyDependsOn + << multiplex; + + pool.storeContainer(artifacts); +} + +ResolvedProduct::ResolvedProduct() + : enabled(true) +{ +} + +ResolvedProduct::~ResolvedProduct() +{ +} + +/*! + * \brief Returns all files of all groups as source artifacts. + * This includes the expanded list of wildcards. + */ +QList<SourceArtifactPtr> ResolvedProduct::allFiles() const +{ + QList<SourceArtifactPtr> lst; + foreach (const GroupConstPtr &group, groups) + lst += group->allFiles(); + return lst; +} + +/*! + * \brief Returns all files of all enabled groups as source artifacts. + * \sa ResolvedProduct::allFiles() + */ +QList<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const +{ + QList<SourceArtifactPtr> lst; + foreach (const GroupConstPtr &group, groups) { + if (group->enabled) + lst += group->allFiles(); + } + return lst; +} + +FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const +{ + FileTags result; + foreach (FileTaggerConstPtr tagger, fileTaggers) { + foreach (const QRegExp &pattern, tagger->patterns()) { + if (FileInfo::globMatches(pattern, fileName)) { + result.unite(tagger->fileTags()); + break; + } + } + } + return result; +} + +void ResolvedProduct::load(PersistentPool &pool) +{ + pool.stream() + >> enabled + >> fileTags + >> additionalFileTags + >> name + >> targetName + >> sourceDirectory + >> destinationDirectory + >> location; + properties = pool.idLoadS<PropertyMapInternal>(); + pool.loadContainerS(rules); + pool.loadContainerS(dependencies); + pool.loadContainerS(fileTaggers); + pool.loadContainerS(modules); + pool.loadContainerS(transformers); + pool.loadContainerS(groups); + pool.loadContainerS(artifactProperties); + buildData.reset(pool.idLoad<ProductBuildData>()); +} + +void ResolvedProduct::store(PersistentPool &pool) const +{ + pool.stream() + << enabled + << fileTags + << additionalFileTags + << name + << targetName + << sourceDirectory + << destinationDirectory + << location; + + pool.store(properties); + pool.storeContainer(rules); + pool.storeContainer(dependencies); + pool.storeContainer(fileTaggers); + pool.storeContainer(modules); + pool.storeContainer(transformers); + pool.storeContainer(groups); + pool.storeContainer(artifactProperties); + pool.store(buildData.data()); +} + +QList<const ResolvedModule*> topSortModules(const QHash<const ResolvedModule*, QList<const ResolvedModule*> > &moduleChildren, + const QList<const ResolvedModule*> &modules, + QSet<QString> &seenModuleNames) +{ + QList<const ResolvedModule*> result; + foreach (const ResolvedModule *m, modules) { + if (m->name.isNull()) + continue; + result.append(topSortModules(moduleChildren, moduleChildren.value(m), seenModuleNames)); + if (!seenModuleNames.contains(m->name)) { + seenModuleNames.insert(m->name); + result.append(m); + } + } + return result; +} + +static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("getEnv expects 1 argument")); + QVariant v = engine->property("_qbs_procenv"); + QProcessEnvironment *procenv = reinterpret_cast<QProcessEnvironment*>(v.value<void*>()); + return engine->toScriptValue(procenv->value(context->argument(0).toString())); +} + +static QScriptValue js_putEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 2)) + return context->throwError(QScriptContext::SyntaxError, + QLatin1String("putEnv expects 2 arguments")); + QVariant v = engine->property("_qbs_procenv"); + QProcessEnvironment *procenv = reinterpret_cast<QProcessEnvironment*>(v.value<void*>()); + procenv->insert(context->argument(0).toString(), context->argument(1).toString()); + return engine->undefinedValue(); +} + +enum EnvType +{ + BuildEnv, RunEnv +}; + +static QProcessEnvironment getProcessEnvironment(ScriptEngine *engine, EnvType envType, + const QList<ResolvedModuleConstPtr> &modules, + const PropertyMapConstPtr &productConfiguration, + TopLevelProject *project, + const QProcessEnvironment &env) +{ + QProcessEnvironment procenv = env; + + // Copy the environment of the platform configuration to the process environment. + const QVariantMap &platformEnv = project->platformEnvironment; + for (QVariantMap::const_iterator it = platformEnv.constBegin(); it != platformEnv.constEnd(); ++it) + procenv.insert(it.key(), it.value().toString()); + + QMap<QString, const ResolvedModule *> moduleMap; + foreach (const ResolvedModuleConstPtr &module, modules) + moduleMap.insert(module->name, module.data()); + + QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleParents; + QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleChildren; + foreach (ResolvedModuleConstPtr module, modules) { + foreach (const QString &moduleName, module->moduleDependencies) { + const ResolvedModule * const depmod = moduleMap.value(moduleName); + QBS_ASSERT(depmod, return env); + moduleParents[depmod].append(module.data()); + moduleChildren[module.data()].append(depmod); + } + } + + QList<const ResolvedModule *> rootModules; + foreach (ResolvedModuleConstPtr module, modules) { + if (moduleParents.value(module.data()).isEmpty()) { + QBS_ASSERT(module, return env); + rootModules.append(module.data()); + } + } + + { + QVariant v; + v.setValue<void*>(&procenv); + engine->setProperty("_qbs_procenv", v); + } + + engine->clearImportsCache(); + QScriptValue scope = engine->newObject(); + + const QScriptValue getEnvValue = engine->newFunction(js_getEnv, 1); + const QScriptValue putEnvValue = engine->newFunction(js_putEnv, 1); + + // TODO: Remove in 1.3 + scope.setProperty("getenv", getEnvValue); + scope.setProperty("putenv", putEnvValue); + + scope.setProperty("getEnv", getEnvValue); + scope.setProperty("putEnv", putEnvValue); + + QSet<QString> seenModuleNames; + QList<const ResolvedModule *> topSortedModules = topSortModules(moduleChildren, rootModules, seenModuleNames); + foreach (const ResolvedModule *module, topSortedModules) { + if ((envType == BuildEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty()) || + (envType == RunEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty() + && module->setupRunEnvironmentScript->sourceCode.isEmpty())) + continue; + + ScriptFunctionConstPtr setupScript; + if (envType == BuildEnv) { + setupScript = module->setupBuildEnvironmentScript; + } else { + if (!module->setupRunEnvironmentScript) + setupScript = module->setupBuildEnvironmentScript; + else + setupScript = module->setupRunEnvironmentScript; + } + + // handle imports + engine->import(setupScript->fileContext->jsImports, scope, scope); + JsExtensions::setupExtensions(setupScript->fileContext->jsExtensions, scope); + + // expose properties of direct module dependencies + QScriptValue scriptValue; + QVariantMap productModules = productConfiguration->value().value("modules").toMap(); + foreach (const ResolvedModule * const depmod, moduleChildren.value(module)) { + scriptValue = engine->newObject(); + QVariantMap moduleCfg = productModules.value(depmod->name).toMap(); + for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) + scriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); + scope.setProperty(depmod->name, scriptValue); + } + + // expose the module's properties + QVariantMap moduleCfg = productModules.value(module->name).toMap(); + for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) + scope.setProperty(it.key(), engine->toScriptValue(it.value())); + + QScriptContext *ctx = engine->currentContext(); + ctx->pushScope(scope); + scriptValue = engine->evaluate(setupScript->sourceCode + QLatin1String("()")); + ctx->popScope(); + if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) { + QString envTypeStr = (envType == BuildEnv ? "build" : "run"); + throw ErrorInfo(QString("Error while setting up %1 environment: %2").arg(envTypeStr, scriptValue.toString())); + } + } + + engine->setProperty("_qbs_procenv", QVariant()); + return procenv; +} + +void ResolvedProduct::setupBuildEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const +{ + if (!buildEnvironment.isEmpty()) + return; + + buildEnvironment = getProcessEnvironment(engine, BuildEnv, modules, properties, + topLevelProject(), env); +} + +void ResolvedProduct::setupRunEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const +{ + if (!runEnvironment.isEmpty()) + return; + + runEnvironment = getProcessEnvironment(engine, RunEnv, modules, properties, + topLevelProject(), env); +} + +const QList<RuleConstPtr> &ResolvedProduct::topSortedRules() const +{ + QBS_CHECK(buildData); + if (buildData->topSortedRules.isEmpty()) { + FileTags productFileTags = fileTags; + productFileTags += additionalFileTags; + RuleGraph ruleGraph; + ruleGraph.build(rules, productFileTags); +// ruleGraph.dump(); + buildData->topSortedRules = ruleGraph.topSorted(); +// int i=0; +// foreach (RulePtr r, m_topSortedRules) +// qDebug() << ++i << r->toString() << (void*)r.data(); + } + return buildData->topSortedRules; +} + +TopLevelProject *ResolvedProduct::topLevelProject() const +{ + return project->topLevelProject(); +} + +static QStringList findGeneratedFiles(const Artifact *base, const FileTags &tags) +{ + QStringList result; + foreach (const Artifact *parent, base->parents) { + if (tags.isEmpty() || parent->fileTags.matches(tags)) + result << parent->filePath(); + } + + if (result.isEmpty() || tags.isEmpty()) + foreach (const Artifact *parent, base->parents) + result << findGeneratedFiles(parent, tags); + + return result; +} + +QStringList ResolvedProduct::generatedFiles(const QString &baseFile, const FileTags &tags) const +{ + ProductBuildData *data = buildData.data(); + if (!data) + return QStringList(); + + foreach (const Artifact *art, data->artifacts) { + if (art->filePath() == baseFile) + return findGeneratedFiles(art, tags); + } + return QStringList(); +} + +ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(0) +{ +} + +TopLevelProject *ResolvedProject::topLevelProject() +{ + if (m_topLevelProject) + return m_topLevelProject; + TopLevelProject *tlp = dynamic_cast<TopLevelProject *>(this); + if (tlp) { + m_topLevelProject = tlp; + return m_topLevelProject; + } + QBS_CHECK(!parentProject.isNull()); + m_topLevelProject = parentProject->topLevelProject(); + return m_topLevelProject; +} + +QList<ResolvedProjectPtr> ResolvedProject::allSubProjects() const +{ + QList<ResolvedProjectPtr> projectList = subProjects; + foreach (const ResolvedProjectConstPtr &subProject, subProjects) + projectList << subProject->allSubProjects(); + return projectList; +} + +QList<ResolvedProductPtr> ResolvedProject::allProducts() const +{ + QList<ResolvedProductPtr> productList = products; + foreach (const ResolvedProjectConstPtr &subProject, subProjects) + productList << subProject->allProducts(); + return productList; +} + +void ResolvedProject::load(PersistentPool &pool) +{ + name = pool.idLoadString(); + int count; + pool.stream() + >> location + >> enabled + >> count; + products.clear(); + products.reserve(count); + for (; --count >= 0;) { + ResolvedProductPtr rProduct = pool.idLoadS<ResolvedProduct>(); + if (rProduct->buildData) { + foreach (Artifact * const a, rProduct->buildData->artifacts) + a->product = rProduct; + } + products.append(rProduct); + } + + pool.stream() >> count; + subProjects.clear(); + subProjects.reserve(count); + for (; --count >= 0;) { + ResolvedProjectPtr p = pool.idLoadS<ResolvedProject>(); + subProjects.append(p); + } + + pool.stream() >> m_projectProperties; +} + +void ResolvedProject::store(PersistentPool &pool) const +{ + pool.storeString(name); + pool.stream() + << location + << enabled + << products.count(); + foreach (const ResolvedProductConstPtr &product, products) + pool.store(product); + pool.stream() << subProjects.count(); + foreach (const ResolvedProjectConstPtr &project, subProjects) + pool.store(project); + pool.stream() << m_projectProperties; +} + + +TopLevelProject::TopLevelProject() : locked(false) +{ +} + +TopLevelProject::~TopLevelProject() +{ +} + +QString TopLevelProject::deriveId(const QVariantMap &config) +{ + const QVariantMap qbsProperties = config.value(QLatin1String("qbs")).toMap(); + const QString buildVariant = qbsProperties.value(QLatin1String("buildVariant")).toString(); + const QString profile = qbsProperties.value(QLatin1String("profile")).toString(); + return profile + QLatin1Char('-') + buildVariant; +} + +QString TopLevelProject::deriveBuildDirectory(const QString &buildRoot, const QString &id) +{ + return buildRoot + QLatin1Char('/') + id; +} + +void TopLevelProject::setBuildConfiguration(const QVariantMap &config) +{ + m_buildConfiguration = config; + m_id = deriveId(config); +} + +QString TopLevelProject::buildGraphFilePath() const +{ + return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); +} + +void TopLevelProject::store(const Logger &logger) const +{ + // TODO: Use progress observer here. + + if (!buildData) + return; + if (!buildData->isDirty) { + logger.qbsDebug() << "[BG] build graph is unchanged in project " << id() << "."; + return; + } + const QString fileName = buildGraphFilePath(); + logger.qbsDebug() << "[BG] storing: " << fileName; + PersistentPool pool(logger); + PersistentPool::HeadData headData; + headData.projectConfig = buildConfiguration(); + pool.setHeadData(headData); + pool.setupWriteStream(fileName); + store(pool); + buildData->isDirty = false; +} + +void TopLevelProject::load(PersistentPool &pool) +{ + ResolvedProject::load(pool); + pool.stream() >> m_id; + pool.stream() >> platformEnvironment; + pool.stream() >> usedEnvironment; + pool.stream() >> fileExistsResults; + pool.stream() >> fileLastModifiedResults; + QHash<QString, QString> envHash; + pool.stream() >> envHash; + for (QHash<QString, QString>::const_iterator i = envHash.begin(); i != envHash.end(); ++i) + environment.insert(i.key(), i.value()); + pool.stream() >> buildSystemFiles; + buildData.reset(pool.idLoad<ProjectBuildData>()); + QBS_CHECK(buildData); + buildData->isDirty = false; +} + +void TopLevelProject::store(PersistentPool &pool) const +{ + ResolvedProject::store(pool); + pool.stream() << m_id; + pool.stream() << platformEnvironment << usedEnvironment << fileExistsResults + << fileLastModifiedResults; + QHash<QString, QString> envHash; + foreach (const QString &key, environment.keys()) + envHash.insert(key, environment.value(key)); + pool.stream() << envHash; + pool.stream() << buildSystemFiles; + pool.store(buildData.data()); +} + +/*! + * \class SourceWildCards + * \brief Objects of the \c SourceWildCards class result from giving wildcards in a + * \c ResolvedGroup's "files" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::prefix + * \brief Inherited from the \c ResolvedGroup + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::patterns + * \brief All elements of the \c ResolvedGroup's "files" binding that contain wildcards. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::excludePatterns + * \brief Corresponds to the \c ResolvedGroup's "excludeFiles" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::files + * \brief The \c SourceArtifacts resulting from the expanded list of matching files. + */ + +QSet<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QString &baseDir) const +{ + QSet<QString> files = expandPatterns(group, patterns, baseDir); + files -= expandPatterns(group, excludePatterns, baseDir); + return files; +} + +QSet<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QStringList &patterns, const QString &baseDir) const +{ + QSet<QString> files; + foreach (QString pattern, patterns) { + pattern.prepend(prefix); + pattern.replace('\\', '/'); + QStringList parts = pattern.split(QLatin1Char('/'), QString::SkipEmptyParts); + if (FileInfo::isAbsolute(pattern)) { + QString rootDir; + if (HostOsInfo::isWindowsHost()) { + rootDir = parts.takeFirst(); + if (!rootDir.endsWith(QLatin1Char('/'))) + rootDir.append(QLatin1Char('/')); + } else { + rootDir = QLatin1Char('/'); + } + expandPatterns(files, group, parts, rootDir); + } else { + expandPatterns(files, group, parts, baseDir); + } + } + + return files; +} + +void SourceWildCards::expandPatterns(QSet<QString> &result, const GroupConstPtr &group, + const QStringList &parts, + const QString &baseDir) const +{ + QStringList changed_parts = parts; + bool recursive = false; + QString part = changed_parts.takeFirst(); + + while (part == QLatin1String("**")) { + recursive = true; + + if (changed_parts.isEmpty()) { + part = QLatin1String("*"); + break; + } + + part = changed_parts.takeFirst(); + } + + const bool isDir = !changed_parts.isEmpty(); + + const QString &filePattern = part; + const QDirIterator::IteratorFlags itFlags = recursive + ? QDirIterator::Subdirectories + : QDirIterator::NoIteratorFlags; + QDir::Filters itFilters = isDir + ? QDir::Dirs + : QDir::Files; + + if (isDir && !FileInfo::isPattern(filePattern)) + itFilters |= QDir::Hidden; + if (filePattern != QLatin1String("..") && filePattern != QLatin1String(".")) + itFilters |= QDir::NoDotAndDotDot; + + QDirIterator it(baseDir, QStringList(filePattern), itFilters, itFlags); + while (it.hasNext()) { + const QString filePath = it.next(); + QBS_ASSERT(FileInfo(filePath).isDir() == isDir, break); + if (isDir) + expandPatterns(result, group, changed_parts, filePath); + else + result += QDir::cleanPath(filePath); + } +} + +void ResolvedTransformer::load(PersistentPool &pool) +{ + module = pool.idLoadS<ResolvedModule>(); + pool.stream() >> inputs; + pool.loadContainerS(outputs); + transform = pool.idLoadS<ScriptFunction>(); + pool.stream() >> explicitlyDependsOn; +} + +void ResolvedTransformer::store(PersistentPool &pool) const +{ + pool.store(module); + pool.stream() << inputs; + pool.storeContainer(outputs); + pool.store(transform); + pool.stream() << explicitlyDependsOn; +} + + +template<typename T> QMap<QString, T> listToMap(const QList<T> &list) +{ + QMap<QString, T> map; + foreach (const T &elem, list) + map.insert(keyFromElem(elem), elem); + return map; +} + +template<typename T> bool listsAreEqual(const QList<T> &l1, const QList<T> &l2) +{ + if (l1.count() != l2.count()) + return false; + const QMap<QString, T> map1 = listToMap(l1); + const QMap<QString, T> map2 = listToMap(l2); + foreach (const QString &key, map1.keys()) { + const T value2 = map2.value(key); + if (!value2) + return false; + if (*map1.value(key) != *value2) + return false; + } + return true; +} + +QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; } +QString keyFromElem(const ResolvedTransformerConstPtr &t) { return t->transform->sourceCode; } +QString keyFromElem(const RulePtr &r) { return r->toString(); } +QString keyFromElem(const ArtifactPropertiesPtr &ap) { + return ap->fileTagsFilter().toStringList().join(QLatin1String(",")); +} + +bool operator==(const SourceArtifact &sa1, const SourceArtifact &sa2) +{ + if (&sa1 == &sa2) + return true; + if (!!&sa1 != !!&sa2) + return false; + return sa1.absoluteFilePath == sa2.absoluteFilePath + && sa1.fileTags == sa2.fileTags + && sa1.overrideFileTags == sa2.overrideFileTags + && sa1.properties->value() == sa2.properties->value(); +} + +bool sourceArtifactListsAreEqual(const QList<SourceArtifactPtr> &l1, + const QList<SourceArtifactPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const ResolvedTransformer &t1, const ResolvedTransformer &t2) +{ + return modulesAreEqual(t1.module, t2.module) + && t1.inputs.toSet() == t2.inputs.toSet() + && sourceArtifactListsAreEqual(t1.outputs, t2.outputs) + && *t1.transform == *t2.transform + && t1.explicitlyDependsOn == t2.explicitlyDependsOn; +} + +bool transformerListsAreEqual(const QList<ResolvedTransformerConstPtr> &l1, + const QList<ResolvedTransformerConstPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const Rule &r1, const Rule &r2) +{ + if (&r1 == &r2) + return true; + if (!&r1 != !&r2) + return false; + if (r1.artifacts.count() != r2.artifacts.count()) + return false; + for (int i = 0; i < r1.artifacts.count(); ++i) { + if (*r1.artifacts.at(i) != *r2.artifacts.at(i)) + return false; + } + + return r1.module->name == r2.module->name + && r1.script->sourceCode == r2.script->sourceCode + && r1.inputs == r2.inputs + && r1.auxiliaryInputs == r2.auxiliaryInputs + && r1.usings == r2.usings + && r1.explicitlyDependsOn == r2.explicitlyDependsOn + && r1.multiplex == r2.multiplex; +} + +bool ruleListsAreEqual(const QList<RulePtr> &l1, const QList<RulePtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2) +{ + if (&a1 == &a2) + return true; + if (!&a1 != !&a2) + return false; + return a1.fileName == a2.fileName + && a1.fileTags == a2.fileTags + && a1.alwaysUpdated == a2.alwaysUpdated + && a1.bindings.toList().toSet() == a2.bindings.toList().toSet(); +} + +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) +{ + return b1.code == b2.code && b1.name == b2.name; +} + +uint qHash(const RuleArtifact::Binding &b) +{ + return qHash(qMakePair(b.code, b.name.join(QLatin1String(",")))); +} + +bool artifactPropertyListsAreEqual(const QList<ArtifactPropertiesPtr> &l1, + const QList<ArtifactPropertiesPtr> &l2) +{ + return listsAreEqual(l1, l2); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h new file mode 100644 index 000000000..d359686b1 --- /dev/null +++ b/src/lib/corelib/language/language.h @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_LANGUAGE_H +#define QBS_LANGUAGE_H + +#include "filetags.h" +#include "forward_decls.h" +#include "jsimports.h" +#include "propertymapinternal.h" +#include <buildgraph/forward_decls.h> +#include <tools/codelocation.h> +#include <tools/fileinfo.h> +#include <tools/persistentobject.h> +#include <tools/settings.h> +#include <tools/weakpointer.h> + +#include <QByteArray> +#include <QDataStream> +#include <QHash> +#include <QProcessEnvironment> +#include <QRegExp> +#include <QScriptProgram> +#include <QScriptValue> +#include <QScopedPointer> +#include <QSet> +#include <QString> +#include <QStringList> +#include <QVariant> + +QT_BEGIN_NAMESPACE +class QScriptEngine; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class BuildGraphLoader; + +class FileTagger : public PersistentObject +{ +public: + static FileTaggerPtr create() { return FileTaggerPtr(new FileTagger); } + static FileTaggerPtr create(const QStringList &patterns, const FileTags &fileTags) { + return FileTaggerPtr(new FileTagger(patterns, fileTags)); + } + + const QList<QRegExp> &patterns() const { return m_patterns; } + const FileTags &fileTags() const { return m_fileTags; } + +private: + FileTagger(const QStringList &patterns, const FileTags &fileTags); + FileTagger() {} + + void setPatterns(const QStringList &patterns); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + QList<QRegExp> m_patterns; + FileTags m_fileTags; +}; + +class RuleArtifact : public PersistentObject +{ +public: + static RuleArtifactPtr create() { return RuleArtifactPtr(new RuleArtifact); } + + QString fileName; + FileTags fileTags; + bool alwaysUpdated; + + class Binding + { + public: + QStringList name; + QString code; + CodeLocation location; + }; + + QVector<Binding> bindings; + +private: + RuleArtifact() + : alwaysUpdated(true) + {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +uint qHash(const RuleArtifact::Binding &b); +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2); +inline bool operator!=(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) { + return !(b1 == b2); +} +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2); +inline bool operator!=(const RuleArtifact &a1, const RuleArtifact &a2) { return !(a1 == a2); } + +class SourceArtifact : public PersistentObject +{ +public: + static SourceArtifactPtr create() { return SourceArtifactPtr(new SourceArtifact); } + + QString absoluteFilePath; + FileTags fileTags; + bool overrideFileTags; + PropertyMapPtr properties; + +private: + SourceArtifact() : overrideFileTags(true) {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const SourceArtifact &sa1, const SourceArtifact &sa2); +inline bool operator!=(const SourceArtifact &sa1, const SourceArtifact &sa2) { + return !(sa1 == sa2); +} + +bool sourceArtifactListsAreEqual(const QList<SourceArtifactPtr> &l1, + const QList<SourceArtifactPtr> &l2); + +class SourceWildCards : public PersistentObject +{ +public: + typedef QSharedPointer<SourceWildCards> Ptr; + typedef QSharedPointer<const SourceWildCards> ConstPtr; + + static Ptr create() { return Ptr(new SourceWildCards); } + + QSet<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir) const; + + // TODO: Use back pointer to Group instead? + QString prefix; + + QStringList patterns; + QStringList excludePatterns; + QList<SourceArtifactPtr> files; + +private: + SourceWildCards() {} + + QSet<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns, + const QString &baseDir) const; + void expandPatterns(QSet<QString> &result, const GroupConstPtr &group, + const QStringList &parts, const QString &baseDir) const; + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedGroup : public PersistentObject +{ +public: + static GroupPtr create() { return GroupPtr(new ResolvedGroup); } + + CodeLocation location; + + QString name; + bool enabled; + QString prefix; + QList<SourceArtifactPtr> files; + SourceWildCards::Ptr wildcards; + PropertyMapPtr properties; + FileTags fileTags; + bool overrideTags; + + QList<SourceArtifactPtr> allFiles() const; + +private: + ResolvedGroup() + : enabled(true) + {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedFileContext : public PersistentObject +{ +public: + static ResolvedFileContextPtr create() + { + return ResolvedFileContextPtr(new ResolvedFileContext); + } + + QString filePath; + QStringList jsExtensions; + JsImports jsImports; + +private: + ResolvedFileContext() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b); +inline bool operator!=(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ return !(a == b); } + +class ScriptFunction : public PersistentObject +{ +public: + static ScriptFunctionPtr create() { return ScriptFunctionPtr(new ScriptFunction); } + + QString sourceCode; + QStringList argumentNames; + CodeLocation location; + ResolvedFileContextConstPtr fileContext; + mutable QScriptValue scriptFunction; // cache + +private: + ScriptFunction() {} + + void load(PersistentPool &); + void store(PersistentPool &) const; +}; + +bool operator==(const ScriptFunction &a, const ScriptFunction &b); +inline bool operator!=(const ScriptFunction &a, const ScriptFunction &b) { return !(a == b); } + +class ResolvedModule : public PersistentObject +{ +public: + static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); } + + QString name; + QStringList moduleDependencies; + ScriptFunctionPtr setupBuildEnvironmentScript; + ScriptFunctionPtr setupRunEnvironmentScript; + +private: + ResolvedModule() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2); +inline bool operator!=(const ResolvedModule &m1, const ResolvedModule &m2) { return !(m1 == m2); } + +/** + * Per default each rule is a "non-multiplex rule". + * + * A "multiplex rule" creates one transformer that takes all + * input artifacts with the matching input file tag and creates + * one or more artifacts. (e.g. linker rule) + * + * A "non-multiplex rule" creates one transformer per matching input file. + */ +class Rule : public PersistentObject +{ +public: + static RulePtr create() { return RulePtr(new Rule); } + + ResolvedModuleConstPtr module; + ScriptFunctionPtr script; + FileTags inputs; + FileTags auxiliaryInputs; + FileTags usings; + FileTags explicitlyDependsOn; + bool multiplex; + QList<RuleArtifactPtr> artifacts; + + // members that we don't need to save + int ruleGraphId; + + QString toString() const; + FileTags outputFileTags() const; + +private: + Rule() : multiplex(false), ruleGraphId(-1) {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; +bool operator==(const Rule &r1, const Rule &r2); +inline bool operator!=(const Rule &r1, const Rule &r2) { return !(r1 == r2); } +bool ruleListsAreEqual(const QList<RulePtr> &l1, const QList<RulePtr> &l2); + +class ResolvedTransformer : public PersistentObject +{ +public: + static ResolvedTransformerPtr create() + { + return ResolvedTransformerPtr(new ResolvedTransformer); + } + + ResolvedModuleConstPtr module; + QStringList inputs; + QList<SourceArtifactPtr> outputs; + ScriptFunctionPtr transform; + FileTags explicitlyDependsOn; + +private: + ResolvedTransformer() {} + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +bool operator==(const ResolvedTransformer &t1, const ResolvedTransformer &t2); +inline bool operator!=(const ResolvedTransformer &t1, const ResolvedTransformer &t2) { + return !(t1 == t2); +} +bool transformerListsAreEqual(const QList<ResolvedTransformerConstPtr> &l1, + const QList<ResolvedTransformerConstPtr> &l2); + +class TopLevelProject; +class ScriptEngine; + +class ResolvedProduct : public PersistentObject +{ +public: + static ResolvedProductPtr create() { return ResolvedProductPtr(new ResolvedProduct); } + + ~ResolvedProduct(); + + bool enabled; + FileTags fileTags; + FileTags additionalFileTags; + QString name; + QString targetName; + QString sourceDirectory; + QString destinationDirectory; + CodeLocation location; + WeakPointer<ResolvedProject> project; + PropertyMapPtr properties; + QSet<RulePtr> rules; + QSet<ResolvedProductPtr> dependencies; + QList<FileTaggerConstPtr> fileTaggers; + QList<ResolvedModuleConstPtr> modules; + QList<ResolvedTransformerConstPtr> transformers; + QList<GroupPtr> groups; + QList<ArtifactPropertiesPtr> artifactProperties; + QScopedPointer<ProductBuildData> buildData; + + mutable QProcessEnvironment buildEnvironment; // must not be saved + mutable QProcessEnvironment runEnvironment; // must not be saved + QHash<QString, QString> executablePathCache; + + QList<SourceArtifactPtr> allFiles() const; + QList<SourceArtifactPtr> allEnabledFiles() const; + FileTags fileTagsForFileName(const QString &fileName) const; + void setupBuildEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; + void setupRunEnvironment(ScriptEngine *scriptEngine, const QProcessEnvironment &env) const; + + const QList<RuleConstPtr> &topSortedRules() const; + TopLevelProject *topLevelProject() const; + + QStringList generatedFiles(const QString &baseFile, const FileTags &tags) const; + +private: + ResolvedProduct(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +}; + +class ResolvedProject : public PersistentObject +{ +public: + static ResolvedProjectPtr create() { return ResolvedProjectPtr(new ResolvedProject); } + + QString name; + CodeLocation location; + bool enabled; + QList<ResolvedProductPtr> products; + QList<ResolvedProjectPtr> subProjects; + WeakPointer<ResolvedProject> parentProject; + + void setProjectProperties(const QVariantMap &config) { m_projectProperties = config; } + const QVariantMap &projectProperties() const { return m_projectProperties; } + + TopLevelProject *topLevelProject(); + QList<ResolvedProjectPtr> allSubProjects() const; + QList<ResolvedProductPtr> allProducts() const; + +protected: + ResolvedProject(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +private: + QVariantMap m_projectProperties; + TopLevelProject *m_topLevelProject; +}; + +class TopLevelProject : public ResolvedProject +{ + friend class BuildGraphLoader; +public: + ~TopLevelProject(); + + static TopLevelProjectPtr create() { return TopLevelProjectPtr(new TopLevelProject); } + + static QString deriveId(const QVariantMap &config); + static QString deriveBuildDirectory(const QString &buildRoot, const QString &id); + + QString buildDirectory; // Not saved + QProcessEnvironment environment; + QVariantMap platformEnvironment; + QHash<QString, QString> usedEnvironment; // Environment variables requested by the project while resolving. + QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()". + QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()". + QScopedPointer<ProjectBuildData> buildData; + bool locked; + + QSet<QString> buildSystemFiles; + + void setBuildConfiguration(const QVariantMap &config); + const QVariantMap &buildConfiguration() const { return m_buildConfiguration; } + QString id() const { return m_id; } + + QString buildGraphFilePath() const; + void store(const Logger &logger) const; + +private: + TopLevelProject(); + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + + QString m_id; + QVariantMap m_buildConfiguration; +}; + +bool artifactPropertyListsAreEqual(const QList<ArtifactPropertiesPtr> &l1, + const QList<ArtifactPropertiesPtr> &l2); + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::JsImport, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::RuleArtifact::Binding, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_LANGUAGE_H diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri new file mode 100644 index 000000000..09d7e9a1b --- /dev/null +++ b/src/lib/corelib/language/language.pri @@ -0,0 +1,70 @@ +HEADERS += \ + $$PWD/artifactproperties.h \ + $$PWD/asttools.h \ + $$PWD/builtindeclarations.h \ + $$PWD/builtinvalue.h \ + $$PWD/evaluationdata.h \ + $$PWD/evaluator.h \ + $$PWD/evaluatorscriptclass.h \ + $$PWD/filecontext.h \ + $$PWD/filetags.h \ + $$PWD/forward_decls.h \ + $$PWD/functiondeclaration.h \ + $$PWD/identifiersearch.h \ + $$PWD/importversion.h \ + $$PWD/item.h \ + $$PWD/itemdeclaration.h \ + $$PWD/itemobserver.h \ + $$PWD/itempool.h \ + $$PWD/itemreader.h \ + $$PWD/itemreaderastvisitor.h \ + $$PWD/jsimports.h \ + $$PWD/language.h \ + $$PWD/loader.h \ + $$PWD/moduleloader.h \ + $$PWD/preparescriptobserver.h \ + $$PWD/projectresolver.h \ + $$PWD/property.h \ + $$PWD/propertydeclaration.h \ + $$PWD/propertymapinternal.h \ + $$PWD/scriptengine.h \ + $$PWD/scriptpropertyobserver.h \ + $$PWD/value.h + +SOURCES += \ + $$PWD/artifactproperties.cpp \ + $$PWD/asttools.cpp \ + $$PWD/builtindeclarations.cpp \ + $$PWD/builtinvalue.cpp \ + $$PWD/evaluator.cpp \ + $$PWD/evaluatorscriptclass.cpp \ + $$PWD/filecontext.cpp \ + $$PWD/filetags.cpp \ + $$PWD/identifiersearch.cpp \ + $$PWD/importversion.cpp \ + $$PWD/item.cpp \ + $$PWD/itemdeclaration.cpp \ + $$PWD/itempool.cpp \ + $$PWD/itemreader.cpp \ + $$PWD/itemreaderastvisitor.cpp \ + $$PWD/language.cpp \ + $$PWD/loader.cpp \ + $$PWD/moduleloader.cpp \ + $$PWD/preparescriptobserver.cpp \ + $$PWD/projectresolver.cpp \ + $$PWD/propertydeclaration.cpp \ + $$PWD/propertymapinternal.cpp \ + $$PWD/scriptengine.cpp \ + $$PWD/value.cpp + +all_tests { + HEADERS += $$PWD/tst_language.h + SOURCES += $$PWD/tst_language.cpp + OTHER_FILES += $$PWD/testdata/* +} + +!qbs_no_dev_install { + language_headers.files = $$PWD/forward_decls.h + language_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/language + INSTALLS += language_headers +} diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp new file mode 100644 index 000000000..8a3f3c98f --- /dev/null +++ b/src/lib/corelib/language/loader.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "loader.h" + +#include "builtindeclarations.h" +#include "item.h" +#include "moduleloader.h" +#include "projectresolver.h" +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> +#include <tools/setupprojectparameters.h> + +#include <QDir> + +namespace qbs { +namespace Internal { + +Loader::Loader(ScriptEngine *engine, const Logger &logger) + : m_logger(logger) + , m_progressObserver(0) + , m_builtins(new BuiltinDeclarations) + , m_moduleLoader(new ModuleLoader(engine, m_builtins, logger)) + , m_projectResolver(new ProjectResolver(m_moduleLoader, m_builtins, logger)) + , m_engine(engine) +{ +} + +Loader::~Loader() +{ + delete m_projectResolver; + delete m_moduleLoader; + delete m_builtins; +} + +void Loader::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; + m_moduleLoader->setProgressObserver(observer); + m_projectResolver->setProgressObserver(observer); +} + +void Loader::setSearchPaths(const QStringList &_searchPaths) +{ + QStringList searchPaths; + foreach (const QString &searchPath, _searchPaths) { + if (!FileInfo::exists(searchPath)) { + m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.") + .arg(QDir::toNativeSeparators(searchPath)); + } else { + searchPaths += searchPath; + } + } + + m_moduleLoader->setSearchPaths(searchPaths); +} + +TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters ¶meters) +{ + QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); + + m_engine->setEnvironment(parameters.environment()); + m_engine->clearExceptions(); + + // At this point, we cannot set a sensible total effort, because we know nothing about + // the project yet. That's why we use a placeholder here, so the user at least + // sees that an operation is starting. The real total effort will be set later when + // we have enough information. + if (m_progressObserver) { + m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1") + .arg(TopLevelProject::deriveId(parameters.buildConfigurationTree())), 1); + } + + ModuleLoaderResult loadResult + = m_moduleLoader->load(parameters.projectFilePath(), + parameters.overriddenValuesTree(), + parameters.buildConfigurationTree(), + true); + const TopLevelProjectPtr project = m_projectResolver->resolve(loadResult, parameters); + + // E.g. if the top-level project is disabled. + if (m_progressObserver) + m_progressObserver->setFinished(); + + return project; +} + +QByteArray Loader::qmlTypeInfo() +{ + return m_builtins->qmlTypeInfo(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/language/loader.h new file mode 100644 index 000000000..64e3a2ad6 --- /dev/null +++ b/src/lib/corelib/language/loader.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#ifndef QBS_LOADER_H +#define QBS_LOADER_H + +#include "forward_decls.h" +#include <tools/qbs_export.h> +#include <logging/logger.h> + +#include <QStringList> + +namespace qbs { +class Settings; +class SetupProjectParameters; +namespace Internal { +class BuiltinDeclarations; +class Logger; +class ModuleLoader; +class ProgressObserver; +class ScriptEngine; +class ProjectResolver; + +class QBS_EXPORT Loader // FIXME: Exported for qbs-qmltypes +{ +public: + Loader(ScriptEngine *engine, const Logger &logger); + ~Loader(); + + void setProgressObserver(ProgressObserver *observer); + void setSearchPaths(const QStringList &searchPaths); + TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); + QByteArray qmlTypeInfo(); + +private: + Logger m_logger; + ProgressObserver *m_progressObserver; + BuiltinDeclarations *m_builtins; + ModuleLoader *m_moduleLoader; + ProjectResolver *m_projectResolver; + ScriptEngine * const m_engine; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOADER_H diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp new file mode 100644 index 000000000..a0c06bb61 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.cpp @@ -0,0 +1,1143 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "moduleloader.h" + +#include "builtindeclarations.h" +#include "builtinvalue.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "itemreader.h" +#include "scriptengine.h" +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/hostosinfo.h> +#include <tools/progressobserver.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> + +#include <QDebug> +#include <QDir> +#include <QDirIterator> + +namespace qbs { +namespace Internal { + +class ModuleLoader::ItemModuleList : public QList<Item::Module> {}; + +const QString moduleSearchSubDir = QLatin1String("modules"); + +ModuleLoader::ModuleLoader(ScriptEngine *engine, BuiltinDeclarations *builtins, + const Logger &logger) + : m_engine(engine) + , m_pool(0) + , m_logger(logger) + , m_progressObserver(0) + , m_reader(new ItemReader(builtins, logger)) + , m_evaluator(new Evaluator(engine, logger)) +{ +} + +ModuleLoader::~ModuleLoader() +{ + delete m_evaluator; + delete m_reader; +} + +void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) +{ + m_progressObserver = progressObserver; +} + +static void addExtraModuleSearchPath(QStringList &list, const QString &searchPath) +{ + list += FileInfo::resolvePath(searchPath, moduleSearchSubDir); +} + +void ModuleLoader::setSearchPaths(const QStringList &searchPaths) +{ + m_reader->setSearchPaths(searchPaths); + + m_moduleDirListCache.clear(); + m_moduleSearchPaths.clear(); + foreach (const QString &path, searchPaths) + addExtraModuleSearchPath(m_moduleSearchPaths, path); +} + +ModuleLoaderResult ModuleLoader::load(const QString &filePath, + const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, + bool wrapWithProjectItem) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] load" << filePath; + m_overriddenProperties = overriddenProperties; + m_buildConfigProperties = buildConfigProperties; + m_validItemPropertyNamesPerItem.clear(); + m_disabledItems.clear(); + + ModuleLoaderResult result; + m_pool = result.itemPool.data(); + m_reader->setPool(m_pool); + + Item *root = m_reader->readFile(filePath); + if (!root) + return ModuleLoaderResult(); + + if (wrapWithProjectItem && root->typeName() != QLatin1String("Project")) + root = wrapWithProject(root); + + handleProject(&result, root, QSet<QString>() << QDir::cleanPath(filePath)); + result.root = root; + result.qbsFiles = m_reader->filesRead(); + return result; +} + +class PropertyDeclarationCheck : public ValueHandler +{ + const QHash<Item *, QSet<QString> > &m_validItemPropertyNamesPerItem; + const QSet<Item *> &m_disabledItems; + Item *m_parentItem; + QString m_currentName; +public: + PropertyDeclarationCheck(const QHash<Item *, QSet<QString> > &validItemPropertyNamesPerItem, + const QSet<Item *> &disabledItems) + : m_validItemPropertyNamesPerItem(validItemPropertyNamesPerItem) + , m_disabledItems(disabledItems) + , m_parentItem(0) + { + } + + void operator()(Item *item) + { + handleItem(item); + } + +private: + void handle(JSSourceValue *value) + { + if (!m_parentItem->propertyDeclaration(m_currentName).isValid()) { + throw ErrorInfo(Tr::tr("Property '%1' is not declared.").arg(m_currentName), + value->location()); + } + } + + void handle(ItemValue *value) + { + if (!value->item()->isModuleInstance() + && !m_validItemPropertyNamesPerItem.value(m_parentItem).contains(m_currentName) + && m_parentItem->file() + && !m_parentItem->file()->idScope()->hasProperty(m_currentName)) { + throw ErrorInfo(Tr::tr("Item '%1' is not declared. " + "Did you forget to add a Depends item?").arg(m_currentName), + value->location().isValid() ? value->location() + : m_parentItem->location()); + } + + handleItem(value->item()); + } + + void handleItem(Item *item) + { + if (m_disabledItems.contains(item) || item->typeName() == QLatin1String("SubProject")) + return; + + Item *oldParentItem = m_parentItem; + for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) { + if (item->propertyDeclaration(it.key()).isValid()) + continue; + m_currentName = it.key(); + m_parentItem = item; + it.value()->apply(this); + } + m_parentItem = oldParentItem; + foreach (Item *child, item->children()) + handleItem(child); + } + + void handle(VariantValue *) { /* only created internally - no need to check */ } + void handle(BuiltinValue *) { /* only created internally - no need to check */ } +}; + +void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, + const QSet<QString> &referencedFilePaths) +{ + if (!checkItemCondition(item)) + return; + ProjectContext projectContext; + projectContext.result = loadResult; + projectContext.localModuleSearchPath = FileInfo::resolvePath(item->file()->dirPath(), + moduleSearchSubDir); + + ProductContext dummyProductContext; + dummyProductContext.project = &projectContext; + loadBaseModule(&dummyProductContext, item); + overrideItemProperties(item, QLatin1String("project"), m_overriddenProperties); + + projectContext.extraSearchPaths = readExtraSearchPaths(item); + m_reader->pushExtraSearchPaths(projectContext.extraSearchPaths); + projectContext.item = item; + ItemValuePtr itemValue = ItemValue::create(item); + projectContext.scope = Item::create(m_pool); + projectContext.scope->setProperty(QLatin1String("project"), itemValue); + + foreach (Item *child, item->children()) { + child->setScope(projectContext.scope); + if (child->typeName() == QLatin1String("Product")) { + handleProduct(&projectContext, child); + } else if (child->typeName() == QLatin1String("SubProject")) { + handleSubProject(&projectContext, child, referencedFilePaths); + } else if (child->typeName() == QLatin1String("Project")) { + copyProperties(item, child); + handleProject(loadResult, child, referencedFilePaths); + } + } + + const QString projectFileDirPath = FileInfo::path(item->file()->filePath()); + const QStringList refs = m_evaluator->stringListValue(item, QLatin1String("references")); + foreach (const QString &filePath, refs) { + QString absReferencePath = FileInfo::resolvePath(projectFileDirPath, filePath); + if (FileInfo(absReferencePath).isDir()) { + QString qbsFilePath; + QDirIterator dit(absReferencePath, QStringList(QLatin1String("*.qbs"))); + while (dit.hasNext()) { + if (!qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " + "qbs file.").arg(absReferencePath), + item->property(QLatin1String("references"))->location()); + } + qbsFilePath = dit.next(); + } + if (qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") + .arg(absReferencePath), + item->property(QLatin1String("references"))->location()); + } + absReferencePath = qbsFilePath; + } + if (referencedFilePaths.contains(absReferencePath)) + throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(filePath), + item->property(QLatin1String("references"))->location()); + Item *subItem = m_reader->readFile(absReferencePath); + subItem->setScope(projectContext.scope); + subItem->setParent(projectContext.item); + QList<Item *> projectChildren = projectContext.item->children(); + projectChildren += subItem; + projectContext.item->setChildren(projectChildren); + if (subItem->typeName() == "Product") { + handleProduct(&projectContext, subItem); + } else if (subItem->typeName() == "Project") { + copyProperties(item, subItem); + handleProject(loadResult, subItem, + QSet<QString>(referencedFilePaths) << absReferencePath); + } else { + throw ErrorInfo(Tr::tr("The top-level item of a file in a \"references\" list must be " + "a Product or a Project, but it is \"%1\".").arg(subItem->typeName()), + subItem->location()); + } + } + + checkItemTypes(item); + + PropertyDeclarationCheck check(m_validItemPropertyNamesPerItem, m_disabledItems); + check(item); + + m_reader->popExtraSearchPaths(); +} + +void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) +{ + checkCancelation(); + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] handleProduct " << item->file()->filePath(); + + ProductContext productContext; + productContext.project = projectContext; + bool extraSearchPathsSet = false; + const QStringList extraSearchPaths = readExtraSearchPaths(item, &extraSearchPathsSet); + if (extraSearchPathsSet) { // Inherit from project if not set in product itself. + productContext.extraSearchPaths = extraSearchPaths; + m_reader->pushExtraSearchPaths(extraSearchPaths); + } else { + productContext.extraSearchPaths = projectContext->extraSearchPaths; + } + productContext.item = item; + ItemValuePtr itemValue = ItemValue::create(item); + productContext.scope = Item::create(m_pool); + productContext.scope->setProperty(QLatin1String("product"), itemValue); + productContext.scope->setScope(projectContext->scope); + DependsContext dependsContext; + dependsContext.product = &productContext; + dependsContext.productDependencies = &productContext.info.usedProducts; + setScopeForDescendants(item, productContext.scope); + resolveDependencies(&dependsContext, item); + if (!checkItemCondition(item)) + return; + createAdditionalModuleInstancesInProduct(&productContext); + + foreach (Item *child, item->children()) { + if (child->typeName() == QLatin1String("Group")) + handleGroup(&productContext, child); + else if (child->typeName() == QLatin1String("Artifact")) + handleArtifact(&productContext, child); + else if (child->typeName() == QLatin1String("Export")) + deferExportItem(&productContext, child); + else if (child->typeName() == QLatin1String("Probe")) + resolveProbe(item, child); + } + + mergeExportItems(&productContext); + projectContext->result->productInfos.insert(item, productContext.info); + if (extraSearchPathsSet) + m_reader->popExtraSearchPaths(); +} + +void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *item, + const QSet<QString> &referencedFilePaths) +{ + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[MODLDR] handleSubProject " << item->file()->filePath(); + + Item * const propertiesItem = item->child(QLatin1String("Properties")); + bool subProjectEnabled = true; + if (propertiesItem) + subProjectEnabled = checkItemCondition(propertiesItem); + if (!subProjectEnabled) + return; + + const QString projectFileDirPath = FileInfo::path(item->file()->filePath()); + const QString relativeFilePath = m_evaluator->property(item, + QLatin1String("filePath")).toString(); + QString subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), item->location()); + Item *loadedItem = m_reader->readFile(subProjectFilePath); + if (loadedItem->typeName() == QLatin1String("Product")) + loadedItem = wrapWithProject(loadedItem); + const bool inheritProperties + = m_evaluator->boolValue(item, QLatin1String("inheritProperties"), true); + + if (inheritProperties) + copyProperties(item->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin(); + it != overriddenProperties.constEnd(); ++it) { + loadedItem->setProperty(it.key(), overriddenProperties.value(it.key())); + } + } + + if (loadedItem->typeName() != QLatin1String("Project")) { + ErrorInfo error; + error.append(Tr::tr("Expected Project item, but encountered '%1'.") + .arg(loadedItem->typeName()), loadedItem->location()); + const ValuePtr &filePathProperty = item->properties().value(QLatin1String("filePath")); + error.append(Tr::tr("The problematic file was referenced from here."), + filePathProperty->location()); + throw error; + } + + Item::addChild(item, loadedItem); + item->setScope(projectContext->scope); + handleProject(projectContext->result, loadedItem, + QSet<QString>(referencedFilePaths) << subProjectFilePath); +} + +void ModuleLoader::createAdditionalModuleInstancesInProduct(ProductContext *productContext) +{ + Item::Modules modulesToCheck; + QSet<QStringList> modulesInProduct; + foreach (const Item::Module &module, productContext->item->modules()) { + modulesInProduct += module.name; + modulesToCheck += module.item->prototype()->modules(); + } + while (!modulesToCheck.isEmpty()) { + Item::Module module = modulesToCheck.takeFirst(); + if (modulesInProduct.contains(module.name)) + continue; + modulesInProduct += module.name; + modulesToCheck += module.item->prototype()->modules(); + Item *instance = Item::create(m_pool); + instantiateModule(productContext, productContext->item, instance, module.item->prototype(), + module.name); + module.item = instance; + productContext->item->modules().append(module); + } +} + +void ModuleLoader::handleGroup(ProductContext *productContext, Item *item) +{ + checkCancelation(); + propagateModulesFromProduct(productContext, item); + checkItemCondition(item); +} + +void ModuleLoader::handleArtifact(ProductContext *productContext, Item *item) +{ + checkCancelation(); + propagateModulesFromProduct(productContext, item); +} + +void ModuleLoader::deferExportItem(ModuleLoader::ProductContext *productContext, Item *item) +{ + productContext->exportItems.append(item); +} + +static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) +{ + if (value->type() == Value::ItemValueType) { + Item *valueItem = value.staticCast<ItemValue>()->item(); + if (!valueItem) + return; + Item *subItem = dst->itemProperty(name, true)->item(); + for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin(); + it != valueItem->properties().constEnd(); ++it) + mergeProperty(subItem, it.key(), it.value()); + } else { + dst->setProperty(name, value); + } +} + +void ModuleLoader::mergeExportItems(ModuleLoader::ProductContext *productContext) +{ + Item *merged = Item::create(productContext->item->pool()); + merged->setTypeName(QLatin1String("Export")); + QSet<Item *> exportItems; + foreach (Item *exportItem, productContext->exportItems) { + checkCancelation(); + if (Q_UNLIKELY(productContext->filesWithExportItem.contains(exportItem->file()))) + throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), + exportItem->location()); + merged->setLocation(exportItem->location()); + productContext->filesWithExportItem += exportItem->file(); + exportItems.insert(exportItem); + foreach (Item *child, exportItem->children()) + Item::addChild(merged, child); + for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); + it != exportItem->properties().constEnd(); ++it) { + mergeProperty(merged, it.key(), it.value()); + } + } + + QList<Item *> children = productContext->item->children(); + for (int i = 0; i < children.count();) { + if (exportItems.contains(children.at(i))) + children.removeAt(i); + else + ++i; + } + productContext->item->setChildren(children); + Item::addChild(productContext->item, merged); + + DependsContext dependsContext; + dependsContext.product = productContext; + dependsContext.productDependencies = &productContext->info.usedProductsFromExportItem; + resolveDependencies(&dependsContext, merged); +} + +void ModuleLoader::propagateModulesFromProduct(ProductContext *productContext, Item *item) +{ + for (Item::Modules::const_iterator it = productContext->item->modules().constBegin(); + it != productContext->item->modules().constEnd(); ++it) + { + Item::Module m = *it; + Item *targetItem = moduleInstanceItem(item, m.name); + targetItem->setPrototype(m.item); + targetItem->setModuleInstanceFlag(true); + targetItem->setScope(m.item->scope()); + targetItem->modules() = m.item->modules(); + + // "parent" should point to the group/artifact parent + targetItem->setParent(item->parent()); + + // the outer item of a module is the product's instance of it + targetItem->setOuterItem(m.item); // ### Is this always the same as the scope item? + + m.item = targetItem; + item->modules() += m; + } +} + +void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item) +{ + loadBaseModule(dependsContext->product, item); + + // Resolve all Depends items. + typedef QHash<Item *, ItemModuleList> ModuleHash; + ModuleHash loadedModules; + ProductDependencyResults productDependencies; + foreach (Item *child, item->children()) + if (child->typeName() == QLatin1String("Depends")) + resolveDependsItem(dependsContext, item, child, &loadedModules[child], + &productDependencies); + + QSet<QString> loadedModuleNames; + foreach (const ItemModuleList &moduleList, loadedModules) { + foreach (const Item::Module &module, moduleList) { + const QString fullName = fullModuleName(module.name); + if (loadedModuleNames.contains(fullName)) { + m_logger.printWarning(ErrorInfo(Tr::tr("Duplicate dependency '%1'.").arg(fullName), + item->location())); + continue; + } + loadedModuleNames.insert(fullName); + item->modules() += module; + resolveProbes(module.item); + } + } + + foreach (const ProductDependencyResult &pd, productDependencies) + dependsContext->productDependencies->append(pd.second); +} + +void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *item, + Item *dependsItem, ItemModuleList *moduleResults, + ProductDependencyResults *productResults) +{ + checkCancelation(); + if (!checkItemCondition(dependsItem)) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "Depends item disabled, ignoring."; + return; + } + const QString name = m_evaluator->property(dependsItem, "name").toString(); + const QStringList nameParts = name.split('.'); + if (Q_UNLIKELY(nameParts.count() > 2)) { + QString msg = Tr::tr("There cannot be more than one dot in a module name."); + throw ErrorInfo(msg, dependsItem->location()); + } + + QString superModuleName; + QStringList submodules = m_evaluator->stringListValue(dependsItem, QLatin1String("submodules")); + if (nameParts.count() == 2) { + if (Q_UNLIKELY(!submodules.isEmpty())) + throw ErrorInfo(Tr::tr("Depends.submodules cannot be used if name contains a dot."), + dependsItem->location()); + superModuleName = nameParts.first(); + submodules += nameParts.last(); + } + if (Q_UNLIKELY(submodules.count() > 1 && !dependsItem->id().isEmpty())) { + QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); + throw ErrorInfo(msg, dependsItem->location()); + } + if (superModuleName.isEmpty()) { + if (submodules.isEmpty()) + submodules += name; + else + superModuleName = name; + } + + QStringList moduleNames; + foreach (const QString &submoduleName, submodules) + moduleNames += submoduleName; + + Item::Module result; + foreach (const QString &moduleName, moduleNames) { + QStringList qualifiedModuleName(moduleName); + if (!superModuleName.isEmpty()) + qualifiedModuleName.prepend(superModuleName); + const bool isRequired + = m_evaluator->boolValue(dependsItem, QLatin1String("required")); + Item *moduleItem = loadModule(dependsContext->product, item, dependsItem->location(), + dependsItem->id(), qualifiedModuleName, false, isRequired); + if (moduleItem) { + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "module loaded: " << fullModuleName(qualifiedModuleName); + result.name = qualifiedModuleName; + result.item = moduleItem; + moduleResults->append(result); + } else { + ModuleLoaderResult::ProductInfo::Dependency dependency; + dependency.name = moduleName; + dependency.required = m_evaluator->property(item, QLatin1String("required")).toBool(); + dependency.failureMessage + = m_evaluator->property(item, QLatin1String("failureMessage")).toString(); + productResults->append(ProductDependencyResult(dependsItem, dependency)); + } + } +} + +Item *ModuleLoader::moduleInstanceItem(Item *item, const QStringList &moduleName) +{ + Item *instance = item; + for (int i = 0; i < moduleName.count(); ++i) { + const QString &moduleNameSegment = moduleName.at(i); + m_validItemPropertyNamesPerItem[instance].insert(moduleNameSegment); + bool createNewItem = true; + const ValuePtr v = instance->properties().value(moduleName.at(i)); + if (v && v->type() == Value::ItemValueType) { + const ItemValuePtr iv = v.staticCast<ItemValue>(); + if (iv->item()) { + createNewItem = false; + instance = iv->item(); + } + } + if (createNewItem) { + Item *newItem = Item::create(m_pool); + instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); + instance = newItem; + } + } + QBS_ASSERT(moduleName.isEmpty() || instance != item, return 0); + return instance; +} + +Item *ModuleLoader::loadModule(ProductContext *productContext, Item *item, + const CodeLocation &dependsItemLocation, + const QString &moduleId, const QStringList &moduleName, bool isBaseModule, bool isRequired) +{ + Item *moduleInstance = moduleId.isEmpty() + ? moduleInstanceItem(item, moduleName) + : moduleInstanceItem(item, QStringList(moduleId)); + if (!moduleInstance->typeName().isNull()) { + // already handled + return moduleInstance; + } + + QStringList moduleSearchPaths(productContext->project->localModuleSearchPath); + foreach (const QString &searchPath, productContext->extraSearchPaths) + addExtraModuleSearchPath(moduleSearchPaths, searchPath); + bool cacheHit; + Item *modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, + moduleName, moduleSearchPaths, isRequired, &cacheHit); + if (!modulePrototype) + return 0; + if (!cacheHit && isBaseModule) + setupBaseModulePrototype(modulePrototype); + instantiateModule(productContext, item, moduleInstance, modulePrototype, moduleName); + callValidateScript(moduleInstance); + return moduleInstance; +} + +// It's not necessarily an error if we don't find a required module with the given name, +// because the dependency could refer to a product instead. +Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QStringList &moduleName, + const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit) +{ + QStringList searchPaths = extraSearchPaths; + searchPaths.append(m_moduleSearchPaths); + + bool triedToLoadModule = moduleName.count() > 1; + const QString fullName = fullModuleName(moduleName); + foreach (const QString &path, searchPaths) { + const QString dirPath = findExistingModulePath(path, moduleName); + if (dirPath.isEmpty()) + continue; + QStringList moduleFileNames = m_moduleDirListCache.value(dirPath); + if (moduleFileNames.isEmpty()) { + QDirIterator dirIter(dirPath, QStringList(QLatin1String("*.qbs"))); + while (dirIter.hasNext()) + moduleFileNames += dirIter.next(); + + m_moduleDirListCache.insert(dirPath, moduleFileNames); + } + foreach (const QString &filePath, moduleFileNames) { + triedToLoadModule = true; + Item *module = loadModuleFile(productContext, fullName, + moduleName.count() == 1 + && moduleName.first() == QLatin1String("qbs"), + filePath, cacheHit); + if (module) + return module; + } + } + + if (!isRequired) { + if (m_logger.traceEnabled()) { + m_logger.qbsTrace() << "Non-required module '" << fullName << "' not found." + << "Creating dummy module for presence check."; + } + Item * const module = Item::create(m_pool); + module->setFile(FileContext::create()); + module->setProperty(QLatin1String("present"), VariantValue::create(false)); + return module; + } + + if (Q_UNLIKELY(triedToLoadModule)) + throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), + dependsItemLocation); + + return 0; +} + +// returns QVariant::Invalid for types that do not need conversion +static QVariant::Type variantType(PropertyDeclaration::Type t) +{ + switch (t) { + case PropertyDeclaration::UnknownType: + break; + case PropertyDeclaration::Boolean: + return QVariant::Bool; + case PropertyDeclaration::Integer: + return QVariant::Int; + case PropertyDeclaration::Path: + return QVariant::String; + case PropertyDeclaration::PathList: + return QVariant::StringList; + case PropertyDeclaration::String: + return QVariant::String; + case PropertyDeclaration::StringList: + return QVariant::StringList; + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Verbatim: + return QVariant::String; + } + return QVariant::Invalid; +} + +static QVariant convertToPropertyType(const QVariant &v, PropertyDeclaration::Type t, + const QStringList &namePrefix, const QString &key) +{ + if (v.isNull() || !v.isValid()) + return v; + const QVariant::Type vt = variantType(t); + if (vt == QVariant::Invalid) + return v; + + // Handle the foo,bar,bla stringlist syntax. + if (t == PropertyDeclaration::StringList && v.type() == QVariant::String) + return v.toString().split(QLatin1Char(',')); + + QVariant c = v; + if (!c.convert(vt)) { + QStringList name = namePrefix; + name << key; + throw ErrorInfo(Tr::tr("Value '%1' of property '%2' has incompatible type.") + .arg(v.toString(), name.join(QLatin1String(".")))); + } + return c; +} + +static PropertyDeclaration firstValidPropertyDeclaration(Item *item, const QString &name) +{ + PropertyDeclaration decl; + do { + decl = item->propertyDeclarations().value(name); + if (decl.isValid()) + return decl; + item = item->prototype(); + } while (item); + return decl; +} + +Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *cacheHit) +{ + checkCancelation(); + Item *module = productContext->moduleItemCache.value(filePath); + if (module) { + m_logger.qbsTrace() << "[LDR] loadModuleFile cache hit for " << filePath; + *cacheHit = true; + return module; + } + + module = productContext->project->moduleItemCache.value(filePath); + if (module) { + m_logger.qbsTrace() << "[LDR] loadModuleFile returns clone for " << filePath; + *cacheHit = true; + return module->clone(m_pool); + } + + m_logger.qbsTrace() << "[LDR] loadModuleFile " << filePath; + *cacheHit = false; + module = m_reader->readFile(filePath); + if (!isBaseModule) { + DependsContext dependsContext; + dependsContext.product = productContext; + dependsContext.productDependencies = &productContext->info.usedProducts; + resolveDependencies(&dependsContext, module); + } + if (!checkItemCondition(module)) { + m_logger.qbsTrace() << "[LDR] module condition is false"; + return 0; + } + + // Module properties that are defined in the profile are used as default values. + const QVariantMap profileModuleProperties + = m_buildConfigProperties.value(fullModuleName).toMap(); + for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); + vmit != profileModuleProperties.end(); ++vmit) + { + if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) + throw ErrorInfo(Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); + const PropertyDeclaration decl = firstValidPropertyDeclaration(module, vmit.key()); + module->setProperty(vmit.key(), + VariantValue::create(convertToPropertyType(vmit.value(), decl.type, + QStringList(fullModuleName), vmit.key()))); + } + + productContext->moduleItemCache.insert(filePath, module); + productContext->project->moduleItemCache.insert(filePath, module); + return module; +} + +void ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) +{ + const QStringList baseModuleName(QLatin1String("qbs")); + Item::Module baseModuleDesc; + baseModuleDesc.name = baseModuleName; + baseModuleDesc.item = loadModule(productContext, item, CodeLocation(), QString(), + baseModuleName, true, true); + if (Q_UNLIKELY(!baseModuleDesc.item)) + throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); + item->modules() += baseModuleDesc; +} + +void ModuleLoader::setupBaseModulePrototype(Item *prototype) +{ + prototype->setProperty(QLatin1String("getNativeSetting"), + BuiltinValue::create(BuiltinValue::GetNativeSettingFunction)); + const BuiltinValuePtr getEnvValue = BuiltinValue::create(BuiltinValue::GetEnvFunction); + prototype->setProperty(QLatin1String("getEnv"), getEnvValue); + prototype->setProperty(QLatin1String("getenv"), getEnvValue); // TODO: Remove in 1.3. + prototype->setProperty(QLatin1String("getHostOS"), + BuiltinValue::create(BuiltinValue::GetHostOSFunction)); + prototype->setProperty(QLatin1String("canonicalArchitecture"), + BuiltinValue::create(BuiltinValue::CanonicalArchitectureFunction)); +} + +static void collectItemsWithId_impl(Item *item, QList<Item *> *result) +{ + if (!item->id().isEmpty()) + result->append(item); + foreach (Item *child, item->children()) + collectItemsWithId_impl(child, result); +} + +static QList<Item *> collectItemsWithId(Item *item) +{ + QList<Item *> result; + collectItemsWithId_impl(item, &result); + return result; +} + +void ModuleLoader::instantiateModule(ProductContext *productContext, Item *instanceScope, + Item *moduleInstance, Item *modulePrototype, + const QStringList &moduleName) +{ + const QString fullName = fullModuleName(moduleName); + modulePrototype->setProperty(QLatin1String("name"), + VariantValue::create(fullName)); + + moduleInstance->setPrototype(modulePrototype); + moduleInstance->setFile(modulePrototype->file()); + moduleInstance->setLocation(modulePrototype->location()); + moduleInstance->setTypeName(modulePrototype->typeName()); + + // create module scope + Item *moduleScope = Item::create(m_pool); + moduleScope->setScope(instanceScope); + copyProperty(QLatin1String("project"), productContext->project->scope, moduleScope); + copyProperty(QLatin1String("product"), productContext->scope, moduleScope); + moduleInstance->setScope(moduleScope); + moduleInstance->setModuleInstanceFlag(true); + + QHash<Item *, Item *> prototypeInstanceMap; + prototypeInstanceMap[modulePrototype] = moduleInstance; + + // create instances for every child of the prototype + createChildInstances(productContext, moduleInstance, modulePrototype, &prototypeInstanceMap); + + // create ids from from the prototype in the instance + if (modulePrototype->file()->idScope()) { + foreach (Item *itemWithId, collectItemsWithId(modulePrototype)) { + Item *idProto = itemWithId; + Item *idInstance = prototypeInstanceMap.value(idProto); + QBS_ASSERT(idInstance, continue); + ItemValuePtr idInstanceValue = ItemValue::create(idInstance); + moduleScope->setProperty(itemWithId->id(), idInstanceValue); + } + } + + // create module instances for the dependencies of this module + foreach (Item::Module m, modulePrototype->modules()) { + Item *depinst = moduleInstanceItem(moduleInstance, m.name); + const bool safetyCheck = true; + if (safetyCheck) { + Item *obj = moduleInstance; + for (int i = 0; i < m.name.count(); ++i) { + ItemValuePtr iv = obj->itemProperty(m.name.at(i)); + QBS_CHECK(iv); + obj = iv->item(); + QBS_CHECK(obj); + } + QBS_CHECK(obj == depinst); + } + depinst->setPrototype(m.item); + depinst->setFile(m.item->file()); + depinst->setLocation(m.item->location()); + depinst->setTypeName(m.item->typeName()); + depinst->setScope(moduleInstance); + m.item = depinst; + moduleInstance->modules() += m; + } + + // override module properties given on the command line + const QVariantMap userModuleProperties = m_overriddenProperties.value(fullName).toMap(); + for (QVariantMap::const_iterator vmit = userModuleProperties.begin(); + vmit != userModuleProperties.end(); ++vmit) { + if (Q_UNLIKELY(!moduleInstance->hasProperty(vmit.key()))) { + throw ErrorInfo(Tr::tr("Unknown property: %1.%2") + .arg(fullModuleName(moduleName), vmit.key())); + } + const PropertyDeclaration decl = firstValidPropertyDeclaration(moduleInstance, vmit.key()); + moduleInstance->setProperty(vmit.key(), + VariantValue::create(convertToPropertyType(vmit.value(), decl.type, moduleName, + vmit.key()))); + } +} + +void ModuleLoader::createChildInstances(ProductContext *productContext, Item *instance, + Item *prototype, + QHash<Item *, Item *> *prototypeInstanceMap) const +{ + foreach (Item *childPrototype, prototype->children()) { + Item *childInstance = Item::create(m_pool); + prototypeInstanceMap->insert(childPrototype, childInstance); + childInstance->setPrototype(childPrototype); + childInstance->setTypeName(childPrototype->typeName()); + childInstance->setFile(childPrototype->file()); + childInstance->setLocation(childPrototype->location()); + childInstance->setScope(productContext->scope); + Item::addChild(instance, childInstance); + createChildInstances(productContext, childInstance, childPrototype, prototypeInstanceMap); + } +} + +void ModuleLoader::resolveProbes(Item *item) +{ + foreach (Item *child, item->children()) + if (child->typeName() == QLatin1String("Probe")) + resolveProbe(item, child); +} + +void ModuleLoader::resolveProbe(Item *parent, Item *probe) +{ + const JSSourceValueConstPtr configureScript = probe->sourceProperty(QLatin1String("configure")); + if (Q_UNLIKELY(!configureScript)) + throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); + typedef QPair<QString, QScriptValue> ProbeProperty; + QList<ProbeProperty> probeBindings; + for (Item *obj = probe; obj; obj = obj->prototype()) { + foreach (const QString &name, obj->properties().keys()) { + if (name == QLatin1String("configure")) + continue; + QScriptValue sv = m_evaluator->property(probe, name); + if (Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(sv))) { + ValuePtr value = obj->property(name); + throw ErrorInfo(sv.toString(), value ? value->location() : CodeLocation()); + } + probeBindings += ProbeProperty(name, sv); + } + } + QScriptValue scope = m_engine->newObject(); + m_engine->currentContext()->pushScope(m_evaluator->scriptValue(parent)); + m_engine->currentContext()->pushScope(scope); + m_engine->currentContext()->pushScope(m_evaluator->fileScope(configureScript->file())); + foreach (const ProbeProperty &b, probeBindings) + scope.setProperty(b.first, b.second); + QScriptValue sv = m_engine->evaluate(configureScript->sourceCode()); + if (Q_UNLIKELY(m_engine->hasErrorOrException(sv))) + throw ErrorInfo(sv.toString(), configureScript->location()); + foreach (const ProbeProperty &b, probeBindings) { + const QVariant newValue = scope.property(b.first).toVariant(); + if (newValue != b.second.toVariant()) + probe->setProperty(b.first, VariantValue::create(newValue)); + } + m_engine->currentContext()->popScope(); + m_engine->currentContext()->popScope(); + m_engine->currentContext()->popScope(); +} + +void ModuleLoader::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId(m_buildConfigProperties))); + } +} + +bool ModuleLoader::checkItemCondition(Item *item) +{ + if (m_evaluator->boolValue(item, QLatin1String("condition"), true)) + return true; + m_disabledItems += item; + return false; +} + +void ModuleLoader::checkItemTypes(Item *item) +{ + if (Q_UNLIKELY(!item->typeName().isEmpty() + && !m_reader->builtins()->containsType(item->typeName()))) { + const QString msg = Tr::tr("Unexpected item type '%1'."); + throw ErrorInfo(msg.arg(item->typeName()), item->location()); + } + + const ItemDeclaration decl = m_reader->builtins()->declarationsForType(item->typeName()); + foreach (Item *child, item->children()) { + if (child->typeName().isEmpty()) + continue; + checkItemTypes(child); + if (!decl.isChildTypeAllowed(child->typeName())) + throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") + .arg(item->typeName(), child->typeName()), item->location()); + } +} + +void ModuleLoader::callValidateScript(Item *module) +{ + m_evaluator->boolValue(module, QLatin1String("validate")); +} + +QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) +{ + QStringList result; + const QString propertyName = QLatin1String("qbsSearchPaths"); + const QStringList paths = m_evaluator->stringListValue(item, propertyName, wasSet); + const ValueConstPtr prop = item->property(propertyName); + foreach (const QString &path, paths) + result += FileInfo::resolvePath(FileInfo::path(prop->location().fileName()), path); + return result; +} + +void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) +{ + if (!sourceProject) + return; + const QList<PropertyDeclaration> &builtinProjectProperties + = m_reader->builtins()->declarationsForType(QLatin1String("Project")).properties(); + QSet<QString> builtinProjectPropertyNames; + foreach (const PropertyDeclaration &p, builtinProjectProperties) + builtinProjectPropertyNames << p.name; + + for (Item::PropertyDeclarationMap::ConstIterator it + = sourceProject->propertyDeclarations().constBegin(); + it != sourceProject->propertyDeclarations().constEnd(); ++it) { + + // We must not inherit built-in properties such as "name", + // but "qbsSearchPaths" is an exception. + if (it.key() == QLatin1String("qbsSearchPaths")) { + const JSSourceValueConstPtr &v + = targetProject->property(it.key()).dynamicCast<const JSSourceValue>(); + QBS_ASSERT(v, continue); + if (v->sourceCode() == QLatin1String("undefined")) + copyProperty(it.key(), sourceProject, targetProject); + continue; + } + + if (builtinProjectPropertyNames.contains(it.key())) + continue; + + if (targetProject->properties().contains(it.key())) + continue; // Ignore stuff the target project already has. + + targetProject->setPropertyDeclaration(it.key(), it.value()); + copyProperty(it.key(), sourceProject, targetProject); + } +} + +Item *ModuleLoader::wrapWithProject(Item *item) +{ + Item *prj = Item::create(item->pool()); + prj->setChildren(QList<Item *>() << item); + item->setParent(prj); + prj->setTypeName("Project"); + prj->setFile(item->file()); + prj->setLocation(item->location()); + m_reader->builtins()->setupItemForBuiltinType(prj); + return prj; +} + +QString ModuleLoader::findExistingModulePath(const QString &searchPath, + const QStringList &moduleName) +{ + QString dirPath = searchPath; + foreach (const QString &moduleNamePart, moduleName) { + dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); + if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) + return QString(); + } + return dirPath; +} + +void ModuleLoader::copyProperty(const QString &propertyName, const Item *source, + Item *destination) +{ + destination->setProperty(propertyName, source->property(propertyName)); +} + +void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) +{ + foreach (Item *child, item->children()) { + child->setScope(scope); + setScopeForDescendants(child, scope); + } +} + +QString ModuleLoader::fullModuleName(const QStringList &moduleName) +{ + // Currently the same as the module sub directory. + // ### Might be nicer to be a valid JS identifier. +#if QT_VERSION >= 0x050000 + return moduleName.join(QLatin1Char('/')); +#else + return moduleName.join(QLatin1String("/")); +#endif +} + +void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig) +{ + const QVariant buildConfigValue = buildConfig.value(buildConfigKey); + if (buildConfigValue.isNull()) + return; + const QVariantMap overridden = buildConfigValue.toMap(); + for (QVariantMap::const_iterator it = overridden.constBegin(); it != overridden.constEnd(); + ++it) { + const PropertyDeclaration decl = item->propertyDeclarations().value(it.key()); + if (!decl.isValid()) { + throw ErrorInfo( + Tr::tr("Unknown property: %1.%2").arg(buildConfigKey, it.key())); + } + item->setProperty(it.key(), + VariantValue::create(convertToPropertyType(it.value(), decl.type, + QStringList(buildConfigKey), it.key()))); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h new file mode 100644 index 000000000..f6f62a2c3 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.h @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_MODULELOADER_H +#define QBS_MODULELOADER_H + +#include "forward_decls.h" +#include "itempool.h" +#include <logging/logger.h> + +#include <QMap> +#include <QSet> +#include <QStringList> +#include <QVariantMap> + +QT_BEGIN_NAMESPACE +class QScriptContext; +class QScriptEngine; +QT_END_NAMESPACE + +namespace qbs { + +class CodeLocation; + +namespace Internal { + +class BuiltinDeclarations; +class Evaluator; +class Item; +class ItemReader; +class ProgressObserver; +class ScriptEngine; + +struct ModuleLoaderResult +{ + ModuleLoaderResult() + : itemPool(new ItemPool), root(0) + {} + + struct ProductInfo + { + struct Dependency + { + QString name; + bool required; + QString failureMessage; + }; + + QList<Dependency> usedProducts; + QList<Dependency> usedProductsFromExportItem; + }; + + QSharedPointer<ItemPool> itemPool; + Item *root; + QHash<Item *, ProductInfo> productInfos; + QSet<QString> qbsFiles; +}; + +/* + * Loader stage II. Responsible for + * - loading modules and module dependencies, + * - project references, + * - Probe items. + */ +class ModuleLoader +{ +public: + ModuleLoader(ScriptEngine *engine, BuiltinDeclarations *builtins, const Logger &logger); + ~ModuleLoader(); + + void setProgressObserver(ProgressObserver *progressObserver); + void setSearchPaths(const QStringList &searchPaths); + Evaluator *evaluator() const { return m_evaluator; } + + ModuleLoaderResult load(const QString &filePath, + const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, + bool wrapWithProjectItem = false); + + static QString fullModuleName(const QStringList &moduleName); + static void overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig); + +private: + class ContextBase + { + public: + ContextBase() + : item(0), scope(0) + {} + + Item *item; + Item *scope; + QStringList extraSearchPaths; + QMap<QString, Item *> moduleItemCache; + }; + + class ProjectContext : public ContextBase + { + public: + ModuleLoaderResult *result; + QString localModuleSearchPath; + }; + + class ProductContext : public ContextBase + { + public: + ProjectContext *project; + ModuleLoaderResult::ProductInfo info; + QSet<FileContextConstPtr> filesWithExportItem; + QList<Item *> exportItems; + }; + + class DependsContext + { + public: + ProductContext *product; + QList<ModuleLoaderResult::ProductInfo::Dependency> *productDependencies; + }; + + typedef QPair<Item *, ModuleLoaderResult::ProductInfo::Dependency> ProductDependencyResult; + typedef QList<ProductDependencyResult> ProductDependencyResults; + + void handleProject(ModuleLoaderResult *loadResult, Item *item, + const QSet<QString> &referencedFilePaths); + void handleProduct(ProjectContext *projectContext, Item *item); + void handleSubProject(ProjectContext *projectContext, Item *item, + const QSet<QString> &referencedFilePaths); + void createAdditionalModuleInstancesInProduct(ProductContext *productContext); + void handleGroup(ProductContext *productContext, Item *group); + void handleArtifact(ProductContext *productContext, Item *item); + void deferExportItem(ProductContext *productContext, Item *item); + void mergeExportItems(ProductContext *productContext); + void propagateModulesFromProduct(ProductContext *productContext, Item *item); + void resolveDependencies(DependsContext *productContext, Item *item); + class ItemModuleList; + void resolveDependsItem(DependsContext *dependsContext, Item *item, Item *dependsItem, ItemModuleList *moduleResults, ProductDependencyResults *productResults); + Item *moduleInstanceItem(Item *item, const QStringList &moduleName); + Item *loadModule(ProductContext *productContext, Item *item, + const CodeLocation &dependsItemLocation, const QString &moduleId, + const QStringList &moduleName, bool isBaseModule, bool isRequired); + Item *searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QStringList &moduleName, + const QStringList &extraSearchPaths, bool isRequired, bool *cacheHit); + Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *cacheHit); + void loadBaseModule(ProductContext *productContext, Item *item); + void setupBaseModulePrototype(Item *prototype); + void instantiateModule(ProductContext *productContext, Item *instanceScope, Item *moduleInstance, Item *modulePrototype, const QStringList &moduleName); + void createChildInstances(ProductContext *productContext, Item *instance, + Item *prototype, QHash<Item *, Item *> *prototypeInstanceMap) const; + void resolveProbes(Item *item); + void resolveProbe(Item *parent, Item *probe); + void checkCancelation() const; + bool checkItemCondition(Item *item); + void checkItemTypes(Item *item); + void callValidateScript(Item *module); + QStringList readExtraSearchPaths(Item *item, bool *wasSet = 0); + void copyProperties(const Item *sourceProject, Item *targetProject); + Item *wrapWithProject(Item *item); + static QString findExistingModulePath(const QString &searchPath, + const QStringList &moduleName); + static void copyProperty(const QString &propertyName, const Item *source, Item *destination); + static void setScopeForDescendants(Item *item, Item *scope); + + ScriptEngine *m_engine; + ItemPool *m_pool; + Logger m_logger; + ProgressObserver *m_progressObserver; + ItemReader *m_reader; + Evaluator *m_evaluator; + QStringList m_moduleSearchPaths; + QMap<QString, QStringList> m_moduleDirListCache; + QHash<Item *, QSet<QString> > m_validItemPropertyNamesPerItem; + QSet<Item *> m_disabledItems; + QVariantMap m_overriddenProperties; + QVariantMap m_buildConfigProperties; +}; + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_MODULELOADER_H diff --git a/src/lib/corelib/language/preparescriptobserver.cpp b/src/lib/corelib/language/preparescriptobserver.cpp new file mode 100644 index 000000000..c4f61fd29 --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "preparescriptobserver.h" + +#include "property.h" +#include "scriptengine.h" + +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +PrepareScriptObserver::PrepareScriptObserver(ScriptEngine *engine) + : m_engine(engine) + , m_productObjectId(-1) + , m_projectObjectId(-1) +{ +} + +void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) +{ + if (object.objectId() == m_productObjectId) { + m_engine->addPropertyRequestedInScript( + Property(QString(), name, value.toVariant(), Property::PropertyInProduct)); + } else if (object.objectId() == m_projectObjectId) { + m_engine->addPropertyRequestedInScript( + Property(QString(), name, value.toVariant(), Property::PropertyInProject)); + } + +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/preparescriptobserver.h b/src/lib/corelib/language/preparescriptobserver.h new file mode 100644 index 000000000..21b101a2f --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#ifndef QBS_PREPARESCRIPTOBSERVER_H +#define QBS_PREPARESCRIPTOBSERVER_H + +#include "scriptpropertyobserver.h" + +namespace qbs { +namespace Internal { +class ScriptEngine; + +class PrepareScriptObserver : public ScriptPropertyObserver +{ +public: + PrepareScriptObserver(ScriptEngine *engine); + + void setProductObjectId(qint64 productId) { m_productObjectId = productId; } + void setProjectObjectId(qint64 projectId) { m_projectObjectId = projectId; } + +private: + void onPropertyRead(const QScriptValue &object, const QString &name, const QScriptValue &value); + + ScriptEngine * const m_engine; + qint64 m_productObjectId; + qint64 m_projectObjectId; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp new file mode 100644 index 000000000..06205c7fe --- /dev/null +++ b/src/lib/corelib/language/projectresolver.cpp @@ -0,0 +1,999 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "projectresolver.h" + +#include "artifactproperties.h" +#include "builtindeclarations.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "moduleloader.h" +#include "propertymapinternal.h" +#include "scriptengine.h" +#include <jsextensions/moduleproperties.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/scripttools.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> + +#include <QFileInfo> +#include <QDir> +#include <QSet> +#include <set> + +namespace qbs { +namespace Internal { + +extern bool debugProperties; + +static const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +ProjectResolver::ProjectResolver(ModuleLoader *ldr, const BuiltinDeclarations *builtins, + const Logger &logger) + : m_evaluator(ldr->evaluator()) + , m_builtins(builtins) + , m_logger(logger) + , m_engine(m_evaluator->engine()) + , m_progressObserver(0) +{ +} + +ProjectResolver::~ProjectResolver() +{ +} + +void ProjectResolver::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) +{ + const QList<ResolvedProductPtr> allProducts = project->allProducts(); + for (int i = 0; i < allProducts.count(); ++i) { + const ResolvedProductConstPtr product1 = allProducts.at(i); + const QString productName = product1->name; + for (int j = i + 1; j < allProducts.count(); ++j) { + const ResolvedProductConstPtr product2 = allProducts.at(j); + if (product2->name == productName) { + ErrorInfo error; + error.append(Tr::tr("Duplicate product name '%1'.").arg(productName)); + error.append(Tr::tr("First product defined here."), product1->location); + error.append(Tr::tr("Second product defined here."), product2->location); + throw error; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolve(ModuleLoaderResult &loadResult, + const SetupProjectParameters &setupParameters) +{ + QBS_CHECK(FileInfo::isAbsolute(setupParameters.buildRoot())); + if (m_logger.traceEnabled()) + m_logger.qbsTrace() << "[PR] resolving " << loadResult.root->file()->filePath(); + + ProjectContext projectContext; + projectContext.loadResult = &loadResult; + m_setupParams = setupParameters; + m_productContext = 0; + m_moduleContext = 0; + resolveTopLevelProject(loadResult.root, &projectContext); + TopLevelProjectPtr top = projectContext.project.staticCast<TopLevelProject>(); + checkForDuplicateProductNames(top); + top->buildSystemFiles.unite(loadResult.qbsFiles); + return top; +} + +void ProjectResolver::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); + } +} + +QString ProjectResolver::verbatimValue(const ValueConstPtr &value) const +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = value.staticCast<const JSSourceValue>(); + result = sourceValue->sourceCode(); + } + return result; +} + +QString ProjectResolver::verbatimValue(Item *item, const QString &name) const +{ + return verbatimValue(item->property(name)); +} + +void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(item); + Q_UNUSED(projectContext); +} + +static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) +{ + QSet<QString> subProjectNames; + QSet<ResolvedProjectPtr> projectsInNeedOfNameChange; + foreach (const ResolvedProjectPtr &p, parentProject->subProjects) { + if (subProjectNames.contains(p->name)) + projectsInNeedOfNameChange << p; + else + subProjectNames << p->name; + makeSubProjectNamesUniqe(p); + } + while (!projectsInNeedOfNameChange.isEmpty()) { + QSet<ResolvedProjectPtr>::Iterator it = projectsInNeedOfNameChange.begin(); + while (it != projectsInNeedOfNameChange.end()) { + const ResolvedProjectPtr p = *it; + p->name += QLatin1Char('_'); + if (!subProjectNames.contains(p->name)) { + subProjectNames << p->name; + it = projectsInNeedOfNameChange.erase(it); + } else { + ++it; + } + } + } +} + +void ProjectResolver::resolveTopLevelProject(Item *item, ProjectContext *projectContext) +{ + if (m_progressObserver) + m_progressObserver->setMaximum(projectContext->loadResult->productInfos.count()); + const TopLevelProjectPtr project = TopLevelProject::create(); + project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); + project->buildDirectory + = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), project->id()); + projectContext->project = project; + resolveProject(item, projectContext); + project->usedEnvironment = m_engine->usedEnvironment(); + project->fileExistsResults = m_engine->fileExistsResults(); + project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); + project->environment = m_engine->environment(); + project->buildSystemFiles = m_engine->imports(); + makeSubProjectNamesUniqe(project); + resolveProductDependencies(projectContext); +} + +void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + projectContext->project->name = m_evaluator->stringValue(item, QLatin1String("name")); + projectContext->project->location = item->location(); + if (projectContext->project->name.isEmpty()) + projectContext->project->name = FileInfo::baseName(item->location().fileName()); // FIXME: Must also be changed in item? + projectContext->project->enabled + = m_evaluator->boolValue(item, QLatin1String("condition")); + if (!projectContext->project->enabled) + return; + + projectContext->dummyModule = ResolvedModule::create(); + + QVariantMap projectProperties; + for (QMap<QString, PropertyDeclaration>::const_iterator it + = item->propertyDeclarations().constBegin(); + it != item->propertyDeclarations().constEnd(); ++it) { + if (it.value().flags.testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + continue; + const ValueConstPtr v = item->property(it.key()); + QBS_ASSERT(v && v->type() != Value::ItemValueType, continue); + projectProperties.insert(it.key(), m_evaluator->property(item, it.key()).toVariant()); + } + projectContext->project->setProjectProperties(projectProperties); + + ItemFuncMap mapping; + mapping["Project"] = &ProjectResolver::resolveProject; + mapping["SubProject"] = &ProjectResolver::resolveSubProject; + mapping["Product"] = &ProjectResolver::resolveProduct; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Rule"] = &ProjectResolver::resolveRule; + + foreach (Item *child, item->children()) + callItemFunction(mapping, child, projectContext); + + foreach (const ResolvedProductPtr &product, projectContext->project->products) + postProcess(product, projectContext); +} + +void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + ProjectContext subProjectContext = createProjectContext(projectContext); + + Item * const projectItem = item->child(QLatin1String("Project")); + if (projectItem) { + resolveProject(projectItem, &subProjectContext); + return; + } + + // No project item was found, which means the project was disabled. + subProjectContext.project->enabled = false; + Item * const propertiesItem = item->child(QLatin1String("Properties")); + if (propertiesItem) { + subProjectContext.project->name + = m_evaluator->stringValue(propertiesItem, QLatin1String("name")); + } +} + +class ModuleNameEquals +{ + QString m_str; +public: + ModuleNameEquals(const QString &str) + : m_str(str) + {} + + bool operator()(const Item::Module &module) + { + return module.name.count() == 1 && module.name.first() == m_str; + } +}; + +void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + ProductContext productContext; + m_productContext = &productContext; + productContext.item = item; + const QString productSourceDirectory = QFileInfo(item->file()->filePath()).absolutePath(); + item->setProperty(QLatin1String("sourceDirectory"), + VariantValue::create(productSourceDirectory)); + item->setProperty(QLatin1String("buildDirectory"), VariantValue::create(projectContext + ->project->topLevelProject()->buildDirectory)); + ResolvedProductPtr product = ResolvedProduct::create(); + product->project = projectContext->project; + m_productItemMap.insert(product, item); + projectContext->project->products += product; + productContext.product = product; + product->name = m_evaluator->stringValue(item, QLatin1String("name")); + if (product->name.isEmpty()) { + product->name = FileInfo::completeBaseName(item->file()->filePath()); + item->setProperty("name", VariantValue::create(product->name)); + } + m_logger.qbsTrace() << "[PR] resolveProduct " << product->name; + + if (std::find_if(item->modules().begin(), item->modules().end(), + ModuleNameEquals(product->name)) != item->modules().end()) { + throw ErrorInfo( + Tr::tr("The product name '%1' collides with a module name.").arg(product->name), + item->location()); + } + + ModuleLoader::overrideItemProperties(item, product->name, m_setupParams.overriddenValuesTree()); + m_productsByName.insert(product->name, product); + product->enabled = m_evaluator->boolValue(item, QLatin1String("condition")); + product->additionalFileTags + = m_evaluator->fileTagsValue(item, QLatin1String("additionalFileTags")); + product->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("type")); + product->targetName = m_evaluator->stringValue(item, QLatin1String("targetName")); + product->sourceDirectory = productSourceDirectory; + product->destinationDirectory + = m_evaluator->stringValue(item, QLatin1String("destinationDirectory")); + product->location = item->location(); + product->properties = PropertyMapInternal::create(); + product->properties->setValue(createProductConfig()); + ModuleProperties::init(m_evaluator->scriptValue(item), product); + + QList<Item *> subItems = item->children(); + const ValuePtr filesProperty = item->property(QLatin1String("files")); + if (filesProperty) { + Item *fakeGroup = Item::create(item->pool()); + fakeGroup->setFile(item->file()); + fakeGroup->setLocation(item->location()); + fakeGroup->setScope(item); + fakeGroup->setTypeName(QLatin1String("Group")); + fakeGroup->setProperty(QLatin1String("name"), VariantValue::create(product->name)); + fakeGroup->setProperty(QLatin1String("files"), filesProperty); + fakeGroup->setProperty(QLatin1String("excludeFiles"), + item->property(QLatin1String("excludeFiles"))); + fakeGroup->setProperty(QLatin1String("overrideTags"), VariantValue::create(false)); + m_builtins->setupItemForBuiltinType(fakeGroup); + subItems.prepend(fakeGroup); + } + + ItemFuncMap mapping; + mapping["Depends"] = &ProjectResolver::ignoreItem; + mapping["Rule"] = &ProjectResolver::resolveRule; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Transformer"] = &ProjectResolver::resolveTransformer; + mapping["Group"] = &ProjectResolver::resolveGroup; + mapping["Export"] = &ProjectResolver::resolveExport; + mapping["Probe"] = &ProjectResolver::ignoreItem; + + foreach (Item *child, subItems) + callItemFunction(mapping, child, projectContext); + + foreach (const Item::Module &module, item->modules()) + resolveModule(module.name, module.item, projectContext); + + m_productContext = 0; + if (m_progressObserver) + m_progressObserver->incrementProgressValue(); +} + +void ProjectResolver::resolveModule(const QStringList &moduleName, Item *item, + ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, QLatin1String("present"))) + return; + ModuleContext moduleContext; + moduleContext.module = ResolvedModule::create(); + m_moduleContext = &moduleContext; + + const ResolvedModulePtr &module = moduleContext.module; + module->name = ModuleLoader::fullModuleName(moduleName); + module->setupBuildEnvironmentScript = scriptFunctionValue(item, "setupBuildEnvironment"); + module->setupRunEnvironmentScript = scriptFunctionValue(item, "setupRunEnvironment"); + + m_productContext->product->additionalFileTags + += m_evaluator->fileTagsValue(item, "additionalProductFileTags"); + + foreach (const Item::Module &m, item->modules()) + module->moduleDependencies += ModuleLoader::fullModuleName(m.name); + + m_productContext->product->modules += module; + + ItemFuncMap mapping; + mapping["Rule"] = &ProjectResolver::resolveRule; + mapping["FileTagger"] = &ProjectResolver::resolveFileTagger; + mapping["Transformer"] = &ProjectResolver::resolveTransformer; + mapping["PropertyOptions"] = &ProjectResolver::ignoreItem; + mapping["Depends"] = &ProjectResolver::ignoreItem; + mapping["Probe"] = &ProjectResolver::ignoreItem; + foreach (Item *child, item->children()) + callItemFunction(mapping, child, projectContext); + + m_moduleContext = 0; +} + +static void createSourceArtifact(const ResolvedProductConstPtr &rproduct, + const PropertyMapPtr &properties, + const QString &fileName, + const FileTags &fileTags, + bool overrideTags, + QList<SourceArtifactPtr> &artifactList) +{ + SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->absoluteFilePath = FileInfo::resolvePath(rproduct->sourceDirectory, fileName); + artifact->fileTags = fileTags; + artifact->overrideFileTags = overrideTags; + artifact->properties = properties; + artifactList += artifact; +} + +static bool isSomeModulePropertySet(Item *group) +{ + for (QMap<QString, ValuePtr>::const_iterator it = group->properties().constBegin(); + it != group->properties().constEnd(); ++it) + { + if (it.value()->type() == Value::ItemValueType) { + // An item value is a module value in this case. + ItemValuePtr iv = it.value().staticCast<ItemValue>(); + foreach (ValuePtr ivv, iv->item()->properties()) { + if (ivv->type() == Value::JSSourceValueType) + return true; + } + } + } + return false; +} + +void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(projectContext); + checkCancelation(); + PropertyMapPtr properties = m_productContext->product->properties; + if (isSomeModulePropertySet(item)) { + properties = PropertyMapInternal::create(); + properties->setValue(evaluateModuleValues(item)); + } + + const bool isEnabled = m_evaluator->boolValue(item, QLatin1String("condition")); + QStringList files = m_evaluator->stringListValue(item, QLatin1String("files")); + const QStringList fileTagsFilter + = m_evaluator->stringListValue(item, QLatin1String("fileTagsFilter")); + if (!fileTagsFilter.isEmpty()) { + if (Q_UNLIKELY(!files.isEmpty())) + throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), + item->location()); + if (!isEnabled) + return; + ArtifactPropertiesPtr aprops = ArtifactProperties::create(); + aprops->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); + PropertyMapPtr cfg = PropertyMapInternal::create(); + cfg->setValue(evaluateModuleValues(item)); + aprops->setPropertyMapInternal(cfg); + m_productContext->product->artifactProperties += aprops; + return; + } + if (Q_UNLIKELY(files.isEmpty() && !item->hasProperty(QLatin1String("files")))) { + // Yield an error if Group without files binding is encountered. + // An empty files value is OK but a binding must exist. + throw ErrorInfo(Tr::tr("Group without files is not allowed."), + item->location()); + } + QStringList patterns; + for (int i = files.count(); --i >= 0;) { + if (FileInfo::isPattern(files[i])) + patterns.append(files.takeAt(i)); + } + GroupPtr group = ResolvedGroup::create(); + group->prefix = m_evaluator->stringValue(item, QLatin1String("prefix")); + if (!group->prefix.isEmpty()) { + for (int i = files.count(); --i >= 0;) + files[i].prepend(group->prefix); + } + group->location = item->location(); + group->enabled = isEnabled; + group->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("fileTags")); + group->overrideTags = m_evaluator->boolValue(item, QLatin1String("overrideTags")); + + if (!patterns.isEmpty()) { + SourceWildCards::Ptr wildcards = SourceWildCards::create(); + wildcards->excludePatterns = m_evaluator->stringListValue(item, "excludeFiles"); + wildcards->prefix = group->prefix; + wildcards->patterns = patterns; + QSet<QString> files = wildcards->expandPatterns(group, m_productContext->product->sourceDirectory); + foreach (const QString &fileName, files) + createSourceArtifact(m_productContext->product, properties, fileName, + group->fileTags, group->overrideTags, wildcards->files); + group->wildcards = wildcards; + } + + foreach (const QString &fileName, files) + createSourceArtifact(m_productContext->product, properties, fileName, + group->fileTags, group->overrideTags, group->files); + ErrorInfo fileError; + foreach (const SourceArtifactConstPtr &a, group->files) { + if (!FileInfo(a->absoluteFilePath).exists()) { + fileError.append(Tr::tr("File '%1' does not exist.") + .arg(a->absoluteFilePath), item->property("files")->location()); + } + } + if (fileError.hasError()) + throw ErrorInfo(fileError); + + group->name = m_evaluator->stringValue(item, "name"); + if (group->name.isEmpty()) + group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.count()); + group->properties = properties; + m_productContext->product->groups += group; +} + +static QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) +{ + const QString args = decl.functionArgumentNames.join(QLatin1String(",")); + if (value->hasFunctionForm()) { + // Insert the argument list. + QString code = value->sourceCode(); + code.insert(10, args); + // Remove the function application "()" that has been + // added in ItemReaderASTVisitor::visitStatement. + return code.left(code.length() - 2); + } else { + return QLatin1String("(function(") + args + QLatin1String("){return ") + + value->sourceCode() + QLatin1String(";})"); + } +} + +ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const +{ + ScriptFunctionPtr script = ScriptFunction::create(); + JSSourceValuePtr value = item->sourceProperty(name); + if (value) { + const PropertyDeclaration decl = item->propertyDeclaration(name); + script->sourceCode = sourceCodeAsFunction(value, decl); + script->argumentNames = decl.functionArgumentNames; + script->location = value->location(); + script->fileContext = resolvedFileContext(value->file()); + } + return script; +} + +ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) { + result = ResolvedFileContext::create(); + result->filePath = ctx->filePath(); + result->jsExtensions = ctx->jsExtensions(); + result->jsImports = ctx->jsImports(); + } + return result; +} + +void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (!m_evaluator->boolValue(item, QLatin1String("condition"))) + return; + + RulePtr rule = Rule::create(); + + // read artifacts + bool hasAlwaysUpdatedArtifact = false; + foreach (Item *child, item->children()) { + if (Q_UNLIKELY(child->typeName() != QLatin1String("Artifact"))) + throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), + child->location()); + + resolveRuleArtifact(rule, child, &hasAlwaysUpdatedArtifact); + } + + if (Q_UNLIKELY(!hasAlwaysUpdatedArtifact)) + throw ErrorInfo(Tr::tr("At least one output artifact of a rule " + "must have alwaysUpdated set to true."), + item->location()); + + rule->script = scriptFunctionValue(item, QLatin1String("prepare")); + rule->multiplex = m_evaluator->boolValue(item, QLatin1String("multiplex")); + rule->inputs = m_evaluator->fileTagsValue(item, "inputs"); + rule->usings = m_evaluator->fileTagsValue(item, "usings"); + rule->auxiliaryInputs + = m_evaluator->fileTagsValue(item, QLatin1String("auxiliaryInputs")); + rule->explicitlyDependsOn = m_evaluator->fileTagsValue(item, "explicitlyDependsOn"); + rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + if (m_productContext) + m_productContext->product->rules += rule; + else + projectContext->rules += rule; +} + +class StringListLess +{ +public: + bool operator()(const QStringList &lhs, const QStringList &rhs) + { + const int c = qMin(lhs.count(), rhs.count()); + for (int i = 0; i < c; ++i) { + int n = lhs.at(i).compare(rhs.at(i)); + if (n < 0) + return true; + if (n > 0) + return false; + } + return lhs.count() < rhs.count(); + } +}; + +class StringListSet : public std::set<QStringList, StringListLess> +{ +public: + typedef std::pair<iterator, bool> InsertResult; +}; + +void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item, + bool *hasAlwaysUpdatedArtifact) +{ + if (!m_evaluator->boolValue(item, QLatin1String("condition"))) + return; + RuleArtifactPtr artifact = RuleArtifact::create(); + rule->artifacts += artifact; + artifact->fileName = verbatimValue(item, "fileName"); + artifact->fileTags = m_evaluator->fileTagsValue(item, "fileTags"); + artifact->alwaysUpdated = m_evaluator->boolValue(item, "alwaysUpdated"); + if (artifact->alwaysUpdated) + *hasAlwaysUpdatedArtifact = true; + + StringListSet seenBindings; + for (Item *obj = item; obj; obj = obj->prototype()) { + for (QMap<QString, ValuePtr>::const_iterator it = obj->properties().constBegin(); + it != obj->properties().constEnd(); ++it) + { + if (it.value()->type() != Value::ItemValueType) + continue; + resolveRuleArtifactBinding(artifact, it.value().staticCast<ItemValue>()->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, + Item *item, + const QStringList &namePrefix, + StringListSet *seenBindings) +{ + for (QMap<QString, ValuePtr>::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) + { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(ruleArtifact, + it.value().staticCast<ItemValue>()->item(), name, + seenBindings); + } else if (it.value()->type() == Value::JSSourceValueType) { + const StringListSet::InsertResult insertResult = seenBindings->insert(name); + if (!insertResult.second) + continue; + JSSourceValuePtr sourceValue = it.value().staticCast<JSSourceValue>(); + RuleArtifact::Binding rab; + rab.name = name; + rab.code = sourceValue->sourceCode(); + rab.location = sourceValue->location(); + ruleArtifact->bindings += rab; + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + QList<FileTaggerConstPtr> &fileTaggers = m_productContext + ? m_productContext->product->fileTaggers : projectContext->fileTaggers; + QStringList patterns = m_evaluator->stringListValue(item, QLatin1String("patterns")); + const FileTags fileTags = m_evaluator->fileTagsValue(item, "fileTags"); + if (fileTags.isEmpty()) + throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); + + // TODO: Remove in 1.3. + bool patternWasSet; + const QStringList oldPatterns = m_evaluator->stringListValue(item, QLatin1String("pattern"), + &patternWasSet); + if (patternWasSet) { + m_logger.printWarning(ErrorInfo(Tr::tr("The 'pattern' property is deprecated. Please " + "use 'patterns' instead."), item->location())); + patterns << oldPatterns; + } + + if (patterns.isEmpty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + + foreach (const QString &pattern, patterns) { + if (pattern.isEmpty()) + throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); + } + fileTaggers += FileTagger::create(patterns, fileTags); +} + +void ProjectResolver::resolveTransformer(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, "condition")) { + m_logger.qbsTrace() << "[PR] transformer condition is false"; + return; + } + + ResolvedTransformerPtr rtrafo = ResolvedTransformer::create(); + rtrafo->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + rtrafo->inputs = m_evaluator->stringListValue(item, "inputs"); + for (int i = 0; i < rtrafo->inputs.count(); ++i) + rtrafo->inputs[i] = FileInfo::resolvePath(m_productContext->product->sourceDirectory, rtrafo->inputs.at(i)); + rtrafo->transform = scriptFunctionValue(item, QLatin1String("prepare")); + rtrafo->explicitlyDependsOn = m_evaluator->fileTagsValue(item, "explicitlyDependsOn"); + + foreach (const Item *child, item->children()) { + if (Q_UNLIKELY(child->typeName() != QLatin1String("Artifact"))) + throw ErrorInfo(Tr::tr("Transformer: wrong child type '%0'.").arg(child->typeName())); + SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->properties = m_productContext->product->properties; + QString fileName = m_evaluator->stringValue(child, "fileName"); + if (Q_UNLIKELY(fileName.isEmpty())) + throw ErrorInfo(Tr::tr("Artifact fileName must not be empty.")); + artifact->absoluteFilePath = FileInfo::resolvePath(m_productContext->product->topLevelProject()->buildDirectory, + fileName); + artifact->fileTags = m_evaluator->fileTagsValue(child, "fileTags"); + if (artifact->fileTags.isEmpty()) + artifact->fileTags.insert(unknownFileTag()); + rtrafo->outputs += artifact; + } + + m_productContext->product->transformers += rtrafo; +} + +void ProjectResolver::resolveExport(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(projectContext); + checkCancelation(); + const QString &productName = m_productContext->product->name; + m_exports[productName] = evaluateModuleValues(item); +} + +static void insertExportedConfig(const QString &usedProductName, + const QVariantMap &exportedConfig, + const PropertyMapPtr &propertyMap) +{ + QVariantMap properties = propertyMap->value(); + QVariant &modulesEntry = properties[QLatin1String("modules")]; + QVariantMap modules = modulesEntry.toMap(); + modules.insert(usedProductName, exportedConfig); + modulesEntry = modules; + propertyMap->setValue(properties); +} + +static void addUsedProducts(ModuleLoaderResult::ProductInfo *productInfo, + const ModuleLoaderResult::ProductInfo &usedProductInfo, + bool *productsAdded) +{ + int oldCount = productInfo->usedProducts.count(); + QSet<QString> usedProductNames; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, + productInfo->usedProducts) + usedProductNames += usedProduct.name; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, + usedProductInfo.usedProductsFromExportItem) { + if (!usedProductNames.contains(usedProduct.name)) + productInfo->usedProducts += usedProduct; + } + *productsAdded = (oldCount != productInfo->usedProducts.count()); +} + +void ProjectResolver::resolveProductDependencies(ProjectContext *projectContext) +{ + // Collect product dependencies from Export items. + bool productDependenciesAdded; + QList<ResolvedProductPtr> allProducts = projectContext->project->allProducts(); + do { + productDependenciesAdded = false; + foreach (const ResolvedProductPtr &rproduct, allProducts) { + if (!rproduct->enabled) + continue; + Item *productItem = m_productItemMap.value(rproduct); + ModuleLoaderResult::ProductInfo &productInfo + = projectContext->loadResult->productInfos[productItem]; + foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, + productInfo.usedProducts) { + ResolvedProductPtr usedProduct + = m_productsByName.value(dependency.name); + if (Q_UNLIKELY(!usedProduct)) + throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(dependency.name), + productItem->location()); + Item *usedProductItem = m_productItemMap.value(usedProduct); + const ModuleLoaderResult::ProductInfo usedProductInfo + = projectContext->loadResult->productInfos.value(usedProductItem); + bool added; + addUsedProducts(&productInfo, usedProductInfo, &added); + if (added) + productDependenciesAdded = true; + } + } + } while (productDependenciesAdded); + + // Resolve all inter-product dependencies. + foreach (const ResolvedProductPtr &rproduct, allProducts) { + if (!rproduct->enabled) + continue; + Item *productItem = m_productItemMap.value(rproduct); + foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, + projectContext->loadResult->productInfos.value(productItem).usedProducts) { + const QString &usedProductName = dependency.name; + ResolvedProductPtr usedProduct = m_productsByName.value(usedProductName); + if (Q_UNLIKELY(!usedProduct)) + throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(usedProductName), + productItem->location()); + rproduct->dependencies.insert(usedProduct); + + // insert the configuration of the Export item into the product's configuration + const QVariantMap exportedConfig = m_exports.value(usedProductName); + if (exportedConfig.isEmpty()) + continue; + + insertExportedConfig(usedProductName, exportedConfig, rproduct->properties); + + // insert the configuration of the Export item into the artifact configurations + foreach (SourceArtifactPtr artifact, rproduct->allEnabledFiles()) { + if (artifact->properties != rproduct->properties) + insertExportedConfig(usedProductName, exportedConfig, + artifact->properties); + } + } + } +} + +void ProjectResolver::postProcess(const ResolvedProductPtr &product, + ProjectContext *projectContext) const +{ + product->fileTaggers += projectContext->fileTaggers; + foreach (const RulePtr &rule, projectContext->rules) + product->rules += rule; + applyFileTaggers(product); +} + +void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const +{ + foreach (const SourceArtifactPtr &artifact, product->allEnabledFiles()) + applyFileTaggers(artifact, product, m_logger); +} + +void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product, const Logger &logger) +{ + if (!artifact->overrideFileTags || artifact->fileTags.isEmpty()) { + const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); + const FileTags fileTags = product->fileTagsForFileName(fileName); + artifact->fileTags.unite(fileTags); + if (artifact->fileTags.isEmpty()) + artifact->fileTags.insert(unknownFileTag()); + if (logger.traceEnabled()) + logger.qbsTrace() << "[PR] adding file tags " << artifact->fileTags + << " to " << fileName; + } +} + +QVariantMap ProjectResolver::evaluateModuleValues(Item *item) const +{ + QVariantMap modules; + evaluateModuleValues(item, &modules); + QVariantMap result; + result[QLatin1String("modules")] = modules; + return result; +} + +void ProjectResolver::evaluateModuleValues(Item *item, QVariantMap *modulesMap) const +{ + checkCancelation(); + for (Item::Modules::const_iterator it = item->modules().constBegin(); + it != item->modules().constEnd(); ++it) + { + QVariantMap depmods; + const Item::Module &module = *it; + evaluateModuleValues(module.item, &depmods); + QVariantMap dep = evaluateProperties(module.item); + dep.insert("modules", depmods); + modulesMap->insert(ModuleLoader::fullModuleName(module.name), dep); + } +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item) const +{ + const QVariantMap tmplt; + return evaluateProperties(item, item, tmplt); +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item, + Item *propertiesContainer, + const QVariantMap &tmplt) const +{ + QVariantMap result = tmplt; + for (QMap<QString, ValuePtr>::const_iterator it = propertiesContainer->properties().begin(); + it != propertiesContainer->properties().end(); ++it) + { + checkCancelation(); + switch (it.value()->type()) { + case Value::ItemValueType: + { + // Ignore items. Those point to module instances + // and are handled in evaluateModuleValues(). + break; + } + case Value::JSSourceValueType: + { + if (result.contains(it.key())) + break; + PropertyDeclaration pd; + for (Item *obj = item; obj; obj = obj->prototype()) { + pd = obj->propertyDeclarations().value(it.key()); + if (pd.isValid()) + break; + } + if (pd.type == PropertyDeclaration::Verbatim + || pd.flags.testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + { + break; + } + const QScriptValue scriptValue = m_evaluator->property(item, it.key()); + if (Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(scriptValue))) + throw ErrorInfo(scriptValue.toString(), it.value()->location()); + + // NOTE: Loses type information if scriptValue.isUndefined == true, + // as such QScriptValues become invalid QVariants. + QVariant v = scriptValue.toVariant(); + + if (pd.type == PropertyDeclaration::Path) + v = convertPathProperty(v.toString(), + m_productContext->product->sourceDirectory); + else if (pd.type == PropertyDeclaration::PathList) + v = convertPathListProperty(v.toStringList(), + m_productContext->product->sourceDirectory); + else if (pd.type == PropertyDeclaration::StringList) + v = v.toStringList(); + result[it.key()] = v; + break; + } + case Value::VariantValueType: + { + if (result.contains(it.key())) + break; + VariantValuePtr vvp = it.value().staticCast<VariantValue>(); + result[it.key()] = vvp->value(); + break; + } + case Value::BuiltinValueType: + // ignore + break; + } + } + return propertiesContainer->prototype() + ? evaluateProperties(item, propertiesContainer->prototype(), result) + : result; +} + +QVariantMap ProjectResolver::createProductConfig() const +{ + QVariantMap cfg = evaluateModuleValues(m_productContext->item); + cfg = evaluateProperties(m_productContext->item, m_productContext->item, cfg); + return cfg; +} + +QString ProjectResolver::convertPathProperty(const QString &path, const QString &dirPath) const +{ + return path.isEmpty() ? path : QDir::cleanPath(FileInfo::resolvePath(dirPath, path)); +} + +QStringList ProjectResolver::convertPathListProperty(const QStringList &paths, + const QString &dirPath) const +{ + QStringList result; + foreach (const QString &path, paths) + result += convertPathProperty(path, dirPath); + return result; +} + +void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, + ProjectContext *projectContext) +{ + const QByteArray typeName = item->typeName().toLocal8Bit(); + ItemFuncPtr f = mappings.value(typeName); + QBS_CHECK(f); + if (typeName == "Project") { + ProjectContext subProjectContext = createProjectContext(projectContext); + (this->*f)(item, &subProjectContext); + } else { + (this->*f)(item, projectContext); + } +} + +ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const +{ + ProjectContext subProjectContext; + subProjectContext.project = ResolvedProject::create(); + parentProjectContext->project->subProjects += subProjectContext.project; + subProjectContext.project->parentProject = parentProjectContext->project; + subProjectContext.loadResult = parentProjectContext->loadResult; + return subProjectContext; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h new file mode 100644 index 000000000..d468c8d5e --- /dev/null +++ b/src/lib/corelib/language/projectresolver.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef PROJECTRESOLVER_H +#define PROJECTRESOLVER_H + +#include "evaluator.h" +#include "filetags.h" +#include "language.h" +#include <logging/logger.h> +#include <tools/setupprojectparameters.h> + +#include <QMap> +#include <QSet> + +namespace qbs { +namespace Internal { + +class BuiltinDeclarations; +class Evaluator; +class Item; +class ModuleLoader; +class ProgressObserver; +class ScriptEngine; +class StringListSet; +struct ModuleLoaderResult; + +class ProjectResolver +{ +public: + ProjectResolver(ModuleLoader *ldr, const BuiltinDeclarations *builtins, const Logger &logger); + ~ProjectResolver(); + + void setProgressObserver(ProgressObserver *observer); + TopLevelProjectPtr resolve(ModuleLoaderResult &loadResult, + const SetupProjectParameters &setupParameters); + + static void applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product, const Logger &logger); + +private: + struct ProjectContext + { + ResolvedProjectPtr project; + QList<FileTaggerConstPtr> fileTaggers; + ModuleLoaderResult *loadResult; + QList<RulePtr> rules; + ResolvedModulePtr dummyModule; + }; + + struct ProductContext + { + ResolvedProductPtr product; + Item *item; + }; + + struct ModuleContext + { + ResolvedModulePtr module; + }; + + void checkCancelation() const; + QString verbatimValue(const ValueConstPtr &value) const; + QString verbatimValue(Item *item, const QString &name) const; + ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; + ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; + void ignoreItem(Item *item, ProjectContext *projectContext); + void resolveTopLevelProject(Item *item, ProjectContext *projectContext); + void resolveProject(Item *item, ProjectContext *projectContext); + void resolveSubProject(Item *item, ProjectContext *projectContext); + void resolveProduct(Item *item, ProjectContext *projectContext); + void resolveModule(const QStringList &moduleName, Item *item, ProjectContext *projectContext); + void resolveGroup(Item *item, ProjectContext *projectContext); + void resolveRule(Item *item, ProjectContext *projectContext); + void resolveRuleArtifact(const RulePtr &rule, Item *item, bool *hasAlwaysUpdatedArtifact); + static void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, + StringListSet *seenBindings); + void resolveFileTagger(Item *item, ProjectContext *projectContext); + void resolveTransformer(Item *item, ProjectContext *projectContext); + void resolveExport(Item *item, ProjectContext *projectContext); + void resolveProductDependencies(ProjectContext *projectContext); + void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; + void applyFileTaggers(const ResolvedProductPtr &product) const; + QVariantMap evaluateModuleValues(Item *item) const; + void evaluateModuleValues(Item *item, QVariantMap *modulesMap) const; + QVariantMap evaluateProperties(Item *item) const; + QVariantMap evaluateProperties(Item *item, Item *propertiesContainer, + const QVariantMap &tmplt) const; + QVariantMap createProductConfig() const; + QString convertPathProperty(const QString &path, const QString &dirPath) const; + QStringList convertPathListProperty(const QStringList &paths, const QString &dirPath) const; + ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; + + Evaluator *m_evaluator; + const BuiltinDeclarations *m_builtins; + Logger m_logger; + ScriptEngine *m_engine; + ProgressObserver *m_progressObserver; + ProductContext *m_productContext; + ModuleContext *m_moduleContext; + QMap<QString, ResolvedProductPtr> m_productsByName; + QHash<ResolvedProductPtr, Item *> m_productItemMap; + mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap; + QMap<QString, QVariantMap> m_exports; + SetupProjectParameters m_setupParams; + + typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); + typedef QMap<QByteArray, ItemFuncPtr> ItemFuncMap; + void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); +}; + +} // namespace Internal +} // namespace qbs + +#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/language/property.h b/src/lib/corelib/language/property.h new file mode 100644 index 000000000..b989e5316 --- /dev/null +++ b/src/lib/corelib/language/property.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#ifndef QBS_PROPERTY_H +#define QBS_PROPERTY_H + +#include <tools/qbsassert.h> + +#include <QSet> +#include <QString> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class Property +{ +public: + enum Kind + { + PropertyInModule, + PropertyInProduct, + PropertyInProject + }; + + Property() + : kind(PropertyInModule) + { + } + + Property(const QString &m, const QString &p, const QVariant &v, Kind k = PropertyInModule) + : moduleName(m), propertyName(p), value(v), kind(k) + { + QBS_CHECK(!moduleName.contains(QLatin1Char('.'))); + } + + QString moduleName; + QString propertyName; + QVariant value; + Kind kind; +}; + +inline bool operator==(const Property &p1, const Property &p2) +{ + return p1.moduleName == p2.moduleName && p1.propertyName == p2.propertyName; +} + +inline uint qHash(const Property &p) +{ + return QT_PREPEND_NAMESPACE(qHash)(p.moduleName + p.propertyName); +} + +typedef QSet<Property> PropertyList; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp new file mode 100644 index 000000000..79236e6da --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "propertydeclaration.h" + +namespace qbs { +namespace Internal { + +PropertyDeclaration::PropertyDeclaration() + : type(UnknownType) + , flags(DefaultFlags) +{ +} + +PropertyDeclaration::PropertyDeclaration(const QString &name, Type type, Flags flags) + : name(name) + , type(type) + , flags(flags) +{ +} + +PropertyDeclaration::~PropertyDeclaration() +{ +} + +bool PropertyDeclaration::isValid() const +{ + return type != UnknownType; +} + +PropertyDeclaration::Type PropertyDeclaration::propertyTypeFromString(const QString &typeName) +{ + if (typeName == QLatin1String("bool")) + return PropertyDeclaration::Boolean; + if (typeName == QLatin1String("int")) + return PropertyDeclaration::Integer; + if (typeName == QLatin1String("path")) + return PropertyDeclaration::Path; + if (typeName == QLatin1String("pathList")) + return PropertyDeclaration::PathList; + if (typeName == QLatin1String("string")) + return PropertyDeclaration::String; + if (typeName == QLatin1String("stringList")) + return PropertyDeclaration::StringList; + if (typeName == QLatin1String("var") || typeName == QLatin1String("variant")) + return PropertyDeclaration::Variant; + return PropertyDeclaration::UnknownType; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h new file mode 100644 index 000000000..441e42af9 --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_PROPERTYDECLARATION_H +#define QBS_PROPERTYDECLARATION_H + +#include <QString> +#include <QStringList> +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class PropertyDeclaration +{ +public: + enum Type + { + UnknownType, + Boolean, + Integer, + Path, + PathList, + String, + StringList, + Variant, + Verbatim + }; + + enum Flag + { + DefaultFlags = 0, + ListProperty = 0x1, + PropertyNotAvailableInConfig = 0x2 // Is this property part of a project, product or file configuration? + }; + Q_DECLARE_FLAGS(Flags, Flag) + + PropertyDeclaration(); + PropertyDeclaration(const QString &name, Type type, Flags flags = DefaultFlags); + ~PropertyDeclaration(); + + bool isValid() const; + + static Type propertyTypeFromString(const QString &typeName); + + QString name; + Type type; + Flags flags; + QScriptValue allowedValues; + QString description; + QString initialValueSource; + QStringList functionArgumentNames; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYDECLARATION_H diff --git a/src/lib/corelib/language/propertymapinternal.cpp b/src/lib/corelib/language/propertymapinternal.cpp new file mode 100644 index 000000000..063f9e947 --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "propertymapinternal.h" +#include <tools/propertyfinder.h> +#include <tools/scripttools.h> + +namespace qbs { +namespace Internal { + +/*! + * \class PropertyMapInternal + * \brief The \c PropertyMapInternal class contains a set of properties and their values. + * An instance of this class is attached to every \c ResolvedProduct. + * \c ResolvedGroups inherit their properties from the respective \c ResolvedProduct, \c SourceArtifacts + * inherit theirs from the respective \c ResolvedGroup. \c ResolvedGroups can override the value of an + * inherited property, \c SourceArtifacts cannot. If a property value is overridden, a new + * \c PropertyMapInternal object is allocated, otherwise the pointer is shared. + * \sa ResolvedGroup + * \sa ResolvedProduct + * \sa SourceArtifact + */ +PropertyMapInternal::PropertyMapInternal() +{ +} + +PropertyMapInternal::PropertyMapInternal(const PropertyMapInternal &other) + : PersistentObject(other), m_value(other.m_value) +{ +} + +QVariant PropertyMapInternal::qbsPropertyValue(const QString &key) const +{ + return PropertyFinder().propertyValue(value(), QLatin1String("qbs"), key); +} + +void PropertyMapInternal::setValue(const QVariantMap &map) +{ + m_value = map; +} + +static QString toJSLiteral(const QVariantMap &vm, int level = 0) +{ + QString indent; + for (int i = 0; i < level; ++i) + indent += QLatin1String(" "); + QString str; + for (QVariantMap::const_iterator it = vm.begin(); it != vm.end(); ++it) { + if (it.value().type() == QVariant::Map) { + str += indent + it.key() + QLatin1String(": {\n"); + str += toJSLiteral(it.value().toMap(), level + 1); + str += indent + QLatin1String("}\n"); + } else { + str += indent + it.key() + QLatin1String(": ") + toJSLiteral(it.value()) + + QLatin1Char('\n'); + } + } + return str; +} + +QString PropertyMapInternal::toJSLiteral() const +{ + return qbs::Internal::toJSLiteral(m_value); +} + +void PropertyMapInternal::load(PersistentPool &pool) +{ + pool.stream() >> m_value; +} + +void PropertyMapInternal::store(PersistentPool &pool) const +{ + pool.stream() << m_value; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h new file mode 100644 index 000000000..329f6ddb2 --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_PROPERTYMAPINTERNAL_H +#define QBS_PROPERTYMAPINTERNAL_H + +#include "forward_decls.h" +#include <tools/persistence.h> +#include <QVariantMap> + +namespace qbs { +namespace Internal { + +class PropertyMapInternal : public PersistentObject +{ +public: + static PropertyMapPtr create() { return PropertyMapPtr(new PropertyMapInternal); } + PropertyMapPtr clone() const { return PropertyMapPtr(new PropertyMapInternal(*this)); } + + const QVariantMap &value() const { return m_value; } + QVariant qbsPropertyValue(const QString &key) const; // Convenience function. + void setValue(const QVariantMap &value); + QString toJSLiteral() const; + +private: + PropertyMapInternal(); + PropertyMapInternal(const PropertyMapInternal &other); + + void load(PersistentPool &); + void store(PersistentPool &) const; + + QVariantMap m_value; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYMAPINTERNAL_H diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp new file mode 100644 index 000000000..e7f475d4c --- /dev/null +++ b/src/lib/corelib/language/scriptengine.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "scriptengine.h" + +#include "item.h" +#include "filecontext.h" +#include "propertymapinternal.h" +#include "scriptpropertyobserver.h" +#include <buildgraph/artifact.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +#include <QDebug> +#include <QFile> +#include <QScriptProgram> +#include <QScriptValueIterator> +#include <QSet> +#include <QTextStream> + +namespace qbs { +namespace Internal { + +const bool debugJSImports = false; + +ScriptEngine::ScriptEngine(const Logger &logger, QObject *parent) + : QScriptEngine(parent), m_logger(logger) +{ + QScriptValue objectProto = globalObject().property(QLatin1String("Object")); + m_definePropertyFunction = objectProto.property(QLatin1String("defineProperty")); + QBS_ASSERT(m_definePropertyFunction.isFunction(), /* ignore */); + m_emptyFunction = evaluate("(function(){})"); + QBS_ASSERT(m_emptyFunction.isFunction(), /* ignore */); + // Initially push a new context to turn off scope chain insanity mode. + QScriptEngine::pushContext(); + extendJavaScriptBuiltins(); +} + +ScriptEngine::~ScriptEngine() +{ +} + +void ScriptEngine::import(const JsImports &jsImports, QScriptValue scope, QScriptValue targetObject) +{ + for (JsImports::const_iterator it = jsImports.begin(); it != jsImports.end(); ++it) + import(*it, scope, targetObject); +} + +void ScriptEngine::import(const JsImport &jsImport, QScriptValue scope, QScriptValue targetObject) +{ + QBS_ASSERT(!scope.isValid() || scope.isObject(), return); + QBS_ASSERT(targetObject.isObject(), return); + QBS_ASSERT(targetObject.engine() == this, return); + + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] import into " << jsImport.scopeName; + + foreach (const QString &fileName, jsImport.fileNames) { + QScriptValue jsImportValue; + jsImportValue = m_jsImportCache.value(fileName); + if (jsImportValue.isValid()) { + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] " << fileName << " (cache hit)"; + targetObject.setProperty(jsImport.scopeName, jsImportValue); + } else { + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] " << fileName << " (cache miss)"; + QFile file(fileName); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(tr("Cannot open '%1'.").arg(fileName)); + const QString sourceCode = QTextStream(&file).readAll(); + file.close(); + QScriptProgram program(sourceCode, fileName); + importProgram(program, scope, jsImportValue); + targetObject.setProperty(jsImport.scopeName, jsImportValue); + m_jsImportCache.insert(fileName, jsImportValue); + } + } +} + +void ScriptEngine::clearImportsCache() +{ + m_jsImportCache.clear(); +} + +void ScriptEngine::addPropertyRequestedFromArtifact(const Artifact *artifact, + const Property &property) +{ + m_propertiesRequestedFromArtifact[artifact->filePath()] << property; +} + +void ScriptEngine::addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value) +{ + m_propertyCache.insert(qMakePair(moduleName + QLatin1Char('.') + propertyName, propertyMap), + value); +} + +QVariant ScriptEngine::retrieveFromPropertyCache(const QString &moduleName, + const QString &propertyName, const PropertyMapConstPtr &propertyMap) +{ + return m_propertyCache.value(qMakePair(moduleName + QLatin1Char('.') + propertyName, + propertyMap)); +} + +void ScriptEngine::defineProperty(QScriptValue &object, const QString &name, + const QScriptValue &descriptor) +{ + QScriptValue arguments = newArray(); + arguments.setProperty(0, object); + arguments.setProperty(1, name); + arguments.setProperty(2, descriptor); + QScriptValue result = m_definePropertyFunction.call(QScriptValue(), arguments); + QBS_ASSERT(!hasErrorOrException(result), qDebug() << name << result.toString()); +} + +static QScriptValue js_observedGet(QScriptContext *context, QScriptEngine *, void *arg) +{ + ScriptPropertyObserver * const observer = static_cast<ScriptPropertyObserver *>(arg); + const QScriptValue data = context->callee().property(QLatin1String("qbsdata")); + const QScriptValue value = data.property(2); + observer->onPropertyRead(data.property(0), data.property(1).toVariant().toString(), value); + return value; +} + +void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name, + const QScriptValue &value, ScriptPropertyObserver *observer) +{ + if (!observer) { + object.setProperty(name, value); + return; + } + + QScriptValue data = newArray(); + data.setProperty(0, object); + data.setProperty(1, name); + data.setProperty(2, value); + QScriptValue getterFunc = newFunction(js_observedGet, observer); + getterFunc.setProperty(QLatin1String("qbsdata"), data); + QScriptValue descriptor = newObject(); + descriptor.setProperty(QLatin1String("get"), getterFunc); + descriptor.setProperty(QLatin1String("set"), m_emptyFunction); + defineProperty(object, name, descriptor); +} + +QProcessEnvironment ScriptEngine::environment() const +{ + return m_environment; +} + +void ScriptEngine::setEnvironment(const QProcessEnvironment &env) +{ + m_environment = env; +} + +void ScriptEngine::importProgram(const QScriptProgram &program, const QScriptValue &scope, + QScriptValue &targetObject) +{ + QSet<QString> globalPropertyNames; + { + QScriptValueIterator it(globalObject()); + while (it.hasNext()) { + it.next(); + globalPropertyNames += it.name(); + } + } + + pushContext(); + if (scope.isObject()) + currentContext()->pushScope(scope); + QScriptValue result = evaluate(program); + QScriptValue activationObject = currentContext()->activationObject(); + if (scope.isObject()) + currentContext()->popScope(); + popContext(); + if (Q_UNLIKELY(hasErrorOrException(result))) + throw ErrorInfo(tr("Error when importing '%1': %2").arg(program.fileName(), result.toString())); + + // If targetObject is already an object, it doesn't get overwritten but enhanced by the + // contents of the .js file. + // This is necessary for library imports that consist of multiple js files. + if (!targetObject.isObject()) + targetObject = newObject(); + + // Copy every property of the activation object to the target object. + // We do not just save a reference to the activation object, because QScriptEngine contains + // special magic for activation objects that leads to unanticipated results. + { + QScriptValueIterator it(activationObject); + while (it.hasNext()) { + it.next(); + if (debugJSImports) + m_logger.qbsDebug() << "[ENGINE] Copying property " << it.name(); + targetObject.setProperty(it.name(), it.value()); + } + } + + // Copy new global properties to the target object and remove them from + // the global object. This is to support direct variable assignments + // without the 'var' keyword in JavaScript files. + QScriptValueIterator it(globalObject()); + while (it.hasNext()) { + it.next(); + if (globalPropertyNames.contains(it.name())) + continue; + + if (debugJSImports) { + m_logger.qbsDebug() << "[ENGINE] inserting global property " + << it.name() << " " << it.value().toString(); + } + + targetObject.setProperty(it.name(), it.value()); + it.remove(); + } +} + +void ScriptEngine::addEnvironmentVariable(const QString &name, const QString &value) +{ + m_usedEnvironment.insert(name, value); +} + +void ScriptEngine::addFileExistsResult(const QString &filePath, bool exists) +{ + m_fileExistsResult.insert(filePath, exists); +} + +void ScriptEngine::addFileLastModifiedResult(const QString &filePath, FileTime fileTime) +{ + m_fileLastModifiedResult.insert(filePath, fileTime); +} + +QSet<QString> ScriptEngine::imports() const +{ + return QSet<QString>::fromList(m_jsImportCache.keys()); +} + +QScriptValueList ScriptEngine::argumentList(const QStringList &argumentNames, + const QScriptValue &context) +{ + QScriptValueList result; + for (int i = 0; i < argumentNames.count(); ++i) + result += context.property(argumentNames.at(i)); + return result; +} + +class JSTypeExtender +{ +public: + JSTypeExtender(ScriptEngine *engine, const QString &typeName) + : m_engine(engine) + { + m_proto = engine->globalObject().property(typeName) + .property(QLatin1String("prototype")); + QBS_ASSERT(m_proto.isObject(), return); + m_descriptor = engine->newObject(); + } + + void addFunction(const QString &name, const QString &code) + { + QScriptValue f = m_engine->evaluate(code); + QBS_ASSERT(f.isFunction(), return); + m_descriptor.setProperty(QLatin1String("value"), f); + m_engine->defineProperty(m_proto, name, m_descriptor); + } + +private: + ScriptEngine *const m_engine; + QScriptValue m_proto; + QScriptValue m_descriptor; +}; + +void ScriptEngine::extendJavaScriptBuiltins() +{ + JSTypeExtender arrayExtender(this, QLatin1String("Array")); + arrayExtender.addFunction(QLatin1String("contains"), + QLatin1String("(function(e){return this.indexOf(e) !== -1;})")); + + JSTypeExtender stringExtender(this, QLatin1String("String")); + stringExtender.addFunction(QLatin1String("contains"), + QLatin1String("(function(e){return this.indexOf(e) !== -1;})")); + stringExtender.addFunction(QLatin1String("startsWith"), + QLatin1String("(function(e){return this.slice(0, e.length) === e;})")); + stringExtender.addFunction(QLatin1String("endsWith"), + QLatin1String("(function(e){return this.slice(-e.length) === e;})")); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h new file mode 100644 index 000000000..a2de9d9ea --- /dev/null +++ b/src/lib/corelib/language/scriptengine.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_SCRIPTENGINE_H +#define QBS_SCRIPTENGINE_H + +#include "jsimports.h" +#include "forward_decls.h" +#include <language/property.h> +#include <logging/logger.h> +#include <tools/filetime.h> +#include <tools/qbs_export.h> + +#include <QHash> +#include <QPair> +#include <QProcessEnvironment> +#include <QScriptEngine> +#include <QString> + +namespace qbs { +namespace Internal { +class Artifact; + +class ScriptPropertyObserver; + +// FIXME: Exported for qbs-qmltypes +class QBS_EXPORT ScriptEngine : public QScriptEngine +{ + Q_OBJECT +public: + ScriptEngine(const Logger &logger, QObject *parent = 0); + ~ScriptEngine(); + + void setLogger(const Logger &logger) { m_logger = logger; } + void import(const JsImports &jsImports, QScriptValue scope, QScriptValue targetObject); + void import(const JsImport &jsImport, QScriptValue scope, QScriptValue targetObject); + void clearImportsCache(); + + void addPropertyRequestedInScript(const Property &property) { + m_propertiesRequestedInScript += property; + } + void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); + void clearRequestedProperties() { + m_propertiesRequestedInScript.clear(); + m_propertiesRequestedFromArtifact.clear(); + } + PropertyList propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } + QHash<QString, PropertyList> propertiesRequestedFromArtifact() const { + return m_propertiesRequestedFromArtifact; + } + + void addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value); + QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap); + + void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor); + void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value, + ScriptPropertyObserver *observer); + + QProcessEnvironment environment() const; + void setEnvironment(const QProcessEnvironment &env); + void addEnvironmentVariable(const QString &name, const QString &value); + QHash<QString, QString> usedEnvironment() const { return m_usedEnvironment; } + void addFileExistsResult(const QString &filePath, bool exists); + void addFileLastModifiedResult(const QString &filePath, FileTime fileTime); + QHash<QString, bool> fileExistsResults() const { return m_fileExistsResult; } + QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; } + QSet<QString> imports() const; + static QScriptValueList argumentList(const QStringList &argumentNames, + const QScriptValue &context); + + class ScriptValueCache + { + public: + ScriptValueCache() : observer(0), project(0), product(0) {} + const void *observer; + const void *project; + const void *product; + QScriptValue observerScriptValue; + QScriptValue projectScriptValue; + QScriptValue productScriptValue; + }; + + ScriptValueCache *scriptValueCache() { return &m_scriptValueCache; } + + bool hasErrorOrException(const QScriptValue &v) const { + return v.isError() || hasUncaughtException(); + } + +private: + void extendJavaScriptBuiltins(); + void importProgram(const QScriptProgram &program, const QScriptValue &scope, + QScriptValue &targetObject); + + ScriptValueCache m_scriptValueCache; + QHash<QString, QScriptValue> m_jsImportCache; + QHash<QPair<QString, PropertyMapConstPtr>, QVariant> m_propertyCache; + PropertyList m_propertiesRequestedInScript; + QHash<QString, PropertyList> m_propertiesRequestedFromArtifact; + Logger m_logger; + QScriptValue m_definePropertyFunction; + QScriptValue m_emptyFunction; + QProcessEnvironment m_environment; + QHash<QString, QString> m_usedEnvironment; + QHash<QString, bool> m_fileExistsResult; + QHash<QString, FileTime> m_fileLastModifiedResult; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTENGINE_H diff --git a/src/lib/corelib/language/scriptpropertyobserver.h b/src/lib/corelib/language/scriptpropertyobserver.h new file mode 100644 index 000000000..e40ccc1d2 --- /dev/null +++ b/src/lib/corelib/language/scriptpropertyobserver.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_SCRIPTPROPERTYOBSERVER_H +#define QBS_SCRIPTPROPERTYOBSERVER_H + +#include <QScriptValue> + +namespace qbs { +namespace Internal { + +class ScriptPropertyObserver +{ +public: + virtual void onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTPROPERTYOBSERVER_H diff --git a/src/lib/corelib/language/testdata/Banana b/src/lib/corelib/language/testdata/Banana new file mode 100644 index 000000000..53164be8a --- /dev/null +++ b/src/lib/corelib/language/testdata/Banana @@ -0,0 +1 @@ +Peanut butter jelly time! diff --git a/src/lib/corelib/language/testdata/aboutdialog.cpp b/src/lib/corelib/language/testdata/aboutdialog.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/aboutdialog.cpp diff --git a/src/lib/corelib/language/testdata/baseproperty.qbs b/src/lib/corelib/language/testdata/baseproperty.qbs new file mode 100644 index 000000000..74024aedc --- /dev/null +++ b/src/lib/corelib/language/testdata/baseproperty.qbs @@ -0,0 +1,7 @@ +import "baseproperty_base.qbs" as BaseProduct + +BaseProduct { + name: "product1" + narf: base.concat(["boo"]) + zort: base.concat(["boo"]) +} diff --git a/src/lib/corelib/language/testdata/baseproperty_base.qbs b/src/lib/corelib/language/testdata/baseproperty_base.qbs new file mode 100644 index 000000000..85b64b76e --- /dev/null +++ b/src/lib/corelib/language/testdata/baseproperty_base.qbs @@ -0,0 +1,4 @@ +Product { + property var narf + property var zort: ["bar"] +} diff --git a/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs b/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs new file mode 100644 index 000000000..623919317 --- /dev/null +++ b/src/lib/corelib/language/testdata/buildconfigstringlistsyntax.qbs @@ -0,0 +1,3 @@ +Project { + property stringList someStrings +} diff --git a/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs b/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs new file mode 100644 index 000000000..f8f1b4d17 --- /dev/null +++ b/src/lib/corelib/language/testdata/builtinFunctionInSearchPathsProperty.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + qbsSearchPaths: { + if (!qbs.getenv("PATH")) + throw "getenv doesn't seem to work"; + } +} diff --git a/src/lib/corelib/language/testdata/canonicalArchitecture.qbs b/src/lib/corelib/language/testdata/canonicalArchitecture.qbs new file mode 100644 index 000000000..94da7b276 --- /dev/null +++ b/src/lib/corelib/language/testdata/canonicalArchitecture.qbs @@ -0,0 +1,3 @@ +Product { + name: qbs.canonicalArchitecture("i386") +} diff --git a/src/lib/corelib/language/testdata/conditionaldepends.qbs b/src/lib/corelib/language/testdata/conditionaldepends.qbs new file mode 100644 index 000000000..8ad3660ec --- /dev/null +++ b/src/lib/corelib/language/testdata/conditionaldepends.qbs @@ -0,0 +1,67 @@ +import qbs 1.0 +import "conditionaldepends_base.qbs" as CondBase + +Project { + CondBase { + name: 'conditionaldepends_derived' + someProp: true + } + + CondBase { + name: 'conditionaldepends_derived_false' + someProp: "knolf" === "narf" + } + + Product { + name: "product_props_true" + property bool someTrueProp: true + Depends { condition: someTrueProp; name: "dummy"} + } + + Product { + name: "product_props_false" + property bool someFalseProp: false + Depends { condition: someFalseProp; name: "dummy"} + } + + property bool someTruePrjProp: true + Product { + name: "project_props_true" + Depends { condition: project.someTruePrjProp; name: "dummy"} + } + + property bool someFalsePrjProp: false + Product { + name: "project_props_false" + Depends { condition: project.someFalsePrjProp; name: "dummy"} + } + + Product { + name: "module_props_true" + Depends { name: "dummy2" } + Depends { condition: dummy2.someTrueProp; name: "dummy" } + } + + Product { + name: "module_props_false" + Depends { name: "dummy2" } + Depends { condition: dummy2.someFalseProp; name: "dummy" } + } + + Product { + name: "contradictory_conditions1" + Depends { condition: false; name: "dummy" } + Depends { condition: true; name: "dummy" } // this one wins + } + + Product { + name: "contradictory_conditions2" + Depends { condition: true; name: "dummy" } // this one wins + Depends { condition: false; name: "dummy" } + } + + Product { + name: "unknown_dependency_condition_false" + Depends { condition: false; name: "doesonlyexistifhellfreezesover" } + } +} diff --git a/src/lib/corelib/language/testdata/conditionaldepends_base.qbs b/src/lib/corelib/language/testdata/conditionaldepends_base.qbs new file mode 100644 index 000000000..81782ba44 --- /dev/null +++ b/src/lib/corelib/language/testdata/conditionaldepends_base.qbs @@ -0,0 +1,10 @@ +import qbs 1.0 + +Application { + name: 'conditionaldepends_base' + property bool someProp: false + Depends { + condition: someProp + name: 'dummy' + } +} diff --git a/src/lib/corelib/language/testdata/drawline.asm b/src/lib/corelib/language/testdata/drawline.asm new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/drawline.asm diff --git a/src/lib/corelib/language/testdata/environmentvariable.qbs b/src/lib/corelib/language/testdata/environmentvariable.qbs new file mode 100644 index 000000000..b930e8511 --- /dev/null +++ b/src/lib/corelib/language/testdata/environmentvariable.qbs @@ -0,0 +1,3 @@ +Product { + name: qbs.getEnv("PRODUCT_NAME") +} diff --git a/src/lib/corelib/language/testdata/erroneous/importloop1.qbs b/src/lib/corelib/language/testdata/erroneous/importloop1.qbs new file mode 100644 index 000000000..91e8f620f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/importloop1.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 +import "importloop2.qbs" as X + +X {} + diff --git a/src/lib/corelib/language/testdata/erroneous/importloop2.qbs b/src/lib/corelib/language/testdata/erroneous/importloop2.qbs new file mode 100644 index 000000000..c41fe7e9f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/importloop2.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 +import "importloop1.qbs" as X + +X {} + diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs new file mode 100644 index 000000000..2341d4b9c --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_child_item_type.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + Depends { name: "foo" } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs new file mode 100644 index 000000000..18f2b044d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_file.qbs @@ -0,0 +1,5 @@ +import qbs + +Application { + files: ["main.cpp", "other.h"] +} diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs new file mode 100644 index 000000000..b9b392736 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_property_type.qbs @@ -0,0 +1,5 @@ +import qbs + +Product { + property nonsense esnesnon +} diff --git a/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs b/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs new file mode 100644 index 000000000..fc30a2af6 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/invalid_stringlist_element.qbs @@ -0,0 +1,3 @@ +Product { + files: ["foo", ["zoo"], "bar"] +} diff --git a/src/lib/corelib/language/testdata/erroneous/main.cpp b/src/lib/corelib/language/testdata/erroneous/main.cpp new file mode 100644 index 000000000..8b8d58de0 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/main.cpp @@ -0,0 +1 @@ +int main() { } diff --git a/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs b/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs new file mode 100644 index 000000000..17c7f6a14 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/multiple_exports.qbs @@ -0,0 +1,4 @@ +Product { + Export {} + Export {} +} diff --git a/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs b/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs new file mode 100644 index 000000000..0ecb41b34 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/multiple_properties_in_subproject.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + SubProject { + Properties { condition: false } + Properties { name: "blubb" } + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs b/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs new file mode 100644 index 000000000..6c5899b5d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/nonexistentouter.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Project { + Product { + name: outer + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs new file mode 100644 index 000000000..6d0960f09 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle2.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs new file mode 100644 index 000000000..0b0d2734d --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle2.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle3.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs b/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs new file mode 100644 index 000000000..2a237d154 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/references_cycle3.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Project { + references: ["references_cycle.qbs"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs b/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs new file mode 100644 index 000000000..3940109d0 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/reserved_name_in_import.qbs @@ -0,0 +1,4 @@ +import qbs +import "../idusagebase.qbs" as TextFile + +Product { } diff --git a/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs b/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs new file mode 100644 index 000000000..4254bb8f6 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/submodule_syntax.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "abc.def"; submodules: ["ghi"] } +} diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs new file mode 100644 index 000000000..0a9cd289f --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle2.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs new file mode 100644 index 000000000..ab92d76dd --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle2.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle3.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs b/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs new file mode 100644 index 000000000..af1e50f5a --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/subproject_cycle3.qbs @@ -0,0 +1,8 @@ +import qbs 1.0 + +Project { + SubProject { + filePath: "subproject_cycle.qbs" + } +} + diff --git a/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs b/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs new file mode 100644 index 000000000..fc251b1a4 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/throw_in_property_binding.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Product { + name: { + throw "something is wrong"; + } +} diff --git a/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs b/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs new file mode 100644 index 000000000..b2edbf013 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/undeclared_item.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + cpp.defines: ["SUPERCRAZY"] +} + diff --git a/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs b/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs new file mode 100644 index 000000000..1dad5f747 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/undeclared_property.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + doesntexist: 123 +} + diff --git a/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs b/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs new file mode 100644 index 000000000..9e34e9243 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/unknown_item_type.qbs @@ -0,0 +1,3 @@ +Narf { + zort: 1 // This invalid binding should not hide the "Unexpected item type" error. +} diff --git a/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs b/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs new file mode 100644 index 000000000..dcfc79a9c --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/unknown_module.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "neitherModuleNorProduct" } +} diff --git a/src/lib/corelib/language/testdata/exports.qbs b/src/lib/corelib/language/testdata/exports.qbs new file mode 100644 index 000000000..b1adfc214 --- /dev/null +++ b/src/lib/corelib/language/testdata/exports.qbs @@ -0,0 +1,49 @@ +import qbs 1.0 +import "exports_product.qbs" as ProductWithInheritedExportItem + +Project { + Application { + name: "myapp" + Depends { name: "mylib" } + } + StaticLibrary { + name: "mylib" + Depends { name: "dummy" } + dummy.defines: ["BUILD_MYLIB"] + Export { + Depends { name: "dummy" } + dummy.defines: ["USE_MYLIB"] + } + } + + Application { + name: "A" + Depends { name: "B" } + } + StaticLibrary { + name: "B" + Export { + Depends { name: "C" } + } + } + StaticLibrary { + name: "C" + Export { + Depends { name: "D" } + } + } + StaticLibrary { + name: "D" + } + + Application { + name: "myapp2" + Depends { name: "productWithInheritedExportItem" } + } + ProductWithInheritedExportItem { + name: "productWithInheritedExportItem" + Export { + dummy.cxxFlags: ["-bar"] + } + } +} diff --git a/src/lib/corelib/language/testdata/exports_product.qbs b/src/lib/corelib/language/testdata/exports_product.qbs new file mode 100644 index 000000000..78136fc32 --- /dev/null +++ b/src/lib/corelib/language/testdata/exports_product.qbs @@ -0,0 +1,7 @@ +Product { + Export { + Depends { name: "dummy" } + dummy.cxxFlags: ["-foo"] + dummy.defines: ["ABC"] + } +} diff --git a/src/lib/corelib/language/testdata/filecontextproperties.qbs b/src/lib/corelib/language/testdata/filecontextproperties.qbs new file mode 100644 index 000000000..5c435b3ba --- /dev/null +++ b/src/lib/corelib/language/testdata/filecontextproperties.qbs @@ -0,0 +1,5 @@ +Product { + name: "product1" + property string narf: filePath + property string zort: path +} diff --git a/src/lib/corelib/language/testdata/filetags.qbs b/src/lib/corelib/language/testdata/filetags.qbs new file mode 100644 index 000000000..38182e5f1 --- /dev/null +++ b/src/lib/corelib/language/testdata/filetags.qbs @@ -0,0 +1,73 @@ +import qbs 1.0 + +Project { + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + + Product { + name: "filetagger_project_scope" + files: ["main.cpp"] + } + + Product { + name: "filetagger_product_scope" + files: ["drawline.asm"] + FileTagger { + patterns: "*.asm" + fileTags: ["asm"] + } + } + + Product { + name: "filetagger_static_pattern" + files: "Banana" + FileTagger { + patterns: "Banana" + fileTags: ["yellow"] + } + } + + Product { + name: "unknown_file_tag" + files: "narf.zort" + } + + Product { + name: "set_file_tag_via_group" + Group { + files: ["main.cpp"] + fileTags: ["c++"] + } + } + + Product { + name: "override_file_tag_via_group" + files: "main.cpp" // gets file tag "cpp" through the FileTagger + Group { + files: product.files + fileTags: ["c++"] + } + } + + Product { + name: "add_file_tag_via_group" + files: "main.cpp" + Group { + overrideTags: false + files: "main.cpp" + fileTags: ["zzz"] + } + } + + Product { + name: "add_file_tag_via_group_and_file_ref" + files: "main.cpp" + Group { + overrideTags: false + files: product.files + fileTags: ["zzz"] + } + } +} diff --git a/src/lib/corelib/language/testdata/getNativeSetting.qbs b/src/lib/corelib/language/testdata/getNativeSetting.qbs new file mode 100644 index 000000000..c414c79f9 --- /dev/null +++ b/src/lib/corelib/language/testdata/getNativeSetting.qbs @@ -0,0 +1,23 @@ +import qbs.FileInfo + +Project { + Product { + name: { + if (qbs.hostOS.contains("osx")) { + return qbs.getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductName"); + } else if (qbs.hostOS.contains("windows")) { + var productName = qbs.getNativeSetting("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); + if (productName.contains("Windows")) { + return "Windows"; + } + return undefined; + } else { + return qbs.getNativeSetting(FileInfo.joinPaths(path, "nativesettings.ini"), "osname"); + } + } + } + + Product { + name: qbs.getNativeSetting("/dev/null", undefined, "fallback"); + } +} diff --git a/src/lib/corelib/language/testdata/groupconditions.qbs b/src/lib/corelib/language/testdata/groupconditions.qbs new file mode 100644 index 000000000..244c80c2c --- /dev/null +++ b/src/lib/corelib/language/testdata/groupconditions.qbs @@ -0,0 +1,53 @@ +import qbs 1.0 + +Project { + property bool someTrueProperty: true + Product { + name: "no_condition_no_group" + files: ["main.cpp"] + } + Product { + name: "no_condition" + Group { + files: ["main.cpp"] + } + } + Product { + name: "true_condition" + Group { + condition: true + files: ["main.cpp"] + } + } + Product { + name: "false_condition" + Group { + condition: false + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_product" + property bool anotherTrueProperty: true + Group { + condition: anotherTrueProperty + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_project" + Group { + condition: project.someTrueProperty + files: ["main.cpp"] + } + } + + Product { + name: "condition_accessing_module_property" + Group { + condition: qbs.targetOS.contains("narf") + files: ["main.cpp"] + qbs.install: false + } + } +} diff --git a/src/lib/corelib/language/testdata/groupname.qbs b/src/lib/corelib/language/testdata/groupname.qbs new file mode 100644 index 000000000..22e58765c --- /dev/null +++ b/src/lib/corelib/language/testdata/groupname.qbs @@ -0,0 +1,20 @@ +Project { + Product { + name: "MyProduct" + Group { + name: product.name + ".MyGroup" + files: "*" + } + } + + Product { + name: "My2ndProduct" + Group { + name: product.name + ".MyGroup" + files: ["narf"] + } + Group { + files: ["zort"] + } + } +} diff --git a/src/lib/corelib/language/testdata/homeDirectory.qbs b/src/lib/corelib/language/testdata/homeDirectory.qbs new file mode 100644 index 000000000..1ceeb5bbd --- /dev/null +++ b/src/lib/corelib/language/testdata/homeDirectory.qbs @@ -0,0 +1,18 @@ +import qbs 1.0 + +Project { + Product { + name: "home" + + // These should resolve + property path home: "~" + property path homeSlash: "~/" + property path homeUp: "~/.." + property path homeFile: "~/a" + + // These are sanity checks and should not + property path bogus1: "a~b" + property path bogus2: "a/~/bb" + property path user: "~foo/bar" // we don't resolve other-user paths + } +} diff --git a/src/lib/corelib/language/testdata/idusage.qbs b/src/lib/corelib/language/testdata/idusage.qbs new file mode 100644 index 000000000..42dc43ad5 --- /dev/null +++ b/src/lib/corelib/language/testdata/idusage.qbs @@ -0,0 +1,20 @@ +import qbs 1.0 +import "idusagebase.qbs" as DerivedProduct + +Project { + id: theProject + property int initialNr: 0 + DerivedProduct { + id: product1 + } + Product { + id: product2 + property int nr: theProject.initialNr + product1.nr + 1 + name: "product2_" + nr + } + Product { + id: product3 + property int nr: product2.nr + 1 + name: "product3_" + nr + } +} diff --git a/src/lib/corelib/language/testdata/idusagebase.qbs b/src/lib/corelib/language/testdata/idusagebase.qbs new file mode 100644 index 000000000..483a00ccf --- /dev/null +++ b/src/lib/corelib/language/testdata/idusagebase.qbs @@ -0,0 +1,5 @@ +Product { + id: baseProduct + property int nr: theProject.initialNr + 1 + name: "product1_" + nr +} diff --git a/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs b/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs new file mode 100644 index 000000000..e3e03a319 --- /dev/null +++ b/src/lib/corelib/language/testdata/invalidBindingInDisabledItem.qbs @@ -0,0 +1,16 @@ +import qbs 1.0 + +Project { + Product { + name: "product1" + condition: false + someNonsense: "Bitte stellen Sie die Tassen auf den Tisch." + } + Product { + name: "product2" + Group { + condition: false + moreNonsense: "Follen. Follen. Hünuntergefollen. Auf dön Töppüch." + } + } +} diff --git a/src/lib/corelib/language/testdata/jsextensions.js b/src/lib/corelib/language/testdata/jsextensions.js new file mode 100644 index 000000000..d26936911 --- /dev/null +++ b/src/lib/corelib/language/testdata/jsextensions.js @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +(function() { // Function wrapper to keep the environment clean. + +/* + * poor man's JS test suite + */ +var testctx = {}; + +function initTestContext(name) +{ + testctx.nr = 1; + testctx.name = name; +} + +function verify(c) +{ + if (!c) + throw testctx.name + ": verification #" + testctx.nr + " failed."; + testctx.nr++; +} + + +/* + * Tests for extensions of JavaScript builtin types. + */ + +var a = ["one", "two", "three"]; +initTestContext("Array.prototype.contains"); +for (var k in a) + verify(k !== "contains"); +verify(a.contains("one")); +verify(a.contains("two")); +verify(a.contains("three")); +verify(!a.contains("four")); + +})() // END function wrapper diff --git a/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js new file mode 100644 index 000000000..4e939505c --- /dev/null +++ b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.js @@ -0,0 +1,12 @@ +function getName(qbsModule) +{ + if (qbsModule.debugInformation) + return "MyProduct_debug"; + else + return "MyProduct"; +} + +function getInstallDir() +{ + return "somewhere"; +} diff --git a/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs new file mode 100644 index 000000000..388cf974b --- /dev/null +++ b/src/lib/corelib/language/testdata/jsimportsinmultiplescopes.qbs @@ -0,0 +1,7 @@ +import "jsimportsinmultiplescopes.js" as MyFunctions + +Product { + name: MyFunctions.getName(qbs) + qbs.installDir: MyFunctions.getInstallDir() + files: "main.cpp" +} diff --git a/src/lib/corelib/language/testdata/main.cpp b/src/lib/corelib/language/testdata/main.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/main.cpp diff --git a/src/lib/corelib/language/testdata/moduleproperties.qbs b/src/lib/corelib/language/testdata/moduleproperties.qbs new file mode 100644 index 000000000..c466a15cf --- /dev/null +++ b/src/lib/corelib/language/testdata/moduleproperties.qbs @@ -0,0 +1,21 @@ +import qbs 1.0 + +Project { + Product { + name: "merge_lists" + Depends { name: "dummyqt"; submodules: ["gui", "network"] } + Depends { name: "dummy" } + dummy.defines: ["THE_PRODUCT"] + } + Product { + name: "merge_lists_and_values" + Depends { name: "dummyqt"; submodules: ["network", "gui"] } + Depends { name: "dummy" } + dummy.defines: "THE_PRODUCT" + } + Product { + name: "merge_lists_with_duplicates" + Depends { name: "dummy" } + dummy.cxxFlags: ["-foo", "BAR", "-foo", "BAZ"] + } +} diff --git a/src/lib/corelib/language/testdata/modules.qbs b/src/lib/corelib/language/testdata/modules.qbs new file mode 100644 index 000000000..7841def35 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules.qbs @@ -0,0 +1,34 @@ +Project { + Product { + name: "no_modules" + property var foo + } + Product { + name: "qt_core" + dummyqt.core.version: "1.2.3" + property var foo: dummyqt.core.coreVersion + Depends { + name: "dummyqt.core" + } + } + Product { + name: "qt_gui" + property var foo: dummyqt.gui.guiProperty + Depends { + name: "dummyqt.gui" + } + } + Product { + name: "qt_gui_network" + property var foo: dummyqt.gui.guiProperty + ',' + dummyqt.network.networkProperty + Depends { + name: "dummyqt" + submodules: ["gui", "network"] + } + } + Product { + name: "dummy_twice" + Depends { name: "dummy" } + Depends { name: "dummy" } + } +} diff --git a/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs b/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs new file mode 100644 index 000000000..9484a70b8 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy/dummy.qbs @@ -0,0 +1,8 @@ +import "dummy_base.qbs" as DummyBase + +DummyBase { + condition: true + property stringList defines + property stringList cFlags + property stringList cxxFlags +} diff --git a/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs b/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs new file mode 100644 index 000000000..0ecd8a1d8 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy/dummy_base.qbs @@ -0,0 +1,4 @@ +Module { + condition: false + property pathList includePaths +} diff --git a/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs b/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs new file mode 100644 index 000000000..f60c38a71 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummy2/dummy2.qbs @@ -0,0 +1,7 @@ +import qbs 1.0 + +Module { + property var defines + property var someTrueProp: true + property var someFalseProp: false +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs new file mode 100644 index 000000000..13f5e6fc1 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/core/dummycore.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 + +Module { + id: qtcore + property int versionMajor: 5 + property int versionMinor: 0 + property int versionPatch: 0 + property string version: versionMajor.toString() + "." + versionMinor.toString() + "." + versionPatch.toString() + property string coreProperty: "coreProperty" + property string coreVersion: qtcore.version + + Depends { name: "dummy" } + dummy.defines: ["QT_CORE"] +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs new file mode 100644 index 000000000..a42003c34 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/gui/dummygui.qbs @@ -0,0 +1,9 @@ +import qbs 1.0 + +Module { + Depends { name: "dummyqt.core" } + property string guiProperty: "guiProperty" + + Depends { name: "dummy" } + dummy.defines: ["QT_GUI"] +} diff --git a/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs b/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs new file mode 100644 index 000000000..2da3af050 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/dummyqt/network/dummynetwork.qbs @@ -0,0 +1,9 @@ +import qbs 1.0 + +Module { + Depends { name: "dummyqt"; submodules: ["core"] } + property string networkProperty: "networkProperty" + + Depends { name: "dummy" } + dummy.defines: ["QT_NETWORK"] +} diff --git a/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs b/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs new file mode 100644 index 000000000..ba7dbcbf0 --- /dev/null +++ b/src/lib/corelib/language/testdata/modules/scopemod/scopemod.qbs @@ -0,0 +1,12 @@ +import qbs 1.0 + +Module { + property int a: 1 + property int b: 1 + property int c: a + 1 + property int d: b + 1 + property int e: 1 + property int f: 1 + property int g: 1 + property int h: 1 +} diff --git a/src/lib/corelib/language/testdata/modulescope.qbs b/src/lib/corelib/language/testdata/modulescope.qbs new file mode 100644 index 000000000..c127f0c61 --- /dev/null +++ b/src/lib/corelib/language/testdata/modulescope.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 +import "modulescope_base.qbs" as MyProduct + +Project { + MyProduct { + name: "product1" + property int e: 12 + property int f: 13 + scopemod.a: 2 + scopemod.f: 2 + scopemod.g: e * f + scopemod.h: base + 2 + } +} diff --git a/src/lib/corelib/language/testdata/modulescope_base.qbs b/src/lib/corelib/language/testdata/modulescope_base.qbs new file mode 100644 index 000000000..16a9875fa --- /dev/null +++ b/src/lib/corelib/language/testdata/modulescope_base.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Product { + Depends { name: "scopemod" } + scopemod.h: e * f +} diff --git a/src/lib/corelib/language/testdata/narf b/src/lib/corelib/language/testdata/narf new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/narf diff --git a/src/lib/corelib/language/testdata/narf.zort b/src/lib/corelib/language/testdata/narf.zort new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/narf.zort diff --git a/src/lib/corelib/language/testdata/nativesettings.ini b/src/lib/corelib/language/testdata/nativesettings.ini new file mode 100644 index 000000000..4caf32e56 --- /dev/null +++ b/src/lib/corelib/language/testdata/nativesettings.ini @@ -0,0 +1 @@ +osname = Unix diff --git a/src/lib/corelib/language/testdata/outerInGroup.qbs b/src/lib/corelib/language/testdata/outerInGroup.qbs new file mode 100644 index 000000000..751392a4d --- /dev/null +++ b/src/lib/corelib/language/testdata/outerInGroup.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 + +Project { + Product { + name: "OuterInGroup" + qbs.installDir: "/somewhere" + files: ["main.cpp"] + Group { + name: "Special Group" + files: ["aboutdialog.cpp"] + qbs.installDir: outer + "/else" + } + } +} diff --git a/src/lib/corelib/language/testdata/pathproperties.qbs b/src/lib/corelib/language/testdata/pathproperties.qbs new file mode 100644 index 000000000..f0eeabf57 --- /dev/null +++ b/src/lib/corelib/language/testdata/pathproperties.qbs @@ -0,0 +1,9 @@ +import "subdir/pathproperties_base.qbs" as ProductBase + +ProductBase { + name: "product1" + property path projectFileDir: "." + property pathList filesInProjectFileDir: ["./aboutdialog.h", "aboutdialog.cpp"] + Depends { name: "dummy" } + dummy.includePaths: ["."] +} diff --git a/src/lib/corelib/language/testdata/productconditions.qbs b/src/lib/corelib/language/testdata/productconditions.qbs new file mode 100644 index 000000000..336c41340 --- /dev/null +++ b/src/lib/corelib/language/testdata/productconditions.qbs @@ -0,0 +1,19 @@ +import qbs 1.0 + +Project { + Product { + name: "product_no_condition" + } + Product { + name: "product_true_condition" + condition: 1 === 1 + } + Product { + name: "product_false_condition" + condition: 1 === 2 + } + Product { + name: "product_condition_dependent_of_module" + condition: qbs.endianness !== (qbs.endianness + "foo") + } +} diff --git a/src/lib/corelib/language/testdata/productdirectories.qbs b/src/lib/corelib/language/testdata/productdirectories.qbs new file mode 100644 index 000000000..dc4315207 --- /dev/null +++ b/src/lib/corelib/language/testdata/productdirectories.qbs @@ -0,0 +1,5 @@ +import qbs 1.0 + +Product { + name: "MyApp" +} diff --git a/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs new file mode 100644 index 000000000..cc1b7b2a2 --- /dev/null +++ b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs @@ -0,0 +1,19 @@ +import qbs 1.0 + +Project { + Application { + name: { + if (!(dummy.cFlags instanceof Array)) + throw new Error("dummy.cFlags: Array type expected."); + if (!(dummy.cxxFlags instanceof Array)) + throw new Error("dummy.cxxFlags: Array type expected."); + if (!(dummy.defines instanceof Array)) + throw new Error("dummy.defines: Array type expected."); + return "product1"; + } + Depends { name: "dummy" } + // dummy.cxxFlags is set via profile and is not overridden + dummy.defines: ["IN_FILE"] // set in profile, overridden in file + dummy.cFlags: ["IN_FILE"] // set in profile, overridden on command line + } +} diff --git a/src/lib/corelib/language/testdata/propertiesblocks.qbs b/src/lib/corelib/language/testdata/propertiesblocks.qbs new file mode 100644 index 000000000..d61f0dd86 --- /dev/null +++ b/src/lib/corelib/language/testdata/propertiesblocks.qbs @@ -0,0 +1,150 @@ +import qbs 1.0 +import "propertiesblocks_base.qbs" as ProductBase + +Project { + Product { + name: "property_overwrite" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["SOMETHING"] + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_overwrite_no_outer" + Depends { id: cpp; name: "dummy" } + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_append_to_outer" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + } + Product { + name: "multiple_exclusive_properties" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["SOMETHING"] + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_no_outer" + Depends { id: cpp; name: "dummy" } + Properties { + condition: true + cpp.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_append_to_outer" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + cpp.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "ambiguous_properties" + Depends { id: cpp; name: "dummy" } + cpp.defines: ["ONE"] + Properties { + condition: true + cpp.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + cpp.defines: outer.concat(["IMPOSSIBLE"]) + } + Properties { // will be ignored + condition: true + cpp.defines: outer.concat(["THREE"]) + } + } + Product { + name: "condition_refers_to_product_property" + property string narf: true + Depends { name: "dummy" } + Properties { + condition: narf + dummy.defines: ["OVERWRITTEN"] + } + } + property string zort: true + Product { + name: "condition_refers_to_project_property" + Depends { name: "dummy" } + Properties { + condition: project.zort + dummy.defines: ["OVERWRITTEN"] + } + } + ProductBase { + name: "inheritance_overwrite_in_subitem" + dummy.defines: ["OVERWRITTEN_IN_SUBITEM"] + } + ProductBase { + name: "inheritance_retain_base1" + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_retain_base2" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + dummy.defines: ["GNAMPF"] + } + ProductBase { + name: "inheritance_retain_base3" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + // no dummy.defines binding + } + ProductBase { + name: "inheritance_condition_in_subitem1" + defineBase: false + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_condition_in_subitem2" + defineBase: false + // no dummy.defines binding + } + Product { + id: knolf + name: "gnampf" + } + Product { + name: "condition_references_id" + Depends { id: cpp; name: "dummy" } + Properties { + condition: knolf.name === "gnampf" + cpp.defines: ["OVERWRITTEN"] + } + } +} diff --git a/src/lib/corelib/language/testdata/propertiesblocks_base.qbs b/src/lib/corelib/language/testdata/propertiesblocks_base.qbs new file mode 100644 index 000000000..71b09a3da --- /dev/null +++ b/src/lib/corelib/language/testdata/propertiesblocks_base.qbs @@ -0,0 +1,11 @@ +import qbs 1.0 + +Product { + property bool defineBase: true + Depends { name: "dummy" } + Properties { + condition: defineBase + dummy.defines: ["BASE"] + } + dummy.defines: ["SOMETHING"] +} diff --git a/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs b/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs new file mode 100644 index 000000000..62427169f --- /dev/null +++ b/src/lib/corelib/language/testdata/subdir/pathproperties_base.qbs @@ -0,0 +1,4 @@ +Product { + property path base_fileInProductDir: "foo" + property path base_fileInBaseProductDir: path + "/bar" +} diff --git a/src/lib/corelib/language/testdata/zort b/src/lib/corelib/language/testdata/zort new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/lib/corelib/language/testdata/zort diff --git a/src/lib/corelib/language/tst_language.cpp b/src/lib/corelib/language/tst_language.cpp new file mode 100644 index 000000000..2308a7aa2 --- /dev/null +++ b/src/lib/corelib/language/tst_language.cpp @@ -0,0 +1,1439 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "tst_language.h" + +#include <language/evaluator.h> +#include <language/identifiersearch.h> +#include <language/item.h> +#include <language/itempool.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/scripttools.h> +#include <tools/error.h> +#include <tools/hostosinfo.h> +#include <tools/propertyfinder.h> + +#include <QProcessEnvironment> + +Q_DECLARE_METATYPE(QList<bool>) + +namespace qbs { +namespace Internal { +static QString testDataDir() { return FileInfo::resolvePath(QLatin1String(SRCDIR), + QLatin1String("language/testdata")); } +static QString testProject(const char *fileName) { + return testDataDir() + QLatin1Char('/') + QLatin1String(fileName); +} + +TestLanguage::TestLanguage(ILogSink *logSink) + : m_logSink(logSink) + , m_wildcardsTestDirPath(QDir::tempPath() + QLatin1String("/_wildcards_test_dir_")) +{ + qsrand(QTime::currentTime().msec()); + qRegisterMetaType<QList<bool> >("QList<bool>"); + defaultParameters.setBuildRoot("/some/build/directory"); +} + +TestLanguage::~TestLanguage() +{ +} + +QHash<QString, ResolvedProductPtr> TestLanguage::productsFromProject(ResolvedProjectPtr project) +{ + QHash<QString, ResolvedProductPtr> result; + foreach (ResolvedProductPtr product, project->products) + result.insert(product->name, product); + return result; +} + +ResolvedModuleConstPtr TestLanguage::findModuleByName(ResolvedProductPtr product, const QString &name) +{ + foreach (const ResolvedModuleConstPtr &module, product->modules) + if (module->name == name) + return module; + return ResolvedModuleConstPtr(); +} + +QVariant TestLanguage::productPropertyValue(ResolvedProductPtr product, QString propertyName) +{ + QStringList propertyNameComponents = propertyName.split(QLatin1Char('.')); + if (propertyNameComponents.count() > 1) + propertyNameComponents.prepend(QLatin1String("modules")); + return getConfigProperty(product->properties->value(), propertyNameComponents); +} + +void TestLanguage::handleInitCleanupDataTags(const char *projectFileName, bool *handled) +{ + const QByteArray dataTag = QTest::currentDataTag(); + if (dataTag == "init") { + *handled = true; + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject(projectFileName)); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + } else if (dataTag == "cleanup") { + *handled = true; + project.clear(); + } else { + *handled = false; + } +} + +QString TestLanguage::buildDir(const SetupProjectParameters ¶ms) const +{ + return FileInfo::resolvePath(params.buildRoot(), + getConfigProperty(params.buildConfigurationTree(), + QStringList() << "qbs" << "profile").toString() + QLatin1Char('-') + + getConfigProperty(params.buildConfigurationTree(), + QStringList() << "qbs" << "buildVariant").toString()); +} + +#define HANDLE_INIT_CLEANUP_DATATAGS(fn) {\ + bool handled;\ + handleInitCleanupDataTags(fn, &handled);\ + if (handled)\ + return;\ + QVERIFY(project);\ +} + +void TestLanguage::initTestCase() +{ + m_logger = Logger(m_logSink); + m_engine = new ScriptEngine(m_logger, this); + loader = new Loader(m_engine, m_logger); + loader->setSearchPaths(QStringList() + << QLatin1String(SRCDIR "/../../../share/qbs")); + QVariantMap buildConfig = defaultParameters.buildConfigurationTree(); + buildConfig.insert("qbs.targetOS", "linux"); + buildConfig.insert("qbs.architecture", "x86_64"); + buildConfig.insert("qbs.profile", "qbs_autotests"); + defaultParameters.setBuildConfiguration(buildConfig); + QVERIFY(QFileInfo(m_wildcardsTestDirPath).isAbsolute()); +} + +void TestLanguage::cleanupTestCase() +{ + delete loader; +} + +void TestLanguage::baseProperty() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("baseproperty.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QCOMPARE(cfg.value("narf").toStringList(), QStringList() << "boo"); + QCOMPARE(cfg.value("zort").toStringList(), QStringList() << "bar" << "boo"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::buildConfigStringListSyntax() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + QVariantMap overriddenValues; + overriddenValues.insert("project.someStrings", "foo,bar,baz"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("buildconfigstringlistsyntax.qbs")); + project = loader->loadProject(parameters); + QVERIFY(project); + QCOMPARE(project->projectProperties().value("someStrings").toStringList(), + QStringList() << "foo" << "bar" << "baz"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::builtinFunctionInSearchPathsProperty() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + parameters.setProjectFilePath(testProject("builtinFunctionInSearchPathsProperty.qbs")); + QVERIFY(loader->loadProject(parameters)); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::canonicalArchitecture() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("canonicalArchitecture.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(QLatin1String("x86")); + QVERIFY(product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::conditionalDepends() +{ + bool exceptionCaught = false; + ResolvedProductPtr product; + ResolvedModuleConstPtr dependency; + try { + defaultParameters.setProjectFilePath(testProject("conditionaldepends.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + + product = products.value("conditionaldepends_derived"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("conditionaldepends_derived_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("product_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("product_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("project_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("project_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("module_props_true"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(dependency); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("module_props_false"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(dependency); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("contradictory_conditions1"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("contradictory_conditions2"); + QVERIFY(product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(dependency); + + product = products.value("unknown_dependency_condition_false"); + QVERIFY(product); + dependency = findModuleByName(product, "doesonlyexistifhellfreezesover"); + QVERIFY(!dependency); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::environmentVariable() +{ + bool exceptionCaught = false; + try { + // Create new environment: + const QString varName = QLatin1String("PRODUCT_NAME"); + const QString productName = QLatin1String("MyApp") + QString::number(qrand()); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(varName, productName); + + QProcessEnvironment origEnv = defaultParameters.environment(); // store orig environment + + defaultParameters.setEnvironment(env); + defaultParameters.setProjectFilePath(testProject("environmentvariable.qbs")); + project = loader->loadProject(defaultParameters); + + defaultParameters.setEnvironment(origEnv); // reset environment + + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::erroneousFiles_data() +{ + QTest::addColumn<QString>("errorMessage"); + QTest::newRow("unknown_module") + << "Product dependency 'neitherModuleNorProduct' not found"; + QTest::newRow("submodule_syntax") + << "Depends.submodules cannot be used if name contains a dot"; + QTest::newRow("multiple_exports") + << "Multiple Export items in one product are prohibited."; + QTest::newRow("multiple_properties_in_subproject") + << "Multiple instances of item 'Properties' found where at most one " + "is allowed."; + QTest::newRow("importloop1") + << "Loop detected when importing"; + QTest::newRow("nonexistentouter") + << "Can't find variable: outer"; + QTest::newRow("invalid_file") + << "does not exist"; + QTest::newRow("invalid_property_type") + << "Unknown type 'nonsense' in property declaration."; + QTest::newRow("reserved_name_in_import") + << "Cannot reuse the name of built-in extension 'TextFile'."; + QTest::newRow("throw_in_property_binding") + << "something is wrong"; + QTest::newRow("references_cycle") + << "Cycle detected while referencing file 'references_cycle.qbs'."; + QTest::newRow("subproject_cycle") + << "Cycle detected while loading subproject file 'subproject_cycle.qbs'."; + QTest::newRow("invalid_stringlist_element") + << "Expected array element of type String at index 1."; + QTest::newRow("undeclared_item") + << "Item 'cpp' is not declared."; + QTest::newRow("undeclared_property") + << "Property 'doesntexist' is not declared."; + QTest::newRow("unknown_item_type") + << "Unexpected item type 'Narf'"; + QTest::newRow("invalid_child_item_type") + << "Items of type 'Project' cannot contain items of type 'Depends'."; +} + +void TestLanguage::erroneousFiles() +{ + QFETCH(QString, errorMessage); + QString fileName = QString::fromLocal8Bit(QTest::currentDataTag()) + QLatin1String(".qbs"); + try { + defaultParameters.setProjectFilePath(testProject("/erroneous/") + fileName); + loader->loadProject(defaultParameters); + } catch (const ErrorInfo &e) { + if (!e.toString().contains(errorMessage)) { + qDebug() << "Message: " << e.toString(); + qDebug() << "Expected: " << errorMessage; + QFAIL("Unexpected error message."); + } + return; + } + QFAIL("No error thrown on invalid input."); +} + +void TestLanguage::exports() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("exports.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 8); + ResolvedProductPtr product; + product = products.value("myapp"); + QVERIFY(product); + QStringList propertyName = QStringList() << "modules" << "mylib" + << "modules" << "dummy" << "defines"; + QVariant propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "USE_MYLIB"); + product = products.value("mylib"); + + QVERIFY(product); + propertyName = QStringList() << "modules" << "dummy" << "defines"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "BUILD_MYLIB"); + + product = products.value("A"); + QVERIFY(product); + QVERIFY(product->dependencies.contains(products.value("B"))); + QVERIFY(product->dependencies.contains(products.value("C"))); + QVERIFY(product->dependencies.contains(products.value("D"))); + product = products.value("B"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + product = products.value("C"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + product = products.value("D"); + QVERIFY(product); + QVERIFY(product->dependencies.isEmpty()); + + product = products.value("myapp2"); + QVERIFY(product); + propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + << "modules" << "dummy" << "cxxFlags"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "-bar"); + propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + << "modules" << "dummy" << "defines"; + propertyValue = getConfigProperty(product->properties->value(), propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "ABC"); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::fileContextProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("filecontextproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QCOMPARE(cfg.value("narf").toString(), defaultParameters.projectFilePath()); + QString dirPath = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + QCOMPARE(cfg.value("zort").toString(), dirPath); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::getNativeSetting() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("getNativeSetting.qbs")); + project = loader->loadProject(defaultParameters); + + QString expectedProductName; + if (HostOsInfo::isOsxHost()) + expectedProductName = QLatin1String("Mac OS X"); + else if (HostOsInfo::isWindowsHost()) + expectedProductName = QLatin1String("Windows"); + else + expectedProductName = QLatin1String("Unix"); + + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value(expectedProductName); + QVERIFY(product); + ResolvedProductPtr product2 = products.value(QLatin1String("fallback")); + QVERIFY(product2); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::groupConditions_data() +{ + QTest::addColumn<int>("groupCount"); + QTest::addColumn<QList<bool> >("groupEnabled"); + QTest::newRow("init") << 0 << QList<bool>(); + QTest::newRow("no_condition_no_group") + << 1 << (QList<bool>() << true); + QTest::newRow("no_condition") + << 2 << (QList<bool>() << true << true); + QTest::newRow("true_condition") + << 2 << (QList<bool>() << true << true); + QTest::newRow("false_condition") + << 2 << (QList<bool>() << true << false); + QTest::newRow("true_condition_from_product") + << 2 << (QList<bool>() << true << true); + QTest::newRow("true_condition_from_project") + << 2 << (QList<bool>() << true << true); + QTest::newRow("condition_accessing_module_property") + << 2 << (QList<bool>() << true << false); + QTest::newRow("cleanup") << 0 << QList<bool>(); +} + +void TestLanguage::groupConditions() +{ + HANDLE_INIT_CLEANUP_DATATAGS("groupconditions.qbs"); + QFETCH(int, groupCount); + QFETCH(QList<bool>, groupEnabled); + QCOMPARE(groupCount, groupEnabled.count()); + const QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QCOMPARE(product->groups.count(), groupCount); + for (int i = 0; i < groupCount; ++i) { + if (product->groups.at(i)->enabled != groupEnabled.at(i)) { + QFAIL(qPrintable( + QString("groups.at(%1)->enabled != %2").arg(i).arg(groupEnabled.at(i)))); + } + } +} + +void TestLanguage::groupName() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("groupname.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 2); + + ResolvedProductPtr product = products.value("MyProduct"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 2); + GroupConstPtr group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, QString("MyProduct")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("MyProduct.MyGroup")); + + product = products.value("My2ndProduct"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 3); + group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, QString("My2ndProduct")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("My2ndProduct.MyGroup")); + group = product->groups.at(2); + QVERIFY(group); + QCOMPARE(group->name, QString("Group 2")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::homeDirectory() +{ + try { + defaultParameters.setProjectFilePath(testProject("homeDirectory.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + + ResolvedProductPtr product = products.value("home"); + QVERIFY(product); + + QDir dir = QDir::home(); + QCOMPARE(product->properties->value().value("home").toString(), dir.canonicalPath()); + QCOMPARE(product->properties->value().value("homeSlash").toString(), dir.canonicalPath()); + + dir.cdUp(); + QCOMPARE(product->properties->value().value("homeUp").toString(), dir.canonicalPath()); + + dir = QDir::home(); + QCOMPARE(product->properties->value().value("homeFile").toString(), dir.filePath("a")); + + QCOMPARE(product->properties->value().value("bogus1").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("a~b"))); + QCOMPARE(product->properties->value().value("bogus2").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("a/~/bb"))); + QCOMPARE(product->properties->value().value("user").toString(), + FileInfo::resolvePath(product->sourceDirectory, QLatin1String("~foo/bar"))); + } + catch (const ErrorInfo &e) { + qDebug() << e.toString(); + } +} + +void TestLanguage::identifierSearch_data() +{ + QTest::addColumn<bool>("expectedHasNarf"); + QTest::addColumn<bool>("expectedHasZort"); + QTest::addColumn<QString>("sourceCode"); + QTest::newRow("no narf, no zort") << false << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'bar';\n" + " print(foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, no zort") << true << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'zort';\n" + " print(narf + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("no narf, zort") << false << true << QString( + "Product {\n" + " name: {\n" + " var foo = 'narf';\n" + " print(zort + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("2 narfs, 1 zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = narf + foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); +} + +void TestLanguage::identifierSearch() +{ + QFETCH(bool, expectedHasNarf); + QFETCH(bool, expectedHasZort); + QFETCH(QString, sourceCode); + + bool hasNarf = !expectedHasNarf; + bool hasZort = !expectedHasZort; + IdentifierSearch isearch; + isearch.add("narf", &hasNarf); + isearch.add("zort", &hasZort); + + QbsQmlJS::Engine engine; + QbsQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, 1); + QbsQmlJS::Parser parser(&engine); + QVERIFY(parser.parse()); + QVERIFY(parser.ast()); + isearch.start(parser.ast()); + QCOMPARE(hasNarf, expectedHasNarf); + QCOMPARE(hasZort, expectedHasZort); +} + +void TestLanguage::idUsage() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("idusage.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 3); + QVERIFY(products.contains("product1_1")); + QVERIFY(products.contains("product2_2")); + QVERIFY(products.contains("product3_3")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::invalidBindingInDisabledItem() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("invalidBindingInDisabledItem.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 2); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +class JSSourceValueCreator +{ + FileContextPtr m_fileContext; +public: + JSSourceValueCreator(const FileContextPtr &fileContext) + : m_fileContext(fileContext) + { + } + + JSSourceValuePtr create(const QString &sourceCode) + { + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_fileContext); + value->setSourceCode(sourceCode); + return value; + } +}; + +void TestLanguage::itemPrototype() +{ + FileContextPtr fileContext = FileContext::create(); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *proto = Item::create(&pool); + proto->setProperty("x", sourceValueCreator.create("1")); + proto->setProperty("y", sourceValueCreator.create("1")); + Item *item = Item::create(&pool); + item->setPrototype(proto); + item->setProperty("y", sourceValueCreator.create("x + 1")); + item->setProperty("z", sourceValueCreator.create("2")); + + Evaluator evaluator(m_engine, m_logger); + QCOMPARE(evaluator.property(proto, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(proto, "y").toVariant().toInt(), 1); + QVERIFY(!evaluator.property(proto, "z").isValid()); + QCOMPARE(evaluator.property(item, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(item, "y").toVariant().toInt(), 2); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 2); +} + +void TestLanguage::itemScope() +{ + FileContextPtr fileContext = FileContext::create(); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *scope1 = Item::create(&pool); + scope1->setProperty("x", sourceValueCreator.create("1")); + Item *scope2 = Item::create(&pool); + scope2->setScope(scope1); + scope2->setProperty("y", sourceValueCreator.create("x + 1")); + Item *item = Item::create(&pool); + item->setScope(scope2); + item->setProperty("z", sourceValueCreator.create("x + y")); + + Evaluator evaluator(m_engine, m_logger); + QCOMPARE(evaluator.property(scope1, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(scope2, "y").toVariant().toInt(), 2); + QVERIFY(!evaluator.property(scope2, "x").isValid()); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 3); +} + +void TestLanguage::jsExtensions() +{ + QFile file(testProject("jsextensions.js")); + QVERIFY(file.open(QFile::ReadOnly)); + QTextStream ts(&file); + QString code = ts.readAll(); + QVERIFY(!code.isEmpty()); + QScriptValue evaluated = m_engine->evaluate(code, file.fileName(), 1); + if (m_engine->hasErrorOrException(evaluated)) { + qDebug() << m_engine->uncaughtExceptionBacktrace(); + QFAIL(qPrintable(evaluated.toString())); + } +} + +void TestLanguage::jsImportUsedInMultipleScopes_data() +{ + QTest::addColumn<QString>("buildVariant"); + QTest::addColumn<QString>("expectedProductName"); + QTest::newRow("debug") << QString("debug") << QString("MyProduct_debug"); + QTest::newRow("release") << QString("release") << QString("MyProduct"); +} + +void TestLanguage::jsImportUsedInMultipleScopes() +{ + QFETCH(QString, buildVariant); + QFETCH(QString, expectedProductName); + + bool exceptionCaught = false; + try { + QVariantMap customBuildConfig = defaultParameters.buildConfiguration(); + customBuildConfig.insert(QLatin1String("qbs.buildVariant"), buildVariant); + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("jsimportsinmultiplescopes.qbs")); + params.setBuildConfiguration(customBuildConfig); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.values().first(); + QVERIFY(product); + QCOMPARE(product->name, expectedProductName); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::moduleProperties_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QStringList>("expectedValues"); + QTest::newRow("init") << QString() << QStringList(); + QTest::newRow("merge_lists") + << "defines" + << (QStringList() << "THE_PRODUCT" << "QT_CORE" << "QT_GUI" << "QT_NETWORK"); + QTest::newRow("merge_lists_and_values") + << "defines" + << (QStringList() << "THE_PRODUCT" << "QT_CORE" << "QT_GUI" << "QT_NETWORK"); + QTest::newRow("merge_lists_with_duplicates") + << "cxxFlags" + << (QStringList() << "-foo" << "BAR" << "-foo" << "BAZ"); + QTest::newRow("cleanup") << QString() << QStringList(); +} + +void TestLanguage::moduleProperties() +{ + HANDLE_INIT_CLEANUP_DATATAGS("moduleproperties.qbs"); + QFETCH(QString, propertyName); + QFETCH(QStringList, expectedValues); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QVariantList values = PropertyFinder().propertyValues(product->properties->value(), + "dummy", propertyName, + PropertyFinder::DoMergeLists); + QStringList valueStrings; + foreach (const QVariant &v, values) + valueStrings += v.toString(); + QCOMPARE(valueStrings, expectedValues); +} + +void TestLanguage::moduleScope() +{ + class IntPropertyFinder + { + const QVariantMap &m_properties; + public: + IntPropertyFinder(const QVariantMap &properties) + : m_properties(properties) + {} + + int intValue(const QString &name) + { + return PropertyFinder().propertyValue(m_properties, "scopemod", name).toInt(); + } + }; + + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("modulescope.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + IntPropertyFinder ipf(product->properties->value()); + QCOMPARE(ipf.intValue("a"), 2); // overridden in module instance + QCOMPARE(ipf.intValue("b"), 1); // genuine + QCOMPARE(ipf.intValue("c"), 3); // genuine, dependent on overridden value + QCOMPARE(ipf.intValue("d"), 2); // genuine, dependent on genuine value + QCOMPARE(ipf.intValue("e"), 1); // genuine + QCOMPARE(ipf.intValue("f"), 2); // overridden + QCOMPARE(ipf.intValue("g"), 156); // overridden, dependent on product properties + QCOMPARE(ipf.intValue("h"), 158); // overridden, base dependent on product properties + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + +} + +void TestLanguage::modules_data() +{ + QTest::addColumn<QStringList>("expectedModulesInProduct"); + QTest::addColumn<QString>("expectedProductProperty"); + QTest::newRow("init") << QStringList(); + QTest::newRow("no_modules") + << (QStringList() << "qbs") + << QString(); + QTest::newRow("qt_core") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core") + << QString("1.2.3"); + QTest::newRow("qt_gui") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core" << "dummyqt/gui") + << QString("guiProperty"); + QTest::newRow("qt_gui_network") + << (QStringList() << "qbs" << "dummy" << "dummyqt/core" << "dummyqt/gui" + << "dummyqt/network") + << QString("guiProperty,networkProperty"); + QTest::newRow("dummy_twice") + << (QStringList() << "qbs" << "dummy") + << QString(); + QTest::newRow("cleanup") << QStringList(); +} + +void TestLanguage::modules() +{ + HANDLE_INIT_CLEANUP_DATATAGS("modules.qbs"); + QFETCH(QStringList, expectedModulesInProduct); + QFETCH(QString, expectedProductProperty); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QStringList modulesInProduct; + foreach (ResolvedModuleConstPtr m, product->modules) + modulesInProduct += m->name; + modulesInProduct.sort(); + expectedModulesInProduct.sort(); + QCOMPARE(modulesInProduct, expectedModulesInProduct); + QCOMPARE(product->properties->value().value("foo").toString(), expectedProductProperty); +} + +void TestLanguage::outerInGroup() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("outerInGroup.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product = products.value("OuterInGroup"); + QVERIFY(product); + QCOMPARE(product->groups.count(), 2); + GroupPtr group = product->groups.at(0); + QVERIFY(group); + QCOMPARE(group->name, product->name); + QCOMPARE(group->files.count(), 1); + SourceArtifactConstPtr artifact = group->files.first(); + QVariant installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere")); + group = product->groups.at(1); + QVERIFY(group); + QCOMPARE(group->name, QString("Special Group")); + QCOMPARE(group->files.count(), 1); + artifact = group->files.first(); + installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere/else")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::pathProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("pathproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + QVariantMap cfg = product->properties->value(); + QString projectFileDir = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + QCOMPARE(cfg.value("projectFileDir").toString(), projectFileDir); + QStringList filesInProjectFileDir = QStringList() + << FileInfo::resolvePath(projectFileDir, "aboutdialog.h") + << FileInfo::resolvePath(projectFileDir, "aboutdialog.cpp"); + QCOMPARE(cfg.value("filesInProjectFileDir").toStringList(), filesInProjectFileDir); + QStringList includePaths = getConfigProperty(cfg, QStringList() << "modules" << "dummy" + << "includePaths").toStringList(); + QCOMPARE(includePaths, QStringList() << projectFileDir); + QCOMPARE(cfg.value("base_fileInProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QLatin1String("foo"))); + QCOMPARE(cfg.value("base_fileInBaseProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QLatin1String("subdir/bar"))); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::profileValuesAndOverriddenValues() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + QVariantMap buildConfig = parameters.buildConfiguration(); + buildConfig.insert("dummy.defines", "IN_PROFILE"); + buildConfig.insert("dummy.cFlags", "IN_PROFILE"); + buildConfig.insert("dummy.cxxFlags", "IN_PROFILE"); + parameters.setBuildConfiguration(buildConfig); + QVariantMap overriddenValues; + overriddenValues.insert("dummy.cFlags", "OVERRIDDEN"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("profilevaluesandoverriddenvalues.qbs")); + project = loader->loadProject(parameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(product); + PropertyFinder pf; + QVariantList values; + values = pf.propertyValues(product->properties->value(), + "dummy", "cxxFlags", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("IN_PROFILE")); + values = pf.propertyValues(product->properties->value(), + "dummy", "defines", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("IN_FILE")); + values = pf.propertyValues(product->properties->value(), + "dummy", "cFlags", PropertyFinder::DoMergeLists); + QCOMPARE(values.length(), 1); + QCOMPARE(values.first().toString(), QString("OVERRIDDEN")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::productConditions() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productconditions.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 4); + ResolvedProductPtr product; + product = products.value("product_no_condition"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_true_condition"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_condition_dependent_of_module"); + QVERIFY(product); + QVERIFY(product->enabled); + + product = products.value("product_false_condition"); + QVERIFY(product); + QVERIFY(!product->enabled); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::productDirectories() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productdirectories.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + QCOMPARE(products.count(), 1); + ResolvedProductPtr product; + product = products.value("MyApp"); + QVERIFY(product); + const QVariantMap config = product->properties->value(); + QCOMPARE(config.value(QLatin1String("buildDirectory")).toString(), + buildDir(defaultParameters)); + QCOMPARE(config.value(QLatin1String("sourceDirectory")).toString(), testDataDir()); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::propertiesBlocks_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QStringList>("expectedValues"); + + QTest::newRow("init") << QString() << QStringList(); + QTest::newRow("property_overwrite") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("property_overwrite_no_outer") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("property_append_to_outer") << QString("dummy.defines") << (QStringList() << QString("ONE") << QString("TWO")); + + QTest::newRow("multiple_exclusive_properties") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("multiple_exclusive_properties_no_outer") << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("multiple_exclusive_properties_append_to_outer") << QString("dummy.defines") << (QStringList() << QString("ONE") << QString("TWO")); + QTest::newRow("condition_refers_to_product_property") + << QString("dummy.defines") << QStringList("OVERWRITTEN"); + QTest::newRow("condition_refers_to_project_property") + << QString("dummy.defines") << QStringList("OVERWRITTEN"); + + QTest::newRow("ambiguous_properties") + << QString("dummy.defines") + << (QStringList() << QString("ONE") << QString("TWO")); + QTest::newRow("inheritance_overwrite_in_subitem") + << QString("dummy.defines") + << (QStringList() << QString("OVERWRITTEN_IN_SUBITEM")); + QTest::newRow("inheritance_retain_base1") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_retain_base2") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_retain_base3") + << QString("dummy.defines") + << (QStringList() << QString("BASE") << QString("SUB")); + QTest::newRow("inheritance_condition_in_subitem1") + << QString("dummy.defines") + << (QStringList() << QString("SOMETHING") << QString("SUB")); + QTest::newRow("inheritance_condition_in_subitem2") + << QString("dummy.defines") + << (QStringList() << QString("SOMETHING")); + QTest::newRow("condition_references_id") + << QString("dummy.defines") + << (QStringList() << QString("OVERWRITTEN")); + QTest::newRow("cleanup") << QString() << QStringList(); +} + +void TestLanguage::propertiesBlocks() +{ + HANDLE_INIT_CLEANUP_DATATAGS("propertiesblocks.qbs"); + QFETCH(QString, propertyName); + QFETCH(QStringList, expectedValues); + QVERIFY(project); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(product); + QCOMPARE(product->name, productName); + QVariant v = productPropertyValue(product, propertyName); + QCOMPARE(v.toStringList(), expectedValues); +} + +void TestLanguage::fileTags_data() +{ + QTest::addColumn<int>("numberOfGroups"); + QTest::addColumn<QStringList>("expectedFileTags"); + + QTest::newRow("init") << 0 << QStringList(); + QTest::newRow("filetagger_project_scope") << 1 << (QStringList() << "cpp"); + QTest::newRow("filetagger_product_scope") << 1 << (QStringList() << "asm"); + QTest::newRow("filetagger_static_pattern") << 1 << (QStringList() << "yellow"); + QTest::newRow("unknown_file_tag") << 1 << (QStringList() << "unknown-file-tag"); + QTest::newRow("set_file_tag_via_group") << 2 << (QStringList() << "c++"); + QTest::newRow("override_file_tag_via_group") << 2 << (QStringList() << "c++"); + QTest::newRow("add_file_tag_via_group") << 2 << (QStringList() << "cpp" << "zzz"); + QTest::newRow("add_file_tag_via_group_and_file_ref") << 2 << (QStringList() << "cpp" << "zzz"); + QTest::newRow("cleanup") << 0 << QStringList(); +} + +void TestLanguage::fileTags() +{ + HANDLE_INIT_CLEANUP_DATATAGS("filetags.qbs"); + QFETCH(int, numberOfGroups); + QFETCH(QStringList, expectedFileTags); + QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + ResolvedProductPtr product; + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + QVERIFY(product = products.value(productName)); + QCOMPARE(product->groups.count(), numberOfGroups); + GroupPtr group = product->groups.last(); + QVERIFY(group); + QCOMPARE(group->files.count(), 1); + SourceArtifactConstPtr sourceFile = group->files.first(); + QStringList fileTags = sourceFile->fileTags.toStringList(); + fileTags.sort(); + QCOMPARE(fileTags, expectedFileTags); +} + +void TestLanguage::wildcards_data() +{ + QTest::addColumn<bool>("useGroup"); + QTest::addColumn<QStringList>("filesToCreate"); + QTest::addColumn<QString>("projectFileSubDir"); + QTest::addColumn<QString>("prefix"); + QTest::addColumn<QStringList>("patterns"); + QTest::addColumn<QStringList>("excludePatterns"); + QTest::addColumn<QStringList>("expected"); + + const bool useGroup = true; + for (int i = 0; i <= 1; ++i) { + const bool useGroup = i; + const QByteArray dataTagSuffix = useGroup ? " group" : " nogroup"; + QTest::newRow(QByteArray("simple 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "foo.h" << "bar.h"); + QTest::newRow(QByteArray("simple 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "foo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("simple 3") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("exclude 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << (QStringList() << "bar*") + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("exclude 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*") + << (QStringList() << "*.qbs") + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("non-recursive") + dataTagSuffix) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/*") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp"); + QTest::newRow(QByteArray("absolute paths") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << m_wildcardsTestDirPath + "/?oo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("relative paths with dotdot") + dataTagSuffix) + << useGroup + << (QStringList() << "bar.h" << "bar.cpp") + << QString("TheLaughingLlama") + << QString() + << (QStringList() << "../bar.*") + << QStringList() + << (QStringList() << "bar.h" << "bar.cpp"); + } + QTest::newRow(QByteArray("recursive 1")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("recursive 2")) + << useGroup + << (QStringList() + << "d/1.h" << "b/d/1.h" << "b/c/d/1.h" + << "d/e/1.h" << "b/d/e/1.h" << "b/c/d/e/1.h" + << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h" + << "a/d/e/1.h" << "a/b/d/e/1.h" << "a/b/c/d/e/1.h" + << "a/d/1.cpp" << "a/b/d/1.cpp" << "a/b/c/d/1.h" + << "a/d/e/1.cpp" << "a/b/d/e/1.cpp" << "a/b/c/d/e/1.cpp") + << QString() + << QString() + << (QStringList() << "a/**/d/*.h") + << QStringList() + << (QStringList() << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h"); + QTest::newRow(QByteArray("recursive 3")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**/**/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("prefix")) + << useGroup + << (QStringList() << "subdir/foo.h" << "subdir/foo.cpp" << "subdir/bar.h" + << "subdir/bar.cpp") + << QString() + << QString("subdir/") + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "subdir/foo.h" << "subdir/bar.h"); +} + +void TestLanguage::wildcards() +{ + QFETCH(bool, useGroup); + QFETCH(QStringList, filesToCreate); + QFETCH(QString, projectFileSubDir); + QFETCH(QString, prefix); + QFETCH(QStringList, patterns); + QFETCH(QStringList, excludePatterns); + QFETCH(QStringList, expected); + + // create test directory + QDir::setCurrent(QDir::tempPath()); + { + QString errorMessage; + if (QFile::exists(m_wildcardsTestDirPath)) { + if (!removeDirectoryWithContents(m_wildcardsTestDirPath, &errorMessage)) { + qDebug() << errorMessage; + QVERIFY2(false, "removeDirectoryWithContents failed"); + } + } + QVERIFY(QDir().mkdir(m_wildcardsTestDirPath)); + } + + // create project file + const QString groupName = "Keks"; + QString dataTag = QString::fromLocal8Bit(QTest::currentDataTag()); + dataTag.replace(' ', '_'); + if (!projectFileSubDir.isEmpty()) { + if (!projectFileSubDir.startsWith('/')) + projectFileSubDir.prepend('/'); + if (projectFileSubDir.endsWith('/')) + projectFileSubDir.chop(1); + QVERIFY(QDir().mkpath(m_wildcardsTestDirPath + projectFileSubDir)); + } + const QString projectFilePath = m_wildcardsTestDirPath + projectFileSubDir + "/test_" + dataTag + + ".qbs"; + { + QFile projectFile(projectFilePath); + QVERIFY(projectFile.open(QIODevice::WriteOnly)); + QTextStream s(&projectFile); + s << "import qbs.base 1.0" << endl << endl + << "Application {" << endl + << " name: \"MyProduct\"" << endl; + if (useGroup) { + s << " Group {" << endl + << " name: " << toJSLiteral(groupName) << endl; + } + if (!prefix.isEmpty()) + s << " prefix: " << toJSLiteral(prefix) << endl; + if (!patterns.isEmpty()) + s << " files: " << toJSLiteral(patterns) << endl; + if (!excludePatterns.isEmpty()) + s << " excludeFiles: " << toJSLiteral(excludePatterns) << endl; + if (useGroup) + s << " }" << endl; + s << "}" << endl << endl; + } + + // create files + foreach (QString filePath, filesToCreate) { + filePath.prepend(m_wildcardsTestDirPath + '/'); + QFileInfo fi(filePath); + if (!QDir(fi.path()).exists()) + QVERIFY(QDir().mkpath(fi.path())); + QFile file(filePath); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + + // read the project + bool exceptionCaught = false; + ResolvedProductPtr product; + try { + defaultParameters.setProjectFilePath(projectFilePath); + project = loader->loadProject(defaultParameters); + QVERIFY(project); + const QHash<QString, ResolvedProductPtr> products = productsFromProject(project); + product = products.value("MyProduct"); + QVERIFY(product); + GroupPtr group; + if (useGroup) { + QCOMPARE(product->groups.count(), 2); + foreach (const GroupPtr &rg, product->groups) { + if (rg->name == groupName) { + group = rg; + break; + } + } + } else { + QCOMPARE(product->groups.count(), 1); + group = product->groups.first(); + } + QVERIFY(group); + QCOMPARE(group->files.count(), 0); + SourceWildCards::Ptr wildcards = group->wildcards; + QVERIFY(wildcards); + QStringList actualFilePaths; + foreach (const SourceArtifactConstPtr &artifact, wildcards->files) { + QString str = artifact->absoluteFilePath; + int idx = str.indexOf(m_wildcardsTestDirPath); + if (idx != -1) + str.remove(0, idx + m_wildcardsTestDirPath.count() + 1); + actualFilePaths << str; + } + actualFilePaths.sort(); + expected.sort(); + QCOMPARE(actualFilePaths, expected); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/tst_language.h b/src/lib/corelib/language/tst_language.h new file mode 100644 index 000000000..7e3ce5df5 --- /dev/null +++ b/src/lib/corelib/language/tst_language.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TST_LANGUAGE_H +#define TST_LANGUAGE_H + +#include <language/forward_decls.h> +#include <language/loader.h> +#include <logging/ilogsink.h> +#include <tools/setupprojectparameters.h> +#include <tools/qbs_export.h> +#include <QtTest> + +namespace qbs { +namespace Internal { + +class QBS_EXPORT TestLanguage : public QObject +{ + Q_OBJECT +public: + TestLanguage(ILogSink *logSink); + ~TestLanguage(); + +private: + ILogSink *m_logSink; + Logger m_logger; + ScriptEngine *m_engine; + Loader *loader; + TopLevelProjectPtr project; + SetupProjectParameters defaultParameters; + const QString m_wildcardsTestDirPath; + + QHash<QString, ResolvedProductPtr> productsFromProject(ResolvedProjectPtr project); + ResolvedModuleConstPtr findModuleByName(ResolvedProductPtr product, const QString &name); + QVariant productPropertyValue(ResolvedProductPtr product, QString propertyName); + void handleInitCleanupDataTags(const char *projectFileName, bool *handled); + QString buildDir(const SetupProjectParameters ¶ms) const; + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void baseProperty(); + void buildConfigStringListSyntax(); + void builtinFunctionInSearchPathsProperty(); + void canonicalArchitecture(); + void conditionalDepends(); + void environmentVariable(); + void erroneousFiles_data(); + void erroneousFiles(); + void exports(); + void fileContextProperties(); + void getNativeSetting(); + void groupConditions_data(); + void groupConditions(); + void groupName(); + void homeDirectory(); + void identifierSearch_data(); + void identifierSearch(); + void idUsage(); + void invalidBindingInDisabledItem(); + void itemPrototype(); + void itemScope(); + void jsExtensions(); + void jsImportUsedInMultipleScopes_data(); + void jsImportUsedInMultipleScopes(); + void moduleProperties_data(); + void moduleProperties(); + void moduleScope(); + void modules_data(); + void modules(); + void outerInGroup(); + void pathProperties(); + void profileValuesAndOverriddenValues(); + void productConditions(); + void productDirectories(); + void propertiesBlocks_data(); + void propertiesBlocks(); + void fileTags_data(); + void fileTags(); + void wildcards_data(); + void wildcards(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // TST_LANGUAGE_H diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp new file mode 100644 index 000000000..e9b755f2e --- /dev/null +++ b/src/lib/corelib/language/value.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "value.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +Value::Value(Type t) + : m_type(t) +{ +} + +Value::~Value() +{ +} + + +JSSourceValue::JSSourceValue() + : Value(JSSourceValueType) + , m_sourceUsesBase(false) + , m_sourceUsesOuter(false) + , m_hasFunctionForm(false) +{ +} + +JSSourceValuePtr JSSourceValue::create() +{ + return JSSourceValuePtr(new JSSourceValue); +} + +JSSourceValue::~JSSourceValue() +{ +} + + +ItemValue::ItemValue(Item *item) + : Value(ItemValueType) + , m_item(item) +{ +} + +ItemValuePtr ItemValue::create(Item *item) +{ + return ItemValuePtr(new ItemValue(item)); +} + +ItemValue::~ItemValue() +{ +} + +VariantValue::VariantValue(const QVariant &v) + : Value(VariantValueType) + , m_value(v) +{ +} + +VariantValuePtr VariantValue::create(const QVariant &v) +{ + return VariantValuePtr(new VariantValue(v)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h new file mode 100644 index 000000000..92313a98b --- /dev/null +++ b/src/lib/corelib/language/value.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_VALUE_H +#define QBS_VALUE_H + +#include "filecontext.h" +#include "item.h" +#include <tools/codelocation.h> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class ValueHandler; + +class Value +{ +public: + enum Type + { + JSSourceValueType, + ItemValueType, + VariantValueType, + BuiltinValueType + }; + + Value(Type t); + virtual ~Value(); + + Type type() const { return m_type; } + virtual void apply(ValueHandler *) = 0; + virtual CodeLocation location() const { return CodeLocation(); } + +private: + Type m_type; +}; + +class ValueHandler +{ +public: + virtual void handle(JSSourceValue *value) = 0; + virtual void handle(ItemValue *value) = 0; + virtual void handle(VariantValue *value) = 0; + virtual void handle(BuiltinValue *value) = 0; +}; + +class JSSourceValue : public Value +{ + friend class ItemReaderASTVisitor; + JSSourceValue(); +public: + static JSSourceValuePtr create(); + ~JSSourceValue(); + + void apply(ValueHandler *handler) { handler->handle(this); } + + void setSourceCode(const QString &sourceCode) { m_sourceCode = sourceCode; } + const QString &sourceCode() const { return m_sourceCode; } + + void setLocation(const CodeLocation &location) { m_location = location; } + CodeLocation location() const { return m_location; } + + void setFile(const FileContextPtr &file) { m_file = file; } + const FileContextPtr &file() const { return m_file; } + + bool sourceUsesBase() const { return m_sourceUsesBase; } + bool sourceUsesOuter() const { return m_sourceUsesOuter; } + bool hasFunctionForm() const { return m_hasFunctionForm; } + + const JSSourceValuePtr &baseValue() const { return m_baseValue; } + void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; } + + struct Alternative + { + QString condition; + const Item *conditionScopeItem; + JSSourceValuePtr value; + }; + + const QList<Alternative> &alternatives() const { return m_alternatives; } + void setAlternatives(const QList<Alternative> &alternatives) { m_alternatives = alternatives; } + void addAlternative(const Alternative &alternative) { m_alternatives.append(alternative); } + +private: + QString m_sourceCode; + CodeLocation m_location; + FileContextPtr m_file; + bool m_sourceUsesBase; + bool m_sourceUsesOuter; + bool m_hasFunctionForm; + JSSourceValuePtr m_baseValue; + QList<Alternative> m_alternatives; +}; + +class Item; + +class ItemValue : public Value +{ + ItemValue(Item *item); +public: + static ItemValuePtr create(Item *item = 0); + ~ItemValue(); + + void apply(ValueHandler *handler) { handler->handle(this); } + Item *item() const; + void setItem(Item *ptr); + +private: + Item *m_item; +}; + +inline Item *ItemValue::item() const +{ + return m_item; +} + +inline void ItemValue::setItem(Item *ptr) +{ + m_item = ptr; +} + + +class VariantValue : public Value +{ + VariantValue(const QVariant &v); +public: + static VariantValuePtr create(const QVariant &v = QVariant()); + + void apply(ValueHandler *handler) { handler->handle(this); } + + void setValue(const QVariant &v) { m_value = v; } + const QVariant &value() const { return m_value; } + +private: + QVariant m_value; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_VALUE_H diff --git a/src/lib/corelib/logging/ilogsink.cpp b/src/lib/corelib/logging/ilogsink.cpp new file mode 100644 index 000000000..118fdf106 --- /dev/null +++ b/src/lib/corelib/logging/ilogsink.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 "ilogsink.h" + +#include <tools/error.h> + +#include <QByteArray> +#include <QMutex> + +namespace qbs { + +QString logLevelTag(LoggerLevel level) +{ + if (level == LoggerInfo) + return QByteArray(); + QString str = logLevelName(level).toUpper(); + if (!str.isEmpty()) + str.append(QLatin1String(": ")); + return str; +} + +QString logLevelName(LoggerLevel level) +{ + switch (level) { + case qbs::LoggerError: + return QLatin1String("error"); + case qbs::LoggerWarning: + return QLatin1String("warning"); + case qbs::LoggerInfo: + return QLatin1String("info"); + case qbs::LoggerDebug: + return QLatin1String("debug"); + case qbs::LoggerTrace: + return QLatin1String("trace"); + default: + break; + } + return QString(); +} + +class ILogSink::ILogSinkPrivate +{ +public: + LoggerLevel logLevel; + QMutex mutex; +}; + +ILogSink::ILogSink() : d(new ILogSinkPrivate) +{ + d->logLevel = defaultLogLevel(); +} + +ILogSink::~ILogSink() +{ + delete d; +} + +void ILogSink::setLogLevel(LoggerLevel level) +{ + d->logLevel = level; +} + +LoggerLevel ILogSink::logLevel() const +{ + return d->logLevel; +} + +void ILogSink::printWarning(const ErrorInfo &warning) +{ + if (willPrint(LoggerWarning)) { + d->mutex.lock(); + doPrintWarning(warning); + d->mutex.unlock(); + } +} + +void ILogSink::printMessage(LoggerLevel level, const QString &message, const QString &tag, + bool force) +{ + if (force || willPrint(level)) { + d->mutex.lock(); + doPrintMessage(level, message, tag); + d->mutex.unlock(); + } +} + +void ILogSink::doPrintWarning(const ErrorInfo &warning) +{ + doPrintMessage(LoggerWarning, warning.toString(), QString()); +} + +} // namespace qbs diff --git a/src/lib/corelib/logging/ilogsink.h b/src/lib/corelib/logging/ilogsink.h new file mode 100644 index 000000000..9c263b52d --- /dev/null +++ b/src/lib/corelib/logging/ilogsink.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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_ILOGSINK_H +#define QBS_ILOGSINK_H + +#include "../tools/qbs_export.h" + +#include <QString> + +namespace qbs { +class ErrorInfo; + +enum LoggerLevel +{ + LoggerMinLevel, + LoggerError = LoggerMinLevel, + LoggerWarning, + LoggerInfo, + LoggerDebug, + LoggerTrace, + LoggerMaxLevel = LoggerTrace +}; + +inline LoggerLevel defaultLogLevel() { return LoggerInfo; } +QBS_EXPORT QString logLevelTag(LoggerLevel level); +QBS_EXPORT QString logLevelName(LoggerLevel level); + +class QBS_EXPORT ILogSink +{ + Q_DISABLE_COPY(ILogSink) +public: + ILogSink(); + virtual ~ILogSink(); + + void setLogLevel(LoggerLevel level); + LoggerLevel logLevel() const; + + bool willPrint(LoggerLevel level) const { return level <= logLevel(); } + + void printWarning(const ErrorInfo &warning); + void printMessage(LoggerLevel level, const QString &message, + const QString &tag = QString(), bool force = false); + +private: + virtual void doPrintWarning(const ErrorInfo &warning); + virtual void doPrintMessage(LoggerLevel level, const QString &message, + const QString &tag) = 0; + + class ILogSinkPrivate; + ILogSinkPrivate * const d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/logging/logger.cpp b/src/lib/corelib/logging/logger.cpp new file mode 100644 index 000000000..9f7a19c01 --- /dev/null +++ b/src/lib/corelib/logging/logger.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#if defined(_MSC_VER) && _MSC_VER > 0 +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "logger.h" + +#include <QByteArray> +#include <QElapsedTimer> +#include <QMutex> +#include <QSet> +#include <QVariant> + +#include <cstdarg> +#include <stdio.h> + +namespace qbs { +namespace Internal { + +LogWriter::LogWriter(ILogSink *logSink, LoggerLevel level, bool force) + : m_logSink(logSink), m_level(level), m_force(force) +{} + +LogWriter::LogWriter(const LogWriter &other) + : m_logSink(other.m_logSink) + , m_level(other.m_level) + , m_message(other.m_message) + , m_tag(other.m_tag) + , m_force(other.m_force) +{ + other.m_message.clear(); +} + +LogWriter::~LogWriter() +{ + if (!m_message.isEmpty()) + m_logSink->printMessage(m_level, m_message, m_tag, m_force); +} + +const LogWriter &LogWriter::operator=(const LogWriter &other) +{ + m_logSink = other.m_logSink; + m_level = other.m_level; + m_message = other.m_message; + m_tag = other.m_tag; + m_force = other.m_force; + other.m_message.clear(); + return *this; +} + +void LogWriter::write(char c) +{ + write(QLatin1Char(c)); +} + +void LogWriter::write(const char *str) +{ + write(QLatin1String(str)); +} + +void LogWriter::write(const QChar &c) +{ + if (m_force || m_logSink->logLevel() >= m_level) + m_message.append(c); +} + +void LogWriter::write(const QString &message) +{ + if (m_force || m_logSink->logLevel() >= m_level) + m_message += message; +} + +void LogWriter::setMessageTag(const QString &tag) +{ + m_tag = tag; +} + +LogWriter operator<<(LogWriter w, const char *str) +{ + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, const QByteArray &byteArray) +{ + w.write(byteArray.data()); + return w; +} + +LogWriter operator<<(LogWriter w, const QString &str) +{ + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, const QStringList &strList) +{ + w.write('['); + for (int i = 0; i < strList.size(); ++i) { + w.write(strList.at(i)); + if (i != strList.size() - 1) + w.write(QLatin1String(", ")); + } + w.write(']'); + return w; +} + +LogWriter operator<<(LogWriter w, const QSet<QString> &strSet) +{ + bool firstLoop = true; + w.write('('); + foreach (const QString &str, strSet) { + if (firstLoop) + firstLoop = false; + else + w.write(QLatin1String(", ")); + w.write(str); + } + w.write(')'); + return w; +} + +LogWriter operator<<(LogWriter w, const QVariant &variant) +{ + QString str = variant.typeName() + QLatin1Char('('); + if (variant.type() == QVariant::List) { + bool firstLoop = true; + foreach (const QVariant &item, variant.toList()) { + str += item.toString(); + if (firstLoop) + firstLoop = false; + else + str += QLatin1String(", "); + } + } else { + str += variant.toString(); + } + str += QLatin1Char(')'); + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, int n) +{ + return w << QString::number(n); +} + +LogWriter operator<<(LogWriter w, qint64 n) +{ + return w << QString::number(n); +} + +LogWriter operator<<(LogWriter w, bool b) +{ + return w << QString(QLatin1String(b ? "true" : "false")); +} + +LogWriter operator<<(LogWriter w, const MessageTag &tag) +{ + w.setMessageTag(tag.tag()); + return w; +} + +Logger::Logger(ILogSink *logger) : m_logSink(logger) +{ +} + +bool Logger::debugEnabled() const +{ + return m_logSink->willPrint(LoggerDebug); +} + +bool Logger::traceEnabled() const +{ + return m_logSink->willPrint(LoggerTrace); +} + +LogWriter Logger::qbsLog(LoggerLevel level, bool force) const +{ + return LogWriter(m_logSink, level, force); +} + + +class TimedActivityLogger::TimedActivityLoggerPrivate +{ +public: + Logger logger; + QString prefix; + QString activity; + LoggerLevel logLevel; + QElapsedTimer timer; + bool alwaysLog; +}; + +TimedActivityLogger::TimedActivityLogger(const Logger &logger, const QString &activity, + const QString &prefix, LoggerLevel logLevel, bool alwaysLog) + : d(0) +{ + if (!alwaysLog && !logger.logSink()->willPrint(logLevel)) + return; + d = new TimedActivityLoggerPrivate; + d->logger = logger; + d->prefix = prefix; + d->activity = activity; + d->logLevel = logLevel; + d->alwaysLog = alwaysLog; + d->logger.qbsLog(logLevel, alwaysLog) << QString::fromLocal8Bit("%1Starting activity '%2'.") + .arg(prefix, activity); + d->timer.start(); +} + +void TimedActivityLogger::finishActivity() +{ + if (!d) + return; + qint64 ms = d->timer.elapsed(); + qint64 s = ms/1000; + ms -= s*1000; + qint64 m = s/60; + s -= m*60; + const qint64 h = m/60; + m -= h*60; + QString timeString = QString::fromLocal8Bit("%1ms").arg(ms); + if (h || m || s) + timeString.prepend(QString::fromLocal8Bit("%1s, ").arg(s)); + if (h || m) + timeString.prepend(QString::fromLocal8Bit("%1m, ").arg(m)); + if (h) + timeString.prepend(QString::fromLocal8Bit("%1h, ").arg(h)); + d->logger.qbsLog(d->logLevel, d->alwaysLog) + << QString::fromLocal8Bit("%1Activity '%2' took %3.") + .arg(d->prefix, d->activity, timeString); + delete d; + d = 0; +} + +TimedActivityLogger::~TimedActivityLogger() +{ + finishActivity(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/logging/logger.h b/src/lib/corelib/logging/logger.h new file mode 100644 index 000000000..1ab11144d --- /dev/null +++ b/src/lib/corelib/logging/logger.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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_LOGGER_H +#define QBS_LOGGER_H + +#include "ilogsink.h" + +#include <QByteArray> +#include <QString> +#include <QStringList> + +QT_BEGIN_NAMESPACE +class QVariant; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +// Note that while these classes are not part of the API, we export some stuff for use by +// our command line tools for the sake of a uniform logging approach. + +class QBS_EXPORT LogWriter +{ +public: + LogWriter(ILogSink *logSink, LoggerLevel level, bool force = false); + + // log writer has move semantics and the last instance of + // a << chain prints the accumulated data + LogWriter(const LogWriter &other); + ~LogWriter(); + const LogWriter &operator=(const LogWriter &other); + + void write(char c); + void write(const char *str); + void write(const QChar &c); + void write(const QString &message); + + void setMessageTag(const QString &tag); + +private: + ILogSink *m_logSink; + LoggerLevel m_level; + mutable QString m_message; + QString m_tag; + bool m_force; +}; + +class QBS_EXPORT MessageTag +{ +public: + explicit MessageTag(const QString &tag) : m_tag(tag) {} + + const QString &tag() const { return m_tag; } + +private: + QString m_tag; +}; + +QBS_EXPORT LogWriter operator<<(LogWriter w, const char *str); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QByteArray &byteArray); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QString &str); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QStringList &strList); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QSet<QString> &strSet); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QVariant &variant); +QBS_EXPORT LogWriter operator<<(LogWriter w, int n); +QBS_EXPORT LogWriter operator<<(LogWriter w, qint64 n); +QBS_EXPORT LogWriter operator<<(LogWriter w, bool b); +QBS_EXPORT LogWriter operator<<(LogWriter w, const MessageTag &tag); + +class QBS_EXPORT Logger +{ +public: + Logger(ILogSink *logSink = 0); + + ILogSink *logSink() const { return m_logSink; } + + bool debugEnabled() const; + bool traceEnabled() const; + + void printWarning(const ErrorInfo &warning) { logSink()->printWarning(warning); } + + LogWriter qbsLog(LoggerLevel level, bool force = false) const; + LogWriter qbsWarning() const { return qbsLog(LoggerWarning); } + LogWriter qbsInfo() const { return qbsLog(LoggerInfo); } + LogWriter qbsDebug() const { return qbsLog(LoggerDebug); } + LogWriter qbsTrace() const { return qbsLog(LoggerTrace); } + +private: + ILogSink *m_logSink; +}; + + +class TimedActivityLogger +{ +public: + TimedActivityLogger(const Logger &logger, const QString &activity, + const QString &prefix = QString(), LoggerLevel logLevel = LoggerDebug, + bool alwaysLog = false); + void finishActivity(); + ~TimedActivityLogger(); + +private: + class TimedActivityLoggerPrivate; + TimedActivityLoggerPrivate *d; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOGGER_H diff --git a/src/lib/corelib/logging/logging.pri b/src/lib/corelib/logging/logging.pri new file mode 100644 index 000000000..8c0367779 --- /dev/null +++ b/src/lib/corelib/logging/logging.pri @@ -0,0 +1,14 @@ +HEADERS += \ + $$PWD/logger.h \ + $$PWD/translator.h \ + $$PWD/ilogsink.h + +SOURCES += \ + $$PWD/logger.cpp \ + $$PWD/ilogsink.cpp + +!qbs_no_dev_install { + logging_headers.files = $$PWD/ilogsink.h + logging_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/logging + INSTALLS += logging_headers +} diff --git a/src/lib/corelib/logging/translator.h b/src/lib/corelib/logging/translator.h new file mode 100644 index 000000000..5c1f5304b --- /dev/null +++ b/src/lib/corelib/logging/translator.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_TRANSLATOR_H +#define QBS_TRANSLATOR_H + +#include <tools/qbs_export.h> + +#include <QCoreApplication> + +namespace qbs { +namespace Internal { + +class QBS_EXPORT Tr // Name intended to be short. Exported for use by command line tools. +{ + Q_DECLARE_TR_FUNCTIONS(Qbs) +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_TRANSLATOR_H diff --git a/src/lib/corelib/parser/parser.pri b/src/lib/corelib/parser/parser.pri new file mode 100644 index 000000000..e6a8a5345 --- /dev/null +++ b/src/lib/corelib/parser/parser.pri @@ -0,0 +1,21 @@ +HEADERS += \ + $$PWD/qmljsast_p.h \ + $$PWD/qmljsastfwd_p.h \ + $$PWD/qmljsastvisitor_p.h \ + $$PWD/qmljsengine_p.h \ + $$PWD/qmljsgrammar_p.h \ + $$PWD/qmljslexer_p.h \ + $$PWD/qmljsmemorypool_p.h \ + $$PWD/qmljsparser_p.h \ + $$PWD/qmljsglobal_p.h \ + $$PWD/qmlerror.h \ + $$PWD/qmljskeywords_p.h \ + +SOURCES += \ + $$PWD/qmljsast.cpp \ + $$PWD/qmljsastvisitor.cpp \ + $$PWD/qmljsengine_p.cpp \ + $$PWD/qmljsgrammar.cpp \ + $$PWD/qmljslexer.cpp \ + $$PWD/qmljsparser.cpp \ + $$PWD/qmlerror.cpp \ diff --git a/src/lib/corelib/parser/qmlerror.cpp b/src/lib/corelib/parser/qmlerror.cpp new file mode 100644 index 000000000..b94fd1556 --- /dev/null +++ b/src/lib/corelib/parser/qmlerror.cpp @@ -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 Qt Creator. +** +** 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 "qmlerror.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qstringlist.h> + +namespace QbsQmlJS { + +/*! + \class QmlError + \since 5.0 + \inmodule QtQml + \brief The QmlError class encapsulates a QML error. + + QmlError includes a textual description of the error, as well + as location information (the file, line, and column). The toString() + method creates a single-line, human-readable string containing all of + this information, for example: + \code + file:///home/user/test.qml:7:8: Invalid property assignment: double expected + \endcode + + You can use qDebug() or qWarning() to output errors to the console. This method + will attempt to open the file indicated by the error + and include additional contextual information. + \code + file:///home/user/test.qml:7:8: Invalid property assignment: double expected + y: "hello" + ^ + \endcode + + Note that the QtQuick 1 version is named QDeclarativeError + + \sa QQuickView::errors(), QmlComponent::errors() +*/ +class QmlErrorPrivate +{ +public: + QmlErrorPrivate(); + + QUrl url; + QString description; + int line; + int column; +}; + +QmlErrorPrivate::QmlErrorPrivate() +: line(-1), column(-1) +{ +} + +/*! + Creates an empty error object. +*/ +QmlError::QmlError() +: d(0) +{ +} + +/*! + Creates a copy of \a other. +*/ +QmlError::QmlError(const QmlError &other) +: d(0) +{ + *this = other; +} + +/*! + Assigns \a other to this error object. +*/ +QmlError &QmlError::operator=(const QmlError &other) +{ + if (!other.d) { + delete d; + d = 0; + } else { + if (!d) d = new QmlErrorPrivate; + d->url = other.d->url; + d->description = other.d->description; + d->line = other.d->line; + d->column = other.d->column; + } + return *this; +} + +/*! + \internal +*/ +QmlError::~QmlError() +{ + delete d; d = 0; +} + +/*! + Returns true if this error is valid, otherwise false. +*/ +bool QmlError::isValid() const +{ + return d != 0; +} + +/*! + Returns the url for the file that caused this error. +*/ +QUrl QmlError::url() const +{ + if (d) return d->url; + else return QUrl(); +} + +/*! + Sets the \a url for the file that caused this error. +*/ +void QmlError::setUrl(const QUrl &url) +{ + if (!d) d = new QmlErrorPrivate; + d->url = url; +} + +/*! + Returns the error description. +*/ +QString QmlError::description() const +{ + if (d) return d->description; + else return QString(); +} + +/*! + Sets the error \a description. +*/ +void QmlError::setDescription(const QString &description) +{ + if (!d) d = new QmlErrorPrivate; + d->description = description; +} + +/*! + Returns the error line number. +*/ +int QmlError::line() const +{ + if (d) return d->line; + else return -1; +} + +/*! + Sets the error \a line number. +*/ +void QmlError::setLine(int line) +{ + if (!d) d = new QmlErrorPrivate; + d->line = line; +} + +/*! + Returns the error column number. +*/ +int QmlError::column() const +{ + if (d) return d->column; + else return -1; +} + +/*! + Sets the error \a column number. +*/ +void QmlError::setColumn(int column) +{ + if (!d) d = new QmlErrorPrivate; + d->column = column; +} + +/*! + Returns the error as a human readable string. +*/ +QString QmlError::toString() const +{ + QString rv; + if (url().isEmpty()) { + rv = QLatin1String("<Unknown File>"); + } else if (line() != -1) { + rv = url().toString() + QLatin1Char(':') + QString::number(line()); + if(column() != -1) + rv += QLatin1Char(':') + QString::number(column()); + } else { + rv = url().toString(); + } + + rv += QLatin1String(": ") + description(); + + return rv; +} + +} // namespace QbsQmlJS + +QT_BEGIN_NAMESPACE + +using namespace QbsQmlJS; + +/*! + \relates QmlError + \fn QDebug operator<<(QDebug debug, const QmlError &error) + + Outputs a human readable version of \a error to \a debug. +*/ + +QDebug operator<<(QDebug debug, const QmlError &error) +{ + debug << qPrintable(error.toString()); + + QUrl url = error.url(); + + if (error.line() > 0 && url.scheme() == QLatin1String("file")) { + QString file = url.toLocalFile(); + QFile f(file); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QTextStream stream(data, QIODevice::ReadOnly); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + const QString code = stream.readAll(); + const QStringList lines = code.split(QLatin1Char('\n')); + + if (lines.count() >= error.line()) { + const QString &line = lines.at(error.line() - 1); + debug << "\n " << qPrintable(line); + + if(error.column() > 0) { + int column = qMax(0, error.column() - 1); + column = qMin(column, line.length()); + + QByteArray ind; + ind.reserve(column); + for (int i = 0; i < column; ++i) { + const QChar ch = line.at(i); + if (ch.isSpace()) + ind.append(ch.unicode()); + else + ind.append(' '); + } + ind.append('^'); + debug << "\n " << ind.constData(); + } + } + } + } + return debug; +} + +QT_END_NAMESPACE diff --git a/src/lib/corelib/parser/qmlerror.h b/src/lib/corelib/parser/qmlerror.h new file mode 100644 index 000000000..bcc82b483 --- /dev/null +++ b/src/lib/corelib/parser/qmlerror.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QQMLERROR_H +#define QQMLERROR_H + + + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +namespace QbsQmlJS { + +class QmlErrorPrivate; +class QmlError +{ +public: + QmlError(); + QmlError(const QmlError &); + QmlError &operator=(const QmlError &); + ~QmlError(); + + bool isValid() const; + + QUrl url() const; + void setUrl(const QUrl &); + QString description() const; + void setDescription(const QString &); + int line() const; + void setLine(int); + int column() const; + void setColumn(int); + + QString toString() const; +private: + QmlErrorPrivate *d; +}; + +} // namespace QbsQmlJS + +QT_BEGIN_NAMESPACE +QDebug operator<<(QDebug debug, const QbsQmlJS::QmlError &error); +Q_DECLARE_TYPEINFO(QbsQmlJS::QmlError, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QQMLERROR_H diff --git a/src/lib/corelib/parser/qmljs.g b/src/lib/corelib/parser/qmljs.g new file mode 100644 index 000000000..a15002857 --- /dev/null +++ b/src/lib/corelib/parser/qmljs.g @@ -0,0 +1,3002 @@ +----------------------------------------------------------------------------- +-- +-- Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +-- Contact: http://www.qt-project.org/legal +-- +-- This file is part of Qt Creator. +-- +-- 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. +-- +----------------------------------------------------------------------------- + + + +%parser QmlJSGrammar +%decl qmljsparser_p.h +%impl qdeclarativejsparser.cpp +%expect 2 +%expect-rr 2 + +%token T_AND "&" T_AND_AND "&&" T_AND_EQ "&=" +%token T_BREAK "break" T_CASE "case" T_CATCH "catch" +%token T_COLON ":" T_COMMA "," T_CONTINUE "continue" +%token T_DEFAULT "default" T_DELETE "delete" T_DIVIDE_ "/" +%token T_DIVIDE_EQ "/=" T_DO "do" T_DOT "." +%token T_ELSE "else" T_EQ "=" T_EQ_EQ "==" +%token T_EQ_EQ_EQ "===" T_FINALLY "finally" T_FOR "for" +%token T_FUNCTION "function" T_GE ">=" T_GT ">" +%token T_GT_GT ">>" T_GT_GT_EQ ">>=" T_GT_GT_GT ">>>" +%token T_GT_GT_GT_EQ ">>>=" T_IDENTIFIER "identifier" T_IF "if" +%token T_IN "in" T_INSTANCEOF "instanceof" T_LBRACE "{" +%token T_LBRACKET "[" T_LE "<=" T_LPAREN "(" +%token T_LT "<" T_LT_LT "<<" T_LT_LT_EQ "<<=" +%token T_MINUS "-" T_MINUS_EQ "-=" T_MINUS_MINUS "--" +%token T_NEW "new" T_NOT "!" T_NOT_EQ "!=" +%token T_NOT_EQ_EQ "!==" T_NUMERIC_LITERAL "numeric literal" T_OR "|" +%token T_OR_EQ "|=" T_OR_OR "||" T_PLUS "+" +%token T_PLUS_EQ "+=" T_PLUS_PLUS "++" T_QUESTION "?" +%token T_RBRACE "}" T_RBRACKET "]" T_REMAINDER "%" +%token T_REMAINDER_EQ "%=" T_RETURN "return" T_RPAREN ")" +%token T_SEMICOLON ";" T_AUTOMATIC_SEMICOLON T_STAR "*" +%token T_STAR_EQ "*=" T_STRING_LITERAL "string literal" +%token T_PROPERTY "property" T_SIGNAL "signal" T_READONLY "readonly" +%token T_SWITCH "switch" T_THIS "this" T_THROW "throw" +%token T_TILDE "~" T_TRY "try" T_TYPEOF "typeof" +%token T_VAR "var" T_VOID "void" T_WHILE "while" +%token T_WITH "with" T_XOR "^" T_XOR_EQ "^=" +%token T_NULL "null" T_TRUE "true" T_FALSE "false" +%token T_CONST "const" +%token T_DEBUGGER "debugger" +%token T_RESERVED_WORD "reserved word" +%token T_MULTILINE_STRING_LITERAL "multiline string literal" +%token T_COMMENT "comment" + +--- context keywords. +%token T_PUBLIC "public" +%token T_IMPORT "import" +%token T_AS "as" +%token T_ON "on" + +%token T_ERROR + +--- feed tokens +%token T_FEED_UI_PROGRAM +%token T_FEED_UI_OBJECT_MEMBER +%token T_FEED_JS_STATEMENT +%token T_FEED_JS_EXPRESSION +%token T_FEED_JS_SOURCE_ELEMENT +%token T_FEED_JS_PROGRAM + +%nonassoc SHIFT_THERE +%nonassoc T_IDENTIFIER T_COLON T_SIGNAL T_PROPERTY T_READONLY +%nonassoc REDUCE_HERE + +%start TopLevel + +/./**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 <QtCore/QDebug> +#include <QtCore/QCoreApplication> + +#include <string.h> + +#include "qmljsengine_p.h" +#include "qmljslexer_p.h" +#include "qmljsast_p.h" +#include "qmljsmemorypool_p.h" + +./ + +/:/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +#ifndef QMLJSPARSER_P_H +#define QMLJSPARSER_P_H + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include "qmljsast_p.h" +#include "qmljsengine_p.h" + +#include <QtCore/QList> +#include <QtCore/QString> + +QT_QML_BEGIN_NAMESPACE + +namespace QmlJS { + +class Engine; + +class QML_PARSER_EXPORT Parser: protected $table +{ +public: + union Value { + int ival; + double dval; + AST::ArgumentList *ArgumentList; + AST::CaseBlock *CaseBlock; + AST::CaseClause *CaseClause; + AST::CaseClauses *CaseClauses; + AST::Catch *Catch; + AST::DefaultClause *DefaultClause; + AST::ElementList *ElementList; + AST::Elision *Elision; + AST::ExpressionNode *Expression; + AST::Finally *Finally; + AST::FormalParameterList *FormalParameterList; + AST::FunctionBody *FunctionBody; + AST::FunctionDeclaration *FunctionDeclaration; + AST::Node *Node; + AST::PropertyName *PropertyName; + AST::PropertyNameAndValueList *PropertyNameAndValueList; + AST::SourceElement *SourceElement; + AST::SourceElements *SourceElements; + AST::Statement *Statement; + AST::StatementList *StatementList; + AST::Block *Block; + AST::VariableDeclaration *VariableDeclaration; + AST::VariableDeclarationList *VariableDeclarationList; + + AST::UiProgram *UiProgram; + AST::UiImportList *UiImportList; + AST::UiImport *UiImport; + AST::UiParameterList *UiParameterList; + AST::UiPublicMember *UiPublicMember; + AST::UiObjectDefinition *UiObjectDefinition; + AST::UiObjectInitializer *UiObjectInitializer; + AST::UiObjectBinding *UiObjectBinding; + AST::UiScriptBinding *UiScriptBinding; + AST::UiArrayBinding *UiArrayBinding; + AST::UiObjectMember *UiObjectMember; + AST::UiObjectMemberList *UiObjectMemberList; + AST::UiArrayMemberList *UiArrayMemberList; + AST::UiQualifiedId *UiQualifiedId; + }; + +public: + Parser(Engine *engine); + ~Parser(); + + // parse a UI program + bool parse() { return parse(T_FEED_UI_PROGRAM); } + bool parseStatement() { return parse(T_FEED_JS_STATEMENT); } + bool parseExpression() { return parse(T_FEED_JS_EXPRESSION); } + bool parseSourceElement() { return parse(T_FEED_JS_SOURCE_ELEMENT); } + bool parseUiObjectMember() { return parse(T_FEED_UI_OBJECT_MEMBER); } + bool parseProgram() { return parse(T_FEED_JS_PROGRAM); } + + AST::UiProgram *ast() const + { return AST::cast<AST::UiProgram *>(program); } + + AST::Statement *statement() const + { + if (! program) + return 0; + + return program->statementCast(); + } + + AST::ExpressionNode *expression() const + { + if (! program) + return 0; + + return program->expressionCast(); + } + + AST::UiObjectMember *uiObjectMember() const + { + if (! program) + return 0; + + return program->uiObjectMemberCast(); + } + + AST::Node *rootNode() const + { return program; } + + QList<DiagnosticMessage> diagnosticMessages() const + { return diagnostic_messages; } + + inline DiagnosticMessage diagnosticMessage() const + { + foreach (const DiagnosticMessage &d, diagnostic_messages) { + if (d.kind != DiagnosticMessage::Warning) + return d; + } + + return DiagnosticMessage(); + } + + inline QString errorMessage() const + { return diagnosticMessage().message; } + + inline int errorLineNumber() const + { return diagnosticMessage().loc.startLine; } + + inline int errorColumnNumber() const + { return diagnosticMessage().loc.startColumn; } + +protected: + bool parse(int startToken); + + void reallocateStack(); + + inline Value &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline QStringRef &stringRef(int index) + { return string_stack [tos + index - 1]; } + + inline AST::SourceLocation &loc(int index) + { return location_stack [tos + index - 1]; } + + AST::UiQualifiedId *reparseAsQualifiedId(AST::ExpressionNode *expr); + +protected: + Engine *driver; + MemoryPool *pool; + int tos; + int stack_size; + Value *sym_stack; + int *state_stack; + AST::SourceLocation *location_stack; + QStringRef *string_stack; + + AST::Node *program; + + // error recovery + enum { TOKEN_BUFFER_SIZE = 3 }; + + struct SavedToken { + int token; + double dval; + AST::SourceLocation loc; + QStringRef spell; + }; + + double yylval; + QStringRef yytokenspell; + AST::SourceLocation yylloc; + AST::SourceLocation yyprevlloc; + + SavedToken token_buffer[TOKEN_BUFFER_SIZE]; + SavedToken *first_token; + SavedToken *last_token; + + QList<DiagnosticMessage> diagnostic_messages; +}; + +} // end of namespace QmlJS + + +:/ + + +/. + +#include "qmljsparser_p.h" +#include <QVarLengthArray> + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +using namespace QmlJS; + +QT_QML_BEGIN_NAMESPACE + +void Parser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack = reinterpret_cast<Value*> (realloc(sym_stack, stack_size * sizeof(Value))); + state_stack = reinterpret_cast<int*> (realloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast<AST::SourceLocation*> (realloc(location_stack, stack_size * sizeof(AST::SourceLocation))); + string_stack = reinterpret_cast<QStringRef*> (realloc(string_stack, stack_size * sizeof(QStringRef))); +} + +Parser::Parser(Engine *engine): + driver(engine), + pool(engine->pool()), + tos(0), + stack_size(0), + sym_stack(0), + state_stack(0), + location_stack(0), + string_stack(0), + first_token(0), + last_token(0) +{ +} + +Parser::~Parser() +{ + if (stack_size) { + free(sym_stack); + free(state_stack); + free(location_stack); + free(string_stack); + } +} + +static inline AST::SourceLocation location(Lexer *lexer) +{ + AST::SourceLocation loc; + loc.offset = lexer->tokenOffset(); + loc.length = lexer->tokenLength(); + loc.startLine = lexer->tokenStartLine(); + loc.startColumn = lexer->tokenStartColumn(); + return loc; +} + +AST::UiQualifiedId *Parser::reparseAsQualifiedId(AST::ExpressionNode *expr) +{ + QVarLengthArray<QStringRef, 4> nameIds; + QVarLengthArray<AST::SourceLocation, 4> locations; + + AST::ExpressionNode *it = expr; + while (AST::FieldMemberExpression *m = AST::cast<AST::FieldMemberExpression *>(it)) { + nameIds.append(m->name); + locations.append(m->identifierToken); + it = m->base; + } + + if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(it)) { + AST::UiQualifiedId *q = new (pool) AST::UiQualifiedId(idExpr->name); + q->identifierToken = idExpr->identifierToken; + + AST::UiQualifiedId *currentId = q; + for (int i = nameIds.size() - 1; i != -1; --i) { + currentId = new (pool) AST::UiQualifiedId(currentId, nameIds[i]); + currentId->identifierToken = locations[i]; + } + + return currentId->finish(); + } + + return 0; +} + +bool Parser::parse(int startToken) +{ + Lexer *lexer = driver->lexer(); + bool hadErrors = false; + int yytoken = -1; + int action = 0; + + token_buffer[0].token = startToken; + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + tos = -1; + program = 0; + + do { + if (++tos == stack_size) + reallocateStack(); + + state_stack[tos] = action; + + _Lcheck_token: + if (yytoken == -1 && -TERMINAL_COUNT != action_index[action]) { + yyprevlloc = yylloc; + + if (first_token == last_token) { + yytoken = lexer->lex(); + yylval = lexer->tokenValue(); + yytokenspell = lexer->tokenSpell(); + yylloc = location(lexer); + } else { + yytoken = first_token->token; + yylval = first_token->dval; + yytokenspell = first_token->spell; + yylloc = first_token->loc; + ++first_token; + } + } + + action = t_action(action, yytoken); + if (action > 0) { + if (action != ACCEPT_STATE) { + yytoken = -1; + sym(1).dval = yylval; + stringRef(1) = yytokenspell; + loc(1) = yylloc; + } else { + --tos; + return ! hadErrors; + } + } else if (action < 0) { + const int r = -action - 1; + tos -= rhs[r]; + + switch (r) { +./ + +-------------------------------------------------------------------------------------------------------- +-- Declarative UI +-------------------------------------------------------------------------------------------------------- + +TopLevel: T_FEED_UI_PROGRAM UiProgram ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_STATEMENT Statement ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_EXPRESSION Expression ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_SOURCE_ELEMENT SourceElement ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_UI_OBJECT_MEMBER UiObjectMember ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_PROGRAM Program ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +UiProgram: UiImportListOpt UiRootMember ; +/. +case $rule_number: { + sym(1).UiProgram = new (pool) AST::UiProgram(sym(1).UiImportList, + sym(2).UiObjectMemberList->finish()); +} break; +./ + +UiImportListOpt: Empty ; +UiImportListOpt: UiImportList ; +/. +case $rule_number: { + sym(1).Node = sym(1).UiImportList->finish(); +} break; +./ + +UiImportList: UiImport ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImport); +} break; +./ + +UiImportList: UiImportList UiImport ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImportList, sym(2).UiImport); +} break; +./ + +ImportId: MemberExpression ; + +UiImport: UiImportHead T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->semicolonToken = loc(2); +} break; +./ + +UiImport: UiImportHead T_NUMERIC_LITERAL T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_NUMERIC_LITERAL T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->semicolonToken = loc(3); +} break; +./ + +UiImport: UiImportHead T_NUMERIC_LITERAL T_AS JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_NUMERIC_LITERAL T_AS JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->asToken = loc(3); + sym(1).UiImport->importIdToken = loc(4); + sym(1).UiImport->importId = stringRef(4); + sym(1).UiImport->semicolonToken = loc(5); +} break; +./ + +UiImport: UiImportHead T_AS JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_AS JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->asToken = loc(2); + sym(1).UiImport->importIdToken = loc(3); + sym(1).UiImport->importId = stringRef(3); + sym(1).UiImport->semicolonToken = loc(4); +} break; +./ + + +UiImportHead: T_IMPORT ImportId ; +/. +case $rule_number: { + AST::UiImport *node = 0; + + if (AST::StringLiteral *importIdLiteral = AST::cast<AST::StringLiteral *>(sym(2).Expression)) { + node = new (pool) AST::UiImport(importIdLiteral->value); + node->fileNameToken = loc(2); + } else if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(2).Expression)) { + node = new (pool) AST::UiImport(qualifiedId); + node->fileNameToken = loc(2); + } + + sym(1).Node = node; + + if (node) { + node->importToken = loc(1); + } else { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id or a string literal"))); + + return false; // ### remove me + } +} break; +./ + +Empty: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +UiRootMember: UiObjectDefinition ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; +./ + +UiObjectMemberList: UiObjectMember ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; +./ + +UiObjectMemberList: UiObjectMemberList UiObjectMember ; +/. +case $rule_number: { + AST::UiObjectMemberList *node = new (pool) AST:: UiObjectMemberList( + sym(1).UiObjectMemberList, sym(2).UiObjectMember); + sym(1).Node = node; +} break; +./ + +UiArrayMemberList: UiObjectDefinition ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiArrayMemberList(sym(1).UiObjectMember); +} break; +./ + +UiArrayMemberList: UiArrayMemberList T_COMMA UiObjectDefinition ; +/. +case $rule_number: { + AST::UiArrayMemberList *node = new (pool) AST::UiArrayMemberList( + sym(1).UiArrayMemberList, sym(3).UiObjectMember); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectInitializer: T_LBRACE T_RBRACE ; +/. +case $rule_number: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer((AST::UiObjectMemberList*)0); + node->lbraceToken = loc(1); + node->rbraceToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectInitializer: T_LBRACE UiObjectMemberList T_RBRACE ; +/. +case $rule_number: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer(sym(2).UiObjectMemberList->finish()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiObjectDefinition: UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectDefinition *node = new (pool) AST::UiObjectDefinition(sym(1).UiQualifiedId, + sym(2).UiObjectInitializer); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiObjectDefinition ; + +UiObjectMember: UiQualifiedId T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET ; +/. +case $rule_number: { + AST::UiArrayBinding *node = new (pool) AST::UiArrayBinding( + sym(1).UiQualifiedId, sym(4).UiArrayMemberList->finish()); + node->colonToken = loc(2); + node->lbracketToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiQualifiedId T_COLON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(1).UiQualifiedId, sym(3).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiQualifiedId T_ON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(3).UiQualifiedId, sym(1).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + node->hasOnToken = true; + sym(1).Node = node; +} break; +./ + +UiScriptStatement: Block ; +UiScriptStatement: EmptyStatement ; +UiScriptStatement: ExpressionStatement ; +UiScriptStatement: IfStatement ; +UiScriptStatement: WithStatement ; +UiScriptStatement: SwitchStatement ; +UiScriptStatement: TryStatement ; + +UiObjectMember: UiQualifiedId T_COLON UiScriptStatement ; +/. +case $rule_number: +{ + AST::UiScriptBinding *node = new (pool) AST::UiScriptBinding( + sym(1).UiQualifiedId, sym(3).Statement); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiPropertyType: T_VAR ; +UiPropertyType: T_RESERVED_WORD ; +UiPropertyType: T_IDENTIFIER ; + +UiParameterListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +UiParameterListOpt: UiParameterList ; +/. +case $rule_number: { + sym(1).Node = sym(1).UiParameterList->finish (); +} break; +./ + +UiParameterList: UiPropertyType JsIdentifier ; +/. +case $rule_number: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(stringRef(1), stringRef(2)); + node->propertyTypeToken = loc(1); + node->identifierToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiParameterList: UiParameterList T_COMMA UiPropertyType JsIdentifier ; +/. +case $rule_number: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(sym(1).UiParameterList, stringRef(3), stringRef(4)); + node->commaToken = loc(2); + node->identifierToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_SIGNAL T_IDENTIFIER T_LPAREN UiParameterListOpt T_RPAREN T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_SIGNAL T_IDENTIFIER T_LPAREN UiParameterListOpt T_RPAREN T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->parameters = sym(4).UiParameterList; + node->semicolonToken = loc(6); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_SIGNAL T_IDENTIFIER T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_SIGNAL T_IDENTIFIER T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4)); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->semicolonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3), + sym(5).Statement); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_READONLY T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isReadonlyMember = true; + node->readonlyToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(6)); + propertyName->identifierToken = loc(6); + propertyName->next = 0; + + AST::UiArrayBinding *binding = new (pool) AST::UiArrayBinding( + propertyName, sym(9).UiArrayMemberList->finish()); + binding->colonToken = loc(7); + binding->lbracketToken = loc(8); + binding->rbracketToken = loc(10); + + node->binding = binding; + + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_COLON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(3)); + propertyName->identifierToken = loc(3); + propertyName->next = 0; + + AST::UiObjectBinding *binding = new (pool) AST::UiObjectBinding( + propertyName, sym(5).UiQualifiedId, sym(6).UiObjectInitializer); + binding->colonToken = loc(4); + + node->binding = binding; + + sym(1).Node = node; +} break; +./ + +UiObjectMember: FunctionDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; +./ + +UiObjectMember: VariableStatement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; +./ + +JsIdentifier: T_IDENTIFIER; + +JsIdentifier: T_PROPERTY ; +JsIdentifier: T_SIGNAL ; +JsIdentifier: T_READONLY ; +JsIdentifier: T_ON ; + +-------------------------------------------------------------------------------------------------------- +-- Expressions +-------------------------------------------------------------------------------------------------------- + +PrimaryExpression: T_THIS ; +/. +case $rule_number: { + AST::ThisExpression *node = new (pool) AST::ThisExpression(); + node->thisToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: JsIdentifier ; +/. +case $rule_number: { + AST::IdentifierExpression *node = new (pool) AST::IdentifierExpression(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_NULL ; +/. +case $rule_number: { + AST::NullExpression *node = new (pool) AST::NullExpression(); + node->nullToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_TRUE ; +/. +case $rule_number: { + AST::TrueLiteral *node = new (pool) AST::TrueLiteral(); + node->trueToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_FALSE ; +/. +case $rule_number: { + AST::FalseLiteral *node = new (pool) AST::FalseLiteral(); + node->falseToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_NUMERIC_LITERAL ; +/. +case $rule_number: { + AST::NumericLiteral *node = new (pool) AST::NumericLiteral(sym(1).dval); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_MULTILINE_STRING_LITERAL ; +/.case $rule_number:./ + +PrimaryExpression: T_STRING_LITERAL ; +/. +case $rule_number: { + AST::StringLiteral *node = new (pool) AST::StringLiteral(stringRef(1)); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_DIVIDE_ ; +/: +#define J_SCRIPT_REGEXPLITERAL_RULE1 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(Lexer::NoPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; // ### remove me + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_DIVIDE_EQ ; +/: +#define J_SCRIPT_REGEXPLITERAL_RULE2 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(Lexer::EqualPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral((AST::Elision *) 0); + node->lbracketToken = loc(1); + node->rbracketToken = loc(2); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET Elision T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).Elision->finish()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish ()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_COMMA T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + (AST::Elision *) 0); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_COMMA Elision T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + sym(4).Elision->finish()); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; +./ + +-- PrimaryExpression: T_LBRACE T_RBRACE ; +-- /. +-- case $rule_number: { +-- sym(1).Node = new (pool) AST::ObjectLiteral(); +-- } break; +-- ./ + +PrimaryExpression: T_LBRACE PropertyNameAndValueListOpt T_RBRACE ; +/. +case $rule_number: { + AST::ObjectLiteral *node = 0; + if (sym(2).Node) + node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + else + node = new (pool) AST::ObjectLiteral(); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACE PropertyNameAndValueList T_COMMA T_RBRACE ; +/. +case $rule_number: { + AST::ObjectLiteral *node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(4); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LPAREN Expression T_RPAREN ; +/. +case $rule_number: { + AST::NestedExpression *node = new (pool) AST::NestedExpression(sym(2).Expression); + node->lparenToken = loc(1); + node->rparenToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiQualifiedId: MemberExpression ; +/. +case $rule_number: { + if (AST::ArrayMemberExpression *mem = AST::cast<AST::ArrayMemberExpression *>(sym(1).Expression)) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, mem->lbracketToken, + QLatin1String("Ignored annotation"))); + + sym(1).Expression = mem->base; + } + + if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(1).Expression)) { + sym(1).UiQualifiedId = qualifiedId; + } else { + sym(1).UiQualifiedId = 0; + + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id"))); + + return false; // ### recover + } +} break; +./ + +ElementList: AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ElementList((AST::Elision *) 0, sym(1).Expression); +} break; +./ + +ElementList: Elision AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ElementList(sym(1).Elision->finish(), sym(2).Expression); +} break; +./ + +ElementList: ElementList T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, + (AST::Elision *) 0, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ElementList: ElementList T_COMMA Elision AssignmentExpression ; +/. +case $rule_number: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, sym(3).Elision->finish(), + sym(4).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +Elision: T_COMMA ; +/. +case $rule_number: { + AST::Elision *node = new (pool) AST::Elision(); + node->commaToken = loc(1); + sym(1).Node = node; +} break; +./ + +Elision: Elision T_COMMA ; +/. +case $rule_number: { + AST::Elision *node = new (pool) AST::Elision(sym(1).Elision); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +PropertyNameAndValueList: PropertyName T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyName, sym(3).Expression); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +PropertyNameAndValueList: PropertyNameAndValueList T_COMMA PropertyName T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyNameAndValueList, sym(3).PropertyName, sym(5).Expression); + node->commaToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +PropertyName: T_IDENTIFIER %prec SHIFT_THERE ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_SIGNAL ; +/.case $rule_number:./ + +PropertyName: T_PROPERTY ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_STRING_LITERAL ; +/. +case $rule_number: { + AST::StringLiteralPropertyName *node = new (pool) AST::StringLiteralPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_NUMERIC_LITERAL ; +/. +case $rule_number: { + AST::NumericLiteralPropertyName *node = new (pool) AST::NumericLiteralPropertyName(sym(1).dval); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: ReservedIdentifier ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +ReservedIdentifier: T_BREAK ; +ReservedIdentifier: T_CASE ; +ReservedIdentifier: T_CATCH ; +ReservedIdentifier: T_CONTINUE ; +ReservedIdentifier: T_DEFAULT ; +ReservedIdentifier: T_DELETE ; +ReservedIdentifier: T_DO ; +ReservedIdentifier: T_ELSE ; +ReservedIdentifier: T_FALSE ; +ReservedIdentifier: T_FINALLY ; +ReservedIdentifier: T_FOR ; +ReservedIdentifier: T_FUNCTION ; +ReservedIdentifier: T_IF ; +ReservedIdentifier: T_IN ; +ReservedIdentifier: T_INSTANCEOF ; +ReservedIdentifier: T_NEW ; +ReservedIdentifier: T_NULL ; +ReservedIdentifier: T_RETURN ; +ReservedIdentifier: T_SWITCH ; +ReservedIdentifier: T_THIS ; +ReservedIdentifier: T_THROW ; +ReservedIdentifier: T_TRUE ; +ReservedIdentifier: T_TRY ; +ReservedIdentifier: T_TYPEOF ; +ReservedIdentifier: T_VAR ; +ReservedIdentifier: T_VOID ; +ReservedIdentifier: T_WHILE ; +ReservedIdentifier: T_CONST ; +ReservedIdentifier: T_DEBUGGER ; +ReservedIdentifier: T_RESERVED_WORD ; +ReservedIdentifier: T_WITH ; + +PropertyIdentifier: JsIdentifier ; +PropertyIdentifier: ReservedIdentifier ; + +MemberExpression: PrimaryExpression ; +MemberExpression: FunctionExpression ; + +MemberExpression: MemberExpression T_LBRACKET Expression T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +MemberExpression: MemberExpression T_DOT PropertyIdentifier ; +/. +case $rule_number: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +MemberExpression: T_NEW MemberExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::NewMemberExpression *node = new (pool) AST::NewMemberExpression(sym(2).Expression, sym(4).ArgumentList); + node->newToken = loc(1); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + sym(1).Node = node; +} break; +./ + +NewExpression: MemberExpression ; + +NewExpression: T_NEW NewExpression ; +/. +case $rule_number: { + AST::NewExpression *node = new (pool) AST::NewExpression(sym(2).Expression); + node->newToken = loc(1); + sym(1).Node = node; +} break; +./ + +CallExpression: MemberExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_LBRACKET Expression T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_DOT PropertyIdentifier ; +/. +case $rule_number: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +ArgumentListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ArgumentListOpt: ArgumentList ; +/. +case $rule_number: { + sym(1).Node = sym(1).ArgumentList->finish(); +} break; +./ + +ArgumentList: AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ArgumentList(sym(1).Expression); +} break; +./ + +ArgumentList: ArgumentList T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::ArgumentList *node = new (pool) AST::ArgumentList(sym(1).ArgumentList, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +LeftHandSideExpression: NewExpression ; +LeftHandSideExpression: CallExpression ; +PostfixExpression: LeftHandSideExpression ; + +PostfixExpression: LeftHandSideExpression T_PLUS_PLUS ; +/. +case $rule_number: { + AST::PostIncrementExpression *node = new (pool) AST::PostIncrementExpression(sym(1).Expression); + node->incrementToken = loc(2); + sym(1).Node = node; +} break; +./ + +PostfixExpression: LeftHandSideExpression T_MINUS_MINUS ; +/. +case $rule_number: { + AST::PostDecrementExpression *node = new (pool) AST::PostDecrementExpression(sym(1).Expression); + node->decrementToken = loc(2); + sym(1).Node = node; +} break; +./ + +UnaryExpression: PostfixExpression ; + +UnaryExpression: T_DELETE UnaryExpression ; +/. +case $rule_number: { + AST::DeleteExpression *node = new (pool) AST::DeleteExpression(sym(2).Expression); + node->deleteToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_VOID UnaryExpression ; +/. +case $rule_number: { + AST::VoidExpression *node = new (pool) AST::VoidExpression(sym(2).Expression); + node->voidToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_TYPEOF UnaryExpression ; +/. +case $rule_number: { + AST::TypeOfExpression *node = new (pool) AST::TypeOfExpression(sym(2).Expression); + node->typeofToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_PLUS_PLUS UnaryExpression ; +/. +case $rule_number: { + AST::PreIncrementExpression *node = new (pool) AST::PreIncrementExpression(sym(2).Expression); + node->incrementToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_MINUS_MINUS UnaryExpression ; +/. +case $rule_number: { + AST::PreDecrementExpression *node = new (pool) AST::PreDecrementExpression(sym(2).Expression); + node->decrementToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_PLUS UnaryExpression ; +/. +case $rule_number: { + AST::UnaryPlusExpression *node = new (pool) AST::UnaryPlusExpression(sym(2).Expression); + node->plusToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_MINUS UnaryExpression ; +/. +case $rule_number: { + AST::UnaryMinusExpression *node = new (pool) AST::UnaryMinusExpression(sym(2).Expression); + node->minusToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_TILDE UnaryExpression ; +/. +case $rule_number: { + AST::TildeExpression *node = new (pool) AST::TildeExpression(sym(2).Expression); + node->tildeToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_NOT UnaryExpression ; +/. +case $rule_number: { + AST::NotExpression *node = new (pool) AST::NotExpression(sym(2).Expression); + node->notToken = loc(1); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: UnaryExpression ; + +MultiplicativeExpression: MultiplicativeExpression T_STAR UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mul, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: MultiplicativeExpression T_DIVIDE_ UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Div, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: MultiplicativeExpression T_REMAINDER UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mod, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AdditiveExpression: MultiplicativeExpression ; + +AdditiveExpression: AdditiveExpression T_PLUS MultiplicativeExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Add, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AdditiveExpression: AdditiveExpression T_MINUS MultiplicativeExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Sub, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: AdditiveExpression ; + +ShiftExpression: ShiftExpression T_LT_LT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::LShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: ShiftExpression T_GT_GT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::RShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: ShiftExpression T_GT_GT_GT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::URShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: ShiftExpression ; + +RelationalExpression: RelationalExpression T_LT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_GT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_LE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_GE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_INSTANCEOF ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_IN ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::In, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: ShiftExpression ; + +RelationalExpressionNotIn: RelationalExpressionNotIn T_LT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_GT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_LE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_GE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_INSTANCEOF ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: RelationalExpression ; + +EqualityExpression: EqualityExpression T_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_NOT_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_EQ_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_NOT_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: RelationalExpressionNotIn ; + +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ RelationalExpressionNotIn; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseANDExpression: EqualityExpression ; + +BitwiseANDExpression: BitwiseANDExpression T_AND EqualityExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseANDExpressionNotIn: EqualityExpressionNotIn ; + +BitwiseANDExpressionNotIn: BitwiseANDExpressionNotIn T_AND EqualityExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseXORExpression: BitwiseANDExpression ; + +BitwiseXORExpression: BitwiseXORExpression T_XOR BitwiseANDExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseXORExpressionNotIn: BitwiseANDExpressionNotIn ; + +BitwiseXORExpressionNotIn: BitwiseXORExpressionNotIn T_XOR BitwiseANDExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseORExpression: BitwiseXORExpression ; + +BitwiseORExpression: BitwiseORExpression T_OR BitwiseXORExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseORExpressionNotIn: BitwiseXORExpressionNotIn ; + +BitwiseORExpressionNotIn: BitwiseORExpressionNotIn T_OR BitwiseXORExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalANDExpression: BitwiseORExpression ; + +LogicalANDExpression: LogicalANDExpression T_AND_AND BitwiseORExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalANDExpressionNotIn: BitwiseORExpressionNotIn ; + +LogicalANDExpressionNotIn: LogicalANDExpressionNotIn T_AND_AND BitwiseORExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalORExpression: LogicalANDExpression ; + +LogicalORExpression: LogicalORExpression T_OR_OR LogicalANDExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalORExpressionNotIn: LogicalANDExpressionNotIn ; + +LogicalORExpressionNotIn: LogicalORExpressionNotIn T_OR_OR LogicalANDExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ConditionalExpression: LogicalORExpression ; + +ConditionalExpression: LogicalORExpression T_QUESTION AssignmentExpression T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +ConditionalExpressionNotIn: LogicalORExpressionNotIn ; + +ConditionalExpressionNotIn: LogicalORExpressionNotIn T_QUESTION AssignmentExpressionNotIn T_COLON AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +AssignmentExpression: ConditionalExpression ; + +AssignmentExpression: LeftHandSideExpression AssignmentOperator AssignmentExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AssignmentExpressionNotIn: ConditionalExpressionNotIn ; + +AssignmentExpressionNotIn: LeftHandSideExpression AssignmentOperator AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AssignmentOperator: T_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::Assign; +} break; +./ + +AssignmentOperator: T_STAR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceMul; +} break; +./ + +AssignmentOperator: T_DIVIDE_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceDiv; +} break; +./ + +AssignmentOperator: T_REMAINDER_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceMod; +} break; +./ + +AssignmentOperator: T_PLUS_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceAdd; +} break; +./ + +AssignmentOperator: T_MINUS_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceSub; +} break; +./ + +AssignmentOperator: T_LT_LT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceLeftShift; +} break; +./ + +AssignmentOperator: T_GT_GT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceRightShift; +} break; +./ + +AssignmentOperator: T_GT_GT_GT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceURightShift; +} break; +./ + +AssignmentOperator: T_AND_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceAnd; +} break; +./ + +AssignmentOperator: T_XOR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceXor; +} break; +./ + +AssignmentOperator: T_OR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceOr; +} break; +./ + +Expression: AssignmentExpression ; + +Expression: Expression T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ExpressionOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ExpressionOpt: Expression ; + +ExpressionNotIn: AssignmentExpressionNotIn ; + +ExpressionNotIn: ExpressionNotIn T_COMMA AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ExpressionNotInOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ExpressionNotInOpt: ExpressionNotIn ; + +Statement: Block ; +Statement: VariableStatement ; +Statement: EmptyStatement ; +Statement: ExpressionStatement ; +Statement: IfStatement ; +Statement: IterationStatement ; +Statement: ContinueStatement ; +Statement: BreakStatement ; +Statement: ReturnStatement ; +Statement: WithStatement ; +Statement: LabelledStatement ; +Statement: SwitchStatement ; +Statement: ThrowStatement ; +Statement: TryStatement ; +Statement: DebuggerStatement ; + + +Block: T_LBRACE StatementListOpt T_RBRACE ; +/. +case $rule_number: { + AST::Block *node = new (pool) AST::Block(sym(2).StatementList); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +StatementList: Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementList(sym(1).Statement); +} break; +./ + +StatementList: StatementList Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementList(sym(1).StatementList, sym(2).Statement); +} break; +./ + +StatementListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +StatementListOpt: StatementList ; +/. +case $rule_number: { + sym(1).Node = sym(1).StatementList->finish (); +} break; +./ + +VariableStatement: VariableDeclarationKind VariableDeclarationList T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +VariableStatement: VariableDeclarationKind VariableDeclarationList T_SEMICOLON ; +/. +case $rule_number: { + AST::VariableStatement *node = new (pool) AST::VariableStatement( + sym(2).VariableDeclarationList->finish (/*readOnly=*/sym(1).ival == T_CONST)); + node->declarationKindToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +VariableDeclarationKind: T_CONST ; +/. +case $rule_number: { + sym(1).ival = T_CONST; +} break; +./ + +VariableDeclarationKind: T_VAR ; +/. +case $rule_number: { + sym(1).ival = T_VAR; +} break; +./ + +VariableDeclarationList: VariableDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; +./ + +VariableDeclarationList: VariableDeclarationList T_COMMA VariableDeclaration ; +/. +case $rule_number: { + AST::VariableDeclarationList *node = new (pool) AST::VariableDeclarationList( + sym(1).VariableDeclarationList, sym(3).VariableDeclaration); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +VariableDeclarationListNotIn: VariableDeclarationNotIn ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; +./ + +VariableDeclarationListNotIn: VariableDeclarationListNotIn T_COMMA VariableDeclarationNotIn ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclarationList, sym(3).VariableDeclaration); +} break; +./ + +VariableDeclaration: JsIdentifier InitialiserOpt ; +/. +case $rule_number: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +VariableDeclarationNotIn: JsIdentifier InitialiserNotInOpt ; +/. +case $rule_number: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +Initialiser: T_EQ AssignmentExpression ; +/. +case $rule_number: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; +./ + +InitialiserOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +InitialiserOpt: Initialiser ; + +InitialiserNotIn: T_EQ AssignmentExpressionNotIn ; +/. +case $rule_number: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; +./ + +InitialiserNotInOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +InitialiserNotInOpt: InitialiserNotIn ; + +EmptyStatement: T_SEMICOLON ; +/. +case $rule_number: { + AST::EmptyStatement *node = new (pool) AST::EmptyStatement(); + node->semicolonToken = loc(1); + sym(1).Node = node; +} break; +./ + +ExpressionStatement: Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ExpressionStatement: Expression T_SEMICOLON ; +/. +case $rule_number: { + AST::ExpressionStatement *node = new (pool) AST::ExpressionStatement(sym(1).Expression); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement T_ELSE Statement ; +/. +case $rule_number: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement, sym(7).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->elseToken = loc(6); + sym(1).Node = node; +} break; +./ + +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + + +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_SEMICOLON ; +/. +case $rule_number: { + AST::DoWhileStatement *node = new (pool) AST::DoWhileStatement(sym(2).Statement, sym(5).Expression); + node->doToken = loc(1); + node->whileToken = loc(3); + node->lparenToken = loc(4); + node->rparenToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_WHILE T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::WhileStatement *node = new (pool) AST::WhileStatement(sym(3).Expression, sym(5).Statement); + node->whileToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN ExpressionNotInOpt T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +/. +case $rule_number: { + AST::ForStatement *node = new (pool) AST::ForStatement(sym(3).Expression, + sym(5).Expression, sym(7).Expression, sym(9).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->firstSemicolonToken = loc(4); + node->secondSemicolonToken = loc(6); + node->rparenToken = loc(8); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationListNotIn T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +/. +case $rule_number: { + AST::LocalForStatement *node = new (pool) AST::LocalForStatement( + sym(4).VariableDeclarationList->finish (/*readOnly=*/false), sym(6).Expression, + sym(8).Expression, sym(10).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->firstSemicolonToken = loc(5); + node->secondSemicolonToken = loc(7); + node->rparenToken = loc(9); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN LeftHandSideExpression T_IN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST:: ForEachStatement *node = new (pool) AST::ForEachStatement(sym(3).Expression, + sym(5).Expression, sym(7).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->inToken = loc(4); + node->rparenToken = loc(6); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationNotIn T_IN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::LocalForEachStatement *node = new (pool) AST::LocalForEachStatement( + sym(4).VariableDeclaration, sym(6).Expression, sym(8).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->inToken = loc(5); + node->rparenToken = loc(7); + sym(1).Node = node; +} break; +./ + +ContinueStatement: T_CONTINUE T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE T_SEMICOLON ; +/. +case $rule_number: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(); + node->continueToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +ContinueStatement: T_CONTINUE JsIdentifier T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(stringRef(2)); + node->continueToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +BreakStatement: T_BREAK T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK T_SEMICOLON ; +/. +case $rule_number: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(QStringRef()); + node->breakToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +BreakStatement: T_BREAK JsIdentifier T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(stringRef(2)); + node->breakToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +ReturnStatement: T_RETURN ExpressionOpt T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ReturnStatement: T_RETURN ExpressionOpt T_SEMICOLON ; +/. +case $rule_number: { + AST::ReturnStatement *node = new (pool) AST::ReturnStatement(sym(2).Expression); + node->returnToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +WithStatement: T_WITH T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::WithStatement *node = new (pool) AST::WithStatement(sym(3).Expression, sym(5).Statement); + node->withToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +SwitchStatement: T_SWITCH T_LPAREN Expression T_RPAREN CaseBlock ; +/. +case $rule_number: { + AST::SwitchStatement *node = new (pool) AST::SwitchStatement(sym(3).Expression, sym(5).CaseBlock); + node->switchToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CaseBlock: T_LBRACE CaseClausesOpt T_RBRACE ; +/. +case $rule_number: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +CaseBlock: T_LBRACE CaseClausesOpt DefaultClause CaseClausesOpt T_RBRACE ; +/. +case $rule_number: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses, sym(3).DefaultClause, sym(4).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(5); + sym(1).Node = node; +} break; +./ + +CaseClauses: CaseClause ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClause); +} break; +./ + +CaseClauses: CaseClauses CaseClause ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClauses, sym(2).CaseClause); +} break; +./ + +CaseClausesOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +CaseClausesOpt: CaseClauses ; +/. +case $rule_number: { + sym(1).Node = sym(1).CaseClauses->finish (); +} break; +./ + +CaseClause: T_CASE Expression T_COLON StatementListOpt ; +/. +case $rule_number: { + AST::CaseClause *node = new (pool) AST::CaseClause(sym(2).Expression, sym(4).StatementList); + node->caseToken = loc(1); + node->colonToken = loc(3); + sym(1).Node = node; +} break; +./ + +DefaultClause: T_DEFAULT T_COLON StatementListOpt ; +/. +case $rule_number: { + AST::DefaultClause *node = new (pool) AST::DefaultClause(sym(3).StatementList); + node->defaultToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +LabelledStatement: T_SIGNAL T_COLON Statement ; +/.case $rule_number:./ + +LabelledStatement: T_PROPERTY T_COLON Statement ; +/. +case $rule_number: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +LabelledStatement: T_IDENTIFIER T_COLON Statement ; +/. +case $rule_number: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +ThrowStatement: T_THROW Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ThrowStatement: T_THROW Expression T_SEMICOLON ; +/. +case $rule_number: { + AST::ThrowStatement *node = new (pool) AST::ThrowStatement(sym(2).Expression); + node->throwToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Catch ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Finally ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Catch Finally ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch, sym(4).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +Catch: T_CATCH T_LPAREN JsIdentifier T_RPAREN Block ; +/. +case $rule_number: { + AST::Catch *node = new (pool) AST::Catch(stringRef(3), sym(5).Block); + node->catchToken = loc(1); + node->lparenToken = loc(2); + node->identifierToken = loc(3); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +Finally: T_FINALLY Block ; +/. +case $rule_number: { + AST::Finally *node = new (pool) AST::Finally(sym(2).Block); + node->finallyToken = loc(1); + sym(1).Node = node; +} break; +./ + +DebuggerStatement: T_DEBUGGER T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +DebuggerStatement: T_DEBUGGER T_SEMICOLON ; +/. +case $rule_number: { + AST::DebuggerStatement *node = new (pool) AST::DebuggerStatement(); + node->debuggerToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +FunctionDeclaration: T_FUNCTION JsIdentifier T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +/. +case $rule_number: { + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; +./ + +FunctionExpression: T_FUNCTION IdentifierOpt T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +/. +case $rule_number: { + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + if (! stringRef(2).isNull()) + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; +./ + +FormalParameterList: JsIdentifier ; +/. +case $rule_number: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +FormalParameterList: FormalParameterList T_COMMA JsIdentifier ; +/. +case $rule_number: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(sym(1).FormalParameterList, stringRef(3)); + node->commaToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +FormalParameterListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +FormalParameterListOpt: FormalParameterList ; +/. +case $rule_number: { + sym(1).Node = sym(1).FormalParameterList->finish (); +} break; +./ + +FunctionBodyOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +FunctionBodyOpt: FunctionBody ; + +FunctionBody: SourceElements ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::FunctionBody(sym(1).SourceElements->finish ()); +} break; +./ + +Program: Empty ; + +Program: SourceElements ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::Program(sym(1).SourceElements->finish ()); +} break; +./ + +SourceElements: SourceElement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElement); +} break; +./ + +SourceElements: SourceElements SourceElement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElements, sym(2).SourceElement); +} break; +./ + +SourceElement: Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementSourceElement(sym(1).Statement); +} break; +./ + +SourceElement: FunctionDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::FunctionSourceElement(sym(1).FunctionDeclaration); +} break; +./ + +IdentifierOpt: ; +/. +case $rule_number: { + stringRef(1) = QStringRef(); +} break; +./ + +IdentifierOpt: JsIdentifier ; + +PropertyNameAndValueListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +PropertyNameAndValueListOpt: PropertyNameAndValueList ; + +/. + } // switch + action = nt_action(state_stack[tos], lhs[r] - TERMINAL_COUNT); + } // if + } while (action != 0); + + if (first_token == last_token) { + const int errorState = state_stack[tos]; + + // automatic insertion of `;' + if (yytoken != -1 && t_action(errorState, T_AUTOMATIC_SEMICOLON) && lexer->canInsertAutomaticSemicolon(yytoken)) { + SavedToken &tk = token_buffer[0]; + tk.token = yytoken; + tk.dval = yylval; + tk.spell = yytokenspell; + tk.loc = yylloc; + + yylloc = yyprevlloc; + yylloc.offset += yylloc.length; + yylloc.startColumn += yylloc.length; + yylloc.length = 0; + + //const QString msg = qApp->translate("QmlParser", "Missing `;'"); + //diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, yylloc, msg)); + + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + yytoken = T_SEMICOLON; + yylval = 0; + + action = errorState; + + goto _Lcheck_token; + } + + hadErrors = true; + + token_buffer[0].token = yytoken; + token_buffer[0].dval = yylval; + token_buffer[0].spell = yytokenspell; + token_buffer[0].loc = yylloc; + + token_buffer[1].token = yytoken = lexer->lex(); + token_buffer[1].dval = yylval = lexer->tokenValue(); + token_buffer[1].spell = yytokenspell = lexer->tokenSpell(); + token_buffer[1].loc = yylloc = location(lexer); + + if (t_action(errorState, yytoken)) { + QString msg; + int token = token_buffer[0].token; + if (token < 0 || token >= TERMINAL_COUNT) + msg = qApp->translate("QmlParser", "Syntax error"); + else + msg = qApp->translate("QmlParser", "Unexpected token `%1'").arg(QLatin1String(spell[token])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + action = errorState; + goto _Lcheck_token; + } + + static int tokens[] = { + T_PLUS, + T_EQ, + + T_COMMA, + T_COLON, + T_SEMICOLON, + + T_RPAREN, T_RBRACKET, T_RBRACE, + + T_NUMERIC_LITERAL, + T_IDENTIFIER, + + T_LPAREN, T_LBRACKET, T_LBRACE, + + EOF_SYMBOL + }; + + for (int *tk = tokens; *tk != EOF_SYMBOL; ++tk) { + int a = t_action(errorState, *tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[*tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = *tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + first_token = &token_buffer[0]; + last_token = &token_buffer[2]; + + action = errorState; + goto _Lcheck_token; + } + } + + for (int tk = 1; tk < TERMINAL_COUNT; ++tk) { + if (tk == T_AUTOMATIC_SEMICOLON || tk == T_FEED_UI_PROGRAM || + tk == T_FEED_JS_STATEMENT || tk == T_FEED_JS_EXPRESSION || + tk == T_FEED_JS_SOURCE_ELEMENT) + continue; + + int a = t_action(errorState, tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + action = errorState; + goto _Lcheck_token; + } + } + + const QString msg = qApp->translate("QmlParser", "Syntax error"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + } + + return false; +} + +QT_QML_END_NAMESPACE + + +./ +/: +QT_QML_END_NAMESPACE + + + +#endif // QMLJSPARSER_P_H +:/ diff --git a/src/lib/corelib/parser/qmljsast.cpp b/src/lib/corelib/parser/qmljsast.cpp new file mode 100644 index 000000000..3bf7ab968 --- /dev/null +++ b/src/lib/corelib/parser/qmljsast.cpp @@ -0,0 +1,915 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 "qmljsast_p.h" + +#include "qmljsastvisitor_p.h" + +namespace QbsQmlJS { +namespace AST { + +void Node::accept(Visitor *visitor) +{ + if (visitor->preVisit(this)) { + accept0(visitor); + } + visitor->postVisit(this); +} + +void Node::accept(Node *node, Visitor *visitor) +{ + if (node) + node->accept(visitor); +} + +ExpressionNode *Node::expressionCast() +{ + return 0; +} + +BinaryExpression *Node::binaryExpressionCast() +{ + return 0; +} + +Statement *Node::statementCast() +{ + return 0; +} + +UiObjectMember *Node::uiObjectMemberCast() +{ + return 0; +} + +ExpressionNode *ExpressionNode::expressionCast() +{ + return this; +} + +BinaryExpression *BinaryExpression::binaryExpressionCast() +{ + return this; +} + +Statement *Statement::statementCast() +{ + return this; +} + +UiObjectMember *UiObjectMember::uiObjectMemberCast() +{ + return this; +} + +void NestedExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + visitor->endVisit(this); +} + +void ThisExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void IdentifierExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NullExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void TrueLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void FalseLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void StringLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NumericLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void RegExpLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ArrayLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + accept(elision, visitor); + } + + visitor->endVisit(this); +} + +void ObjectLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(properties, visitor); + } + + visitor->endVisit(this); +} + +void ElementList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (ElementList *it = this; it; it = it->next) { + accept(it->elision, visitor); + accept(it->expression, visitor); + } + } + + visitor->endVisit(this); +} + +void Elision::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + // ### + } + + visitor->endVisit(this); +} + +void PropertyNameAndValueList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (PropertyNameAndValueList *it = this; it; it = it->next) { + accept(it->name, visitor); + accept(it->value, visitor); + } + } + + visitor->endVisit(this); +} + +void IdentifierPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void StringLiteralPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NumericLiteralPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ArrayMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void FieldMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void NewMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(arguments, visitor); + } + + visitor->endVisit(this); +} + +void NewExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void CallExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(arguments, visitor); + } + + visitor->endVisit(this); +} + +void ArgumentList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (ArgumentList *it = this; it; it = it->next) { + accept(it->expression, visitor); + } + } + + visitor->endVisit(this); +} + +void PostIncrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void PostDecrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void DeleteExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void VoidExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TypeOfExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void PreIncrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void PreDecrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void UnaryPlusExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void UnaryMinusExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TildeExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void NotExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void BinaryExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(left, visitor); + accept(right, visitor); + } + + visitor->endVisit(this); +} + +void ConditionalExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(ok, visitor); + accept(ko, visitor); + } + + visitor->endVisit(this); +} + +void Expression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(left, visitor); + accept(right, visitor); + } + + visitor->endVisit(this); +} + +void Block::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void StatementList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (StatementList *it = this; it; it = it->next) { + accept(it->statement, visitor); + } + } + + visitor->endVisit(this); +} + +void VariableStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declarations, visitor); + } + + visitor->endVisit(this); +} + +void VariableDeclarationList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (VariableDeclarationList *it = this; it; it = it->next) { + accept(it->declaration, visitor); + } + } + + visitor->endVisit(this); +} + +void VariableDeclaration::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void EmptyStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ExpressionStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void IfStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(ok, visitor); + accept(ko, visitor); + } + + visitor->endVisit(this); +} + +void DoWhileStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void WhileStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ForStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(initialiser, visitor); + accept(condition, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void LocalForStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declarations, visitor); + accept(condition, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ForEachStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(initialiser, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void LocalForEachStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declaration, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ContinueStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void BreakStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ReturnStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void WithStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void SwitchStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(block, visitor); + } + + visitor->endVisit(this); +} + +void CaseBlock::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(clauses, visitor); + accept(defaultClause, visitor); + accept(moreClauses, visitor); + } + + visitor->endVisit(this); +} + +void CaseClauses::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (CaseClauses *it = this; it; it = it->next) { + accept(it->clause, visitor); + } + } + + visitor->endVisit(this); +} + +void CaseClause::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void DefaultClause::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void LabelledStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ThrowStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TryStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(catchExpression, visitor); + accept(finallyExpression, visitor); + } + + visitor->endVisit(this); +} + +void Catch::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void Finally::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void FunctionDeclaration::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(formals, visitor); + accept(body, visitor); + } + + visitor->endVisit(this); +} + +void FunctionExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(formals, visitor); + accept(body, visitor); + } + + visitor->endVisit(this); +} + +void FormalParameterList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + // ### + } + + visitor->endVisit(this); +} + +void FunctionBody::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + } + + visitor->endVisit(this); +} + +void Program::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + } + + visitor->endVisit(this); +} + +void SourceElements::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (SourceElements *it = this; it; it = it->next) { + accept(it->element, visitor); + } + } + + visitor->endVisit(this); +} + +void FunctionSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declaration, visitor); + } + + visitor->endVisit(this); +} + +void StatementSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void DebuggerStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void UiProgram::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(imports, visitor); + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiPublicMember::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(binding, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectDefinition::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedTypeNameId, visitor); + accept(initializer, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectInitializer::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(qualifiedTypeNameId, visitor); + accept(initializer, visitor); + } + + visitor->endVisit(this); +} + +void UiScriptBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void UiArrayBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectMemberList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (UiObjectMemberList *it = this; it; it = it->next) + accept(it->member, visitor); + } + + visitor->endVisit(this); +} + +void UiArrayMemberList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (UiArrayMemberList *it = this; it; it = it->next) + accept(it->member, visitor); + } + + visitor->endVisit(this); +} + +void UiQualifiedId::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void UiImport::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(importUri, visitor); + } + + visitor->endVisit(this); +} + +void UiImportList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(import, visitor); + accept(next, visitor); + } + + visitor->endVisit(this); +} + +void UiSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(sourceElement, visitor); + } + + visitor->endVisit(this); +} + +} // namespace AST +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsast_p.h b/src/lib/corelib/parser/qmljsast_p.h new file mode 100644 index 000000000..3de1dd9f9 --- /dev/null +++ b/src/lib/corelib/parser/qmljsast_p.h @@ -0,0 +1,2623 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSAST_P_H +#define QMLJSAST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsastvisitor_p.h" +#include "qmljsglobal_p.h" +#include "qmljsmemorypool_p.h" + +#include <QtCore/QString> + +namespace QbsQmlJS { + +#define QMLJS_DECLARE_AST_NODE(name) \ + enum { K = Kind_##name }; + +namespace QSOperator // ### rename +{ + +enum Op { + Add, + And, + InplaceAnd, + Assign, + BitAnd, + BitOr, + BitXor, + InplaceSub, + Div, + InplaceDiv, + Equal, + Ge, + Gt, + In, + InplaceAdd, + InstanceOf, + Le, + LShift, + InplaceLeftShift, + Lt, + Mod, + InplaceMod, + Mul, + InplaceMul, + NotEqual, + Or, + InplaceOr, + RShift, + InplaceRightShift, + StrictEqual, + StrictNotEqual, + Sub, + URShift, + InplaceURightShift, + InplaceXor +}; + +} // namespace QSOperator + +namespace AST { + +template <typename _T1, typename _T2> +_T1 cast(_T2 *ast) +{ + if (ast && ast->kind == static_cast<_T1>(0)->K) + return static_cast<_T1>(ast); + + return 0; +} + +class QML_PARSER_EXPORT Node: public Managed +{ +public: + enum Kind { + Kind_Undefined, + + Kind_ArgumentList, + Kind_ArrayLiteral, + Kind_ArrayMemberExpression, + Kind_BinaryExpression, + Kind_Block, + Kind_BreakStatement, + Kind_CallExpression, + Kind_CaseBlock, + Kind_CaseClause, + Kind_CaseClauses, + Kind_Catch, + Kind_ConditionalExpression, + Kind_ContinueStatement, + Kind_DebuggerStatement, + Kind_DefaultClause, + Kind_DeleteExpression, + Kind_DoWhileStatement, + Kind_ElementList, + Kind_Elision, + Kind_EmptyStatement, + Kind_Expression, + Kind_ExpressionStatement, + Kind_FalseLiteral, + Kind_FieldMemberExpression, + Kind_Finally, + Kind_ForEachStatement, + Kind_ForStatement, + Kind_FormalParameterList, + Kind_FunctionBody, + Kind_FunctionDeclaration, + Kind_FunctionExpression, + Kind_FunctionSourceElement, + Kind_IdentifierExpression, + Kind_IdentifierPropertyName, + Kind_IfStatement, + Kind_LabelledStatement, + Kind_LocalForEachStatement, + Kind_LocalForStatement, + Kind_NewExpression, + Kind_NewMemberExpression, + Kind_NotExpression, + Kind_NullExpression, + Kind_NumericLiteral, + Kind_NumericLiteralPropertyName, + Kind_ObjectLiteral, + Kind_PostDecrementExpression, + Kind_PostIncrementExpression, + Kind_PreDecrementExpression, + Kind_PreIncrementExpression, + Kind_Program, + Kind_PropertyName, + Kind_PropertyNameAndValueList, + Kind_RegExpLiteral, + Kind_ReturnStatement, + Kind_SourceElement, + Kind_SourceElements, + Kind_StatementList, + Kind_StatementSourceElement, + Kind_StringLiteral, + Kind_StringLiteralPropertyName, + Kind_SwitchStatement, + Kind_ThisExpression, + Kind_ThrowStatement, + Kind_TildeExpression, + Kind_TrueLiteral, + Kind_TryStatement, + Kind_TypeOfExpression, + Kind_UnaryMinusExpression, + Kind_UnaryPlusExpression, + Kind_VariableDeclaration, + Kind_VariableDeclarationList, + Kind_VariableStatement, + Kind_VoidExpression, + Kind_WhileStatement, + Kind_WithStatement, + Kind_NestedExpression, + + Kind_UiArrayBinding, + Kind_UiImport, + Kind_UiImportList, + Kind_UiObjectBinding, + Kind_UiObjectDefinition, + Kind_UiObjectInitializer, + Kind_UiObjectMemberList, + Kind_UiArrayMemberList, + Kind_UiProgram, + Kind_UiParameterList, + Kind_UiPublicMember, + Kind_UiQualifiedId, + Kind_UiScriptBinding, + Kind_UiSourceElement + }; + + inline Node() + : kind(Kind_Undefined) {} + + // NOTE: node destructors are never called, + // instead we block free the memory + // (see the NodePool class) + virtual ~Node() {} + + virtual ExpressionNode *expressionCast(); + virtual BinaryExpression *binaryExpressionCast(); + virtual Statement *statementCast(); + virtual UiObjectMember *uiObjectMemberCast(); + + void accept(Visitor *visitor); + static void accept(Node *node, Visitor *visitor); + + inline static void acceptChild(Node *node, Visitor *visitor) + { return accept(node, visitor); } // ### remove + + virtual void accept0(Visitor *visitor) = 0; + virtual SourceLocation firstSourceLocation() const = 0; + virtual SourceLocation lastSourceLocation() const = 0; + +// attributes + int kind; +}; + +class QML_PARSER_EXPORT ExpressionNode: public Node +{ +public: + ExpressionNode() {} + + virtual ExpressionNode *expressionCast(); +}; + +class QML_PARSER_EXPORT Statement: public Node +{ +public: + Statement() {} + + virtual Statement *statementCast(); +}; + +class QML_PARSER_EXPORT NestedExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NestedExpression) + + NestedExpression(ExpressionNode *expression) + : expression(expression) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lparenToken; } + + virtual SourceLocation lastSourceLocation() const + { return rparenToken; } + +// attributes + ExpressionNode *expression; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ThisExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ThisExpression) + + ThisExpression() { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return thisToken; } + + virtual SourceLocation lastSourceLocation() const + { return thisToken; } + +// attributes + SourceLocation thisToken; +}; + +class QML_PARSER_EXPORT IdentifierExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(IdentifierExpression) + + IdentifierExpression(const QStringRef &n): + name (n) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return identifierToken; } + +// attributes + QStringRef name; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT NullExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NullExpression) + + NullExpression() { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return nullToken; } + + virtual SourceLocation lastSourceLocation() const + { return nullToken; } + +// attributes + SourceLocation nullToken; +}; + +class QML_PARSER_EXPORT TrueLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TrueLiteral) + + TrueLiteral() { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return trueToken; } + + virtual SourceLocation lastSourceLocation() const + { return trueToken; } + +// attributes + SourceLocation trueToken; +}; + +class QML_PARSER_EXPORT FalseLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FalseLiteral) + + FalseLiteral() { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return falseToken; } + + virtual SourceLocation lastSourceLocation() const + { return falseToken; } + +// attributes + SourceLocation falseToken; +}; + +class QML_PARSER_EXPORT NumericLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NumericLiteral) + + NumericLiteral(double v): + value(v) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return literalToken; } + + virtual SourceLocation lastSourceLocation() const + { return literalToken; } + +// attributes: + double value; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT StringLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(StringLiteral) + + StringLiteral(const QStringRef &v): + value (v) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return literalToken; } + + virtual SourceLocation lastSourceLocation() const + { return literalToken; } + +// attributes: + QStringRef value; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT RegExpLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(RegExpLiteral) + + RegExpLiteral(const QStringRef &p, int f): + pattern (p), flags (f) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return literalToken; } + + virtual SourceLocation lastSourceLocation() const + { return literalToken; } + +// attributes: + QStringRef pattern; + int flags; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT ArrayLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ArrayLiteral) + + ArrayLiteral(Elision *e): + elements (0), elision (e) + { kind = K; } + + ArrayLiteral(ElementList *elts): + elements (elts), elision (0) + { kind = K; } + + ArrayLiteral(ElementList *elts, Elision *e): + elements (elts), elision (e) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lbracketToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbracketToken; } + +// attributes + ElementList *elements; + Elision *elision; + SourceLocation lbracketToken; + SourceLocation commaToken; + SourceLocation rbracketToken; +}; + +class QML_PARSER_EXPORT ObjectLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ObjectLiteral) + + ObjectLiteral(): + properties (0) { kind = K; } + + ObjectLiteral(PropertyNameAndValueList *plist): + properties (plist) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lbraceToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbraceToken; } + +// attributes + PropertyNameAndValueList *properties; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT Elision: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Elision) + + Elision(): + next (this) { kind = K; } + + Elision(Elision *previous) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return commaToken; } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : commaToken; } + + inline Elision *finish () + { + Elision *front = next; + next = 0; + return front; + } + +// attributes + Elision *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT ElementList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(ElementList) + + ElementList(Elision *e, ExpressionNode *expr): + elision (e), expression (expr), next (this) + { kind = K; } + + ElementList(ElementList *previous, Elision *e, ExpressionNode *expr): + elision (e), expression (expr) + { + kind = K; + next = previous->next; + previous->next = this; + } + + inline ElementList *finish () + { + ElementList *front = next; + next = 0; + return front; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { + if (elision) + return elision->firstSourceLocation(); + return expression->firstSourceLocation(); + } + + virtual SourceLocation lastSourceLocation() const + { + if (next) + return next->lastSourceLocation(); + return expression->lastSourceLocation(); + } + +// attributes + Elision *elision; + ExpressionNode *expression; + ElementList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT PropertyName: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(PropertyName) + + PropertyName() { kind = K; } + + virtual SourceLocation firstSourceLocation() const + { return propertyNameToken; } + + virtual SourceLocation lastSourceLocation() const + { return propertyNameToken; } + +// attributes + SourceLocation propertyNameToken; +}; + +class QML_PARSER_EXPORT PropertyNameAndValueList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(PropertyNameAndValueList) + + PropertyNameAndValueList(PropertyName *n, ExpressionNode *v): + name (n), value (v), next (this) + { kind = K; } + + PropertyNameAndValueList(PropertyNameAndValueList *previous, PropertyName *n, ExpressionNode *v): + name (n), value (v) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return name->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { + if (next) + return next->lastSourceLocation(); + return value->lastSourceLocation(); + } + + inline PropertyNameAndValueList *finish () + { + PropertyNameAndValueList *front = next; + next = 0; + return front; + } + +// attributes + PropertyName *name; + ExpressionNode *value; + PropertyNameAndValueList *next; + SourceLocation colonToken; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT IdentifierPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(IdentifierPropertyName) + + IdentifierPropertyName(const QStringRef &n): + id (n) { kind = K; } + + virtual void accept0(Visitor *visitor); + +// attributes + QStringRef id; +}; + +class QML_PARSER_EXPORT StringLiteralPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(StringLiteralPropertyName) + + StringLiteralPropertyName(const QStringRef &n): + id (n) { kind = K; } + + virtual void accept0(Visitor *visitor); + +// attributes + QStringRef id; +}; + +class QML_PARSER_EXPORT NumericLiteralPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(NumericLiteralPropertyName) + + NumericLiteralPropertyName(double n): + id (n) { kind = K; } + + virtual void accept0(Visitor *visitor); + +// attributes + double id; +}; + +class QML_PARSER_EXPORT ArrayMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ArrayMemberExpression) + + ArrayMemberExpression(ExpressionNode *b, ExpressionNode *e): + base (b), expression (e) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return base->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return rbracketToken; } + +// attributes + ExpressionNode *base; + ExpressionNode *expression; + SourceLocation lbracketToken; + SourceLocation rbracketToken; +}; + +class QML_PARSER_EXPORT FieldMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FieldMemberExpression) + + FieldMemberExpression(ExpressionNode *b, const QStringRef &n): + base (b), name (n) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return base->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return identifierToken; } + + // attributes + ExpressionNode *base; + QStringRef name; + SourceLocation dotToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT NewMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NewMemberExpression) + + NewMemberExpression(ExpressionNode *b, ArgumentList *a): + base (b), arguments (a) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return newToken; } + + virtual SourceLocation lastSourceLocation() const + { return rparenToken; } + + // attributes + ExpressionNode *base; + ArgumentList *arguments; + SourceLocation newToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT NewExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NewExpression) + + NewExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return newToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation newToken; +}; + +class QML_PARSER_EXPORT CallExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(CallExpression) + + CallExpression(ExpressionNode *b, ArgumentList *a): + base (b), arguments (a) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return base->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return rparenToken; } + +// attributes + ExpressionNode *base; + ArgumentList *arguments; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ArgumentList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(ArgumentList) + + ArgumentList(ExpressionNode *e): + expression (e), next (this) + { kind = K; } + + ArgumentList(ArgumentList *previous, ExpressionNode *e): + expression (e) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return expression->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { + if (next) + return next->lastSourceLocation(); + return expression->lastSourceLocation(); + } + + inline ArgumentList *finish () + { + ArgumentList *front = next; + next = 0; + return front; + } + +// attributes + ExpressionNode *expression; + ArgumentList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT PostIncrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PostIncrementExpression) + + PostIncrementExpression(ExpressionNode *b): + base (b) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return base->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return incrementToken; } + +// attributes + ExpressionNode *base; + SourceLocation incrementToken; +}; + +class QML_PARSER_EXPORT PostDecrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PostDecrementExpression) + + PostDecrementExpression(ExpressionNode *b): + base (b) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return base->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return decrementToken; } + +// attributes + ExpressionNode *base; + SourceLocation decrementToken; +}; + +class QML_PARSER_EXPORT DeleteExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(DeleteExpression) + + DeleteExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return deleteToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation deleteToken; +}; + +class QML_PARSER_EXPORT VoidExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(VoidExpression) + + VoidExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return voidToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation voidToken; +}; + +class QML_PARSER_EXPORT TypeOfExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TypeOfExpression) + + TypeOfExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return typeofToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation typeofToken; +}; + +class QML_PARSER_EXPORT PreIncrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PreIncrementExpression) + + PreIncrementExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return incrementToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation incrementToken; +}; + +class QML_PARSER_EXPORT PreDecrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PreDecrementExpression) + + PreDecrementExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return decrementToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation decrementToken; +}; + +class QML_PARSER_EXPORT UnaryPlusExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(UnaryPlusExpression) + + UnaryPlusExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return plusToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation plusToken; +}; + +class QML_PARSER_EXPORT UnaryMinusExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(UnaryMinusExpression) + + UnaryMinusExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return minusToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation minusToken; +}; + +class QML_PARSER_EXPORT TildeExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TildeExpression) + + TildeExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return tildeToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation tildeToken; +}; + +class QML_PARSER_EXPORT NotExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NotExpression) + + NotExpression(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return notToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation notToken; +}; + +class QML_PARSER_EXPORT BinaryExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(BinaryExpression) + + BinaryExpression(ExpressionNode *l, int o, ExpressionNode *r): + left (l), op (o), right (r) + { kind = K; } + + virtual BinaryExpression *binaryExpressionCast(); + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return left->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return right->lastSourceLocation(); } + +// attributes + ExpressionNode *left; + int op; + ExpressionNode *right; + SourceLocation operatorToken; +}; + +class QML_PARSER_EXPORT ConditionalExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ConditionalExpression) + + ConditionalExpression(ExpressionNode *e, ExpressionNode *t, ExpressionNode *f): + expression (e), ok (t), ko (f) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return expression->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return ko->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + ExpressionNode *ok; + ExpressionNode *ko; + SourceLocation questionToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT Expression: public ExpressionNode // ### rename +{ +public: + QMLJS_DECLARE_AST_NODE(Expression) + + Expression(ExpressionNode *l, ExpressionNode *r): + left (l), right (r) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return left->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return right->lastSourceLocation(); } + +// attributes + ExpressionNode *left; + ExpressionNode *right; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT Block: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(Block) + + Block(StatementList *slist): + statements (slist) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lbraceToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbraceToken; } + + // attributes + StatementList *statements; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT StatementList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(StatementList) + + StatementList(Statement *stmt): + statement (stmt), next (this) + { kind = K; } + + StatementList(StatementList *previous, Statement *stmt): + statement (stmt) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return statement->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : statement->lastSourceLocation(); } + + inline StatementList *finish () + { + StatementList *front = next; + next = 0; + return front; + } + +// attributes + Statement *statement; + StatementList *next; +}; + +class QML_PARSER_EXPORT VariableStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(VariableStatement) + + VariableStatement(VariableDeclarationList *vlist): + declarations (vlist) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return declarationKindToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + VariableDeclarationList *declarations; + SourceLocation declarationKindToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT VariableDeclaration: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(VariableDeclaration) + + VariableDeclaration(const QStringRef &n, ExpressionNode *e): + name (n), expression (e), readOnly(false) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return expression ? expression->lastSourceLocation() : identifierToken; } + +// attributes + QStringRef name; + ExpressionNode *expression; + bool readOnly; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT VariableDeclarationList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(VariableDeclarationList) + + VariableDeclarationList(VariableDeclaration *decl): + declaration (decl), next (this) + { kind = K; } + + VariableDeclarationList(VariableDeclarationList *previous, VariableDeclaration *decl): + declaration (decl) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return declaration->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { + if (next) + return next->lastSourceLocation(); + return declaration->lastSourceLocation(); + } + + inline VariableDeclarationList *finish (bool readOnly) + { + VariableDeclarationList *front = next; + next = 0; + if (readOnly) { + VariableDeclarationList *vdl; + for (vdl = front; vdl != 0; vdl = vdl->next) + vdl->declaration->readOnly = true; + } + return front; + } + +// attributes + VariableDeclaration *declaration; + VariableDeclarationList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT EmptyStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(EmptyStatement) + + EmptyStatement() { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return semicolonToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT ExpressionStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ExpressionStatement) + + ExpressionStatement(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return expression->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + ExpressionNode *expression; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT IfStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(IfStatement) + + IfStatement(ExpressionNode *e, Statement *t, Statement *f = 0): + expression (e), ok (t), ko (f) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return ifToken; } + + virtual SourceLocation lastSourceLocation() const + { + if (ko) + return ko->lastSourceLocation(); + + return ok->lastSourceLocation(); + } + +// attributes + ExpressionNode *expression; + Statement *ok; + Statement *ko; + SourceLocation ifToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation elseToken; +}; + +class QML_PARSER_EXPORT DoWhileStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(DoWhileStatement) + + DoWhileStatement(Statement *stmt, ExpressionNode *e): + statement (stmt), expression (e) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return doToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + Statement *statement; + ExpressionNode *expression; + SourceLocation doToken; + SourceLocation whileToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT WhileStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(WhileStatement) + + WhileStatement(ExpressionNode *e, Statement *stmt): + expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return whileToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + Statement *statement; + SourceLocation whileToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ForStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ForStatement) + + ForStatement(ExpressionNode *i, ExpressionNode *c, ExpressionNode *e, Statement *stmt): + initialiser (i), condition (c), expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return forToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *initialiser; + ExpressionNode *condition; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation firstSemicolonToken; + SourceLocation secondSemicolonToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT LocalForStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LocalForStatement) + + LocalForStatement(VariableDeclarationList *vlist, ExpressionNode *c, ExpressionNode *e, Statement *stmt): + declarations (vlist), condition (c), expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return forToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + VariableDeclarationList *declarations; + ExpressionNode *condition; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation varToken; + SourceLocation firstSemicolonToken; + SourceLocation secondSemicolonToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ForEachStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ForEachStatement) + + ForEachStatement(ExpressionNode *i, ExpressionNode *e, Statement *stmt): + initialiser (i), expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return forToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *initialiser; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation inToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT LocalForEachStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LocalForEachStatement) + + LocalForEachStatement(VariableDeclaration *v, ExpressionNode *e, Statement *stmt): + declaration (v), expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return forToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + VariableDeclaration *declaration; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation varToken; + SourceLocation inToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ContinueStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ContinueStatement) + + ContinueStatement(const QStringRef &l = QStringRef()): + label (l) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return continueToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + QStringRef label; + SourceLocation continueToken; + SourceLocation identifierToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT BreakStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(BreakStatement) + + BreakStatement(const QStringRef &l): + label (l) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return breakToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + + // attributes + QStringRef label; + SourceLocation breakToken; + SourceLocation identifierToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT ReturnStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ReturnStatement) + + ReturnStatement(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return returnToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + ExpressionNode *expression; + SourceLocation returnToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT WithStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(WithStatement) + + WithStatement(ExpressionNode *e, Statement *stmt): + expression (e), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return withToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + Statement *statement; + SourceLocation withToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT CaseBlock: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseBlock) + + CaseBlock(CaseClauses *c, DefaultClause *d = 0, CaseClauses *r = 0): + clauses (c), defaultClause (d), moreClauses (r) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lbraceToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbraceToken; } + +// attributes + CaseClauses *clauses; + DefaultClause *defaultClause; + CaseClauses *moreClauses; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT SwitchStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(SwitchStatement) + + SwitchStatement(ExpressionNode *e, CaseBlock *b): + expression (e), block (b) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return switchToken; } + + virtual SourceLocation lastSourceLocation() const + { return block->rbraceToken; } + +// attributes + ExpressionNode *expression; + CaseBlock *block; + SourceLocation switchToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT CaseClause: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseClause) + + CaseClause(ExpressionNode *e, StatementList *slist): + expression (e), statements (slist) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return caseToken; } + + virtual SourceLocation lastSourceLocation() const + { return statements ? statements->lastSourceLocation() : colonToken; } + +// attributes + ExpressionNode *expression; + StatementList *statements; + SourceLocation caseToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT CaseClauses: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseClauses) + + CaseClauses(CaseClause *c): + clause (c), next (this) + { kind = K; } + + CaseClauses(CaseClauses *previous, CaseClause *c): + clause (c) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return clause->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : clause->lastSourceLocation(); } + + inline CaseClauses *finish () + { + CaseClauses *front = next; + next = 0; + return front; + } + +//attributes + CaseClause *clause; + CaseClauses *next; +}; + +class QML_PARSER_EXPORT DefaultClause: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(DefaultClause) + + DefaultClause(StatementList *slist): + statements (slist) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return defaultToken; } + + virtual SourceLocation lastSourceLocation() const + { return statements ? statements->lastSourceLocation() : colonToken; } + +// attributes + StatementList *statements; + SourceLocation defaultToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT LabelledStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LabelledStatement) + + LabelledStatement(const QStringRef &l, Statement *stmt): + label (l), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + QStringRef label; + Statement *statement; + SourceLocation identifierToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT ThrowStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ThrowStatement) + + ThrowStatement(ExpressionNode *e): + expression (e) { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return throwToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + + // attributes + ExpressionNode *expression; + SourceLocation throwToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT Catch: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Catch) + + Catch(const QStringRef &n, Block *stmt): + name (n), statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return catchToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + QStringRef name; + Block *statement; + SourceLocation catchToken; + SourceLocation lparenToken; + SourceLocation identifierToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT Finally: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Finally) + + Finally(Block *stmt): + statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return finallyToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement ? statement->lastSourceLocation() : finallyToken; } + +// attributes + Block *statement; + SourceLocation finallyToken; +}; + +class QML_PARSER_EXPORT TryStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(TryStatement) + + TryStatement(Statement *stmt, Catch *c, Finally *f): + statement (stmt), catchExpression (c), finallyExpression (f) + { kind = K; } + + TryStatement(Statement *stmt, Finally *f): + statement (stmt), catchExpression (0), finallyExpression (f) + { kind = K; } + + TryStatement(Statement *stmt, Catch *c): + statement (stmt), catchExpression (c), finallyExpression (0) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return tryToken; } + + virtual SourceLocation lastSourceLocation() const + { + if (finallyExpression) + return finallyExpression->statement->rbraceToken; + else if (catchExpression) + return catchExpression->statement->rbraceToken; + + return statement->lastSourceLocation(); + } + +// attributes + Statement *statement; + Catch *catchExpression; + Finally *finallyExpression; + SourceLocation tryToken; +}; + +class QML_PARSER_EXPORT FunctionExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionExpression) + + FunctionExpression(const QStringRef &n, FormalParameterList *f, FunctionBody *b): + name (n), formals (f), body (b) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return functionToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbraceToken; } + +// attributes + QStringRef name; + FormalParameterList *formals; + FunctionBody *body; + SourceLocation functionToken; + SourceLocation identifierToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT FunctionDeclaration: public FunctionExpression +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionDeclaration) + + FunctionDeclaration(const QStringRef &n, FormalParameterList *f, FunctionBody *b): + FunctionExpression(n, f, b) + { kind = K; } + + virtual void accept0(Visitor *visitor); +}; + +class QML_PARSER_EXPORT FormalParameterList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(FormalParameterList) + + FormalParameterList(const QStringRef &n): + name (n), next (this) + { kind = K; } + + FormalParameterList(FormalParameterList *previous, const QStringRef &n): + name (n) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : identifierToken; } + + inline FormalParameterList *finish () + { + FormalParameterList *front = next; + next = 0; + return front; + } + +// attributes + QStringRef name; + FormalParameterList *next; + SourceLocation commaToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT SourceElement: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(SourceElement) + + inline SourceElement() + { kind = K; } +}; + +class QML_PARSER_EXPORT SourceElements: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(SourceElements) + + SourceElements(SourceElement *elt): + element (elt), next (this) + { kind = K; } + + SourceElements(SourceElements *previous, SourceElement *elt): + element (elt) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return element->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : element->lastSourceLocation(); } + + inline SourceElements *finish () + { + SourceElements *front = next; + next = 0; + return front; + } + +// attributes + SourceElement *element; + SourceElements *next; +}; + +class QML_PARSER_EXPORT FunctionBody: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionBody) + + FunctionBody(SourceElements *elts): + elements (elts) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return elements ? elements->firstSourceLocation() : SourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return elements ? elements->lastSourceLocation() : SourceLocation(); } + +// attributes + SourceElements *elements; +}; + +class QML_PARSER_EXPORT Program: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Program) + + Program(SourceElements *elts): + elements (elts) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return elements ? elements->firstSourceLocation() : SourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return elements ? elements->lastSourceLocation() : SourceLocation(); } + +// attributes + SourceElements *elements; +}; + +class QML_PARSER_EXPORT FunctionSourceElement: public SourceElement +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionSourceElement) + + FunctionSourceElement(FunctionDeclaration *f): + declaration (f) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return declaration->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return declaration->lastSourceLocation(); } + +// attributes + FunctionDeclaration *declaration; +}; + +class QML_PARSER_EXPORT StatementSourceElement: public SourceElement +{ +public: + QMLJS_DECLARE_AST_NODE(StatementSourceElement) + + StatementSourceElement(Statement *stmt): + statement (stmt) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return statement->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + +// attributes + Statement *statement; +}; + +class QML_PARSER_EXPORT DebuggerStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(DebuggerStatement) + + DebuggerStatement() + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return debuggerToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + SourceLocation debuggerToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiQualifiedId: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiQualifiedId) + + UiQualifiedId(const QStringRef &name) + : next(this), name(name) + { kind = K; } + + UiQualifiedId(UiQualifiedId *previous, const QStringRef &name) + : name(name) + { + kind = K; + next = previous->next; + previous->next = this; + } + + UiQualifiedId *finish() + { + UiQualifiedId *head = next; + next = 0; + return head; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : identifierToken; } + +// attributes + UiQualifiedId *next; + QStringRef name; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT UiImport: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiImport) + + UiImport(const QStringRef &fileName) + : fileName(fileName), importUri(0) + { kind = K; } + + UiImport(UiQualifiedId *uri) + : importUri(uri) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return importToken; } + + virtual SourceLocation lastSourceLocation() const + { return semicolonToken; } + +// attributes + QStringRef fileName; + UiQualifiedId *importUri; + QStringRef importId; + SourceLocation importToken; + SourceLocation fileNameToken; + SourceLocation versionToken; + SourceLocation asToken; + SourceLocation importIdToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiImportList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiImportList) + + UiImportList(UiImport *import) + : import(import), + next(this) + { kind = K; } + + UiImportList(UiImportList *previous, UiImport *import) + : import(import) + { + kind = K; + next = previous->next; + previous->next = this; + } + + UiImportList *finish() + { + UiImportList *head = next; + next = 0; + return head; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return import->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : import->lastSourceLocation(); } + +// attributes + UiImport *import; + UiImportList *next; +}; + +class QML_PARSER_EXPORT UiObjectMember: public Node +{ +public: + virtual SourceLocation firstSourceLocation() const = 0; + virtual SourceLocation lastSourceLocation() const = 0; + + virtual UiObjectMember *uiObjectMemberCast(); +}; + +class QML_PARSER_EXPORT UiObjectMemberList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectMemberList) + + UiObjectMemberList(UiObjectMember *member) + : next(this), member(member) + { kind = K; } + + UiObjectMemberList(UiObjectMemberList *previous, UiObjectMember *member) + : member(member) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return member->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + + UiObjectMemberList *finish() + { + UiObjectMemberList *head = next; + next = 0; + return head; + } + +// attributes + UiObjectMemberList *next; + UiObjectMember *member; +}; + +class QML_PARSER_EXPORT UiProgram: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiProgram) + + UiProgram(UiImportList *imports, UiObjectMemberList *members) + : imports(imports), members(members) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { + if (imports) + return imports->firstSourceLocation(); + else if (members) + return members->firstSourceLocation(); + return SourceLocation(); + } + + virtual SourceLocation lastSourceLocation() const + { + if (members) + return members->lastSourceLocation(); + else if (imports) + return imports->lastSourceLocation(); + return SourceLocation(); + } + +// attributes + UiImportList *imports; + UiObjectMemberList *members; +}; + +class QML_PARSER_EXPORT UiArrayMemberList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiArrayMemberList) + + UiArrayMemberList(UiObjectMember *member) + : next(this), member(member) + { kind = K; } + + UiArrayMemberList(UiArrayMemberList *previous, UiObjectMember *member) + : member(member) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return member->firstSourceLocation(); } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + + UiArrayMemberList *finish() + { + UiArrayMemberList *head = next; + next = 0; + return head; + } + +// attributes + UiArrayMemberList *next; + UiObjectMember *member; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT UiObjectInitializer: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectInitializer) + + UiObjectInitializer(UiObjectMemberList *members) + : members(members) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return lbraceToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbraceToken; } + +// attributes + SourceLocation lbraceToken; + UiObjectMemberList *members; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT UiParameterList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiParameterList) + + UiParameterList(const QStringRef &t, const QStringRef &n): + type (t), name (n), next (this) + { kind = K; } + + UiParameterList(UiParameterList *previous, const QStringRef &t, const QStringRef &n): + type (t), name (n) + { + kind = K; + next = previous->next; + previous->next = this; + } + + virtual void accept0(Visitor *) {} + + virtual SourceLocation firstSourceLocation() const + { return propertyTypeToken; } + + virtual SourceLocation lastSourceLocation() const + { return next ? next->lastSourceLocation() : identifierToken; } + + inline UiParameterList *finish () + { + UiParameterList *front = next; + next = 0; + return front; + } + +// attributes + QStringRef type; + QStringRef name; + UiParameterList *next; + SourceLocation commaToken; + SourceLocation propertyTypeToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT UiPublicMember: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiPublicMember) + + UiPublicMember(const QStringRef &memberType, + const QStringRef &name) + : type(Property), memberType(memberType), name(name), statement(0), binding(0), isDefaultMember(false), isReadonlyMember(false), parameters(0) + { kind = K; } + + UiPublicMember(const QStringRef &memberType, + const QStringRef &name, + Statement *statement) + : type(Property), memberType(memberType), name(name), statement(statement), binding(0), isDefaultMember(false), isReadonlyMember(false), parameters(0) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { + if (defaultToken.isValid()) + return defaultToken; + else if (readonlyToken.isValid()) + return readonlyToken; + + return propertyToken; + } + + virtual SourceLocation lastSourceLocation() const + { + if (binding) + return binding->lastSourceLocation(); + if (statement) + return statement->lastSourceLocation(); + + return semicolonToken; + } + +// attributes + enum { Signal, Property } type; + QStringRef typeModifier; + QStringRef memberType; + QStringRef name; + Statement *statement; // initialized with a JS expression + UiObjectMember *binding; // initialized with a QML object or array. + bool isDefaultMember; + bool isReadonlyMember; + UiParameterList *parameters; + SourceLocation defaultToken; + SourceLocation readonlyToken; + SourceLocation propertyToken; + SourceLocation typeModifierToken; + SourceLocation typeToken; + SourceLocation identifierToken; + SourceLocation colonToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiObjectDefinition: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectDefinition) + + UiObjectDefinition(UiQualifiedId *qualifiedTypeNameId, + UiObjectInitializer *initializer) + : qualifiedTypeNameId(qualifiedTypeNameId), initializer(initializer) + { kind = K; } + + virtual void accept0(Visitor *visitor); + + virtual SourceLocation firstSourceLocation() const + { return qualifiedTypeNameId->identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return initializer->rbraceToken; } + +// attributes + UiQualifiedId *qualifiedTypeNameId; + UiObjectInitializer *initializer; +}; + +class QML_PARSER_EXPORT UiSourceElement: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiSourceElement) + + UiSourceElement(Node *sourceElement) + : sourceElement(sourceElement) + { kind = K; } + + virtual SourceLocation firstSourceLocation() const + { + if (FunctionDeclaration *funDecl = cast<FunctionDeclaration *>(sourceElement)) + return funDecl->firstSourceLocation(); + else if (VariableStatement *varStmt = cast<VariableStatement *>(sourceElement)) + return varStmt->firstSourceLocation(); + + return SourceLocation(); + } + + virtual SourceLocation lastSourceLocation() const + { + if (FunctionDeclaration *funDecl = cast<FunctionDeclaration *>(sourceElement)) + return funDecl->lastSourceLocation(); + else if (VariableStatement *varStmt = cast<VariableStatement *>(sourceElement)) + return varStmt->lastSourceLocation(); + + return SourceLocation(); + } + + virtual void accept0(Visitor *visitor); + + +// attributes + Node *sourceElement; +}; + +class QML_PARSER_EXPORT UiObjectBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectBinding) + + UiObjectBinding(UiQualifiedId *qualifiedId, + UiQualifiedId *qualifiedTypeNameId, + UiObjectInitializer *initializer) + : qualifiedId(qualifiedId), + qualifiedTypeNameId(qualifiedTypeNameId), + initializer(initializer), + hasOnToken(false) + { kind = K; } + + virtual SourceLocation firstSourceLocation() const + { + if (hasOnToken && qualifiedTypeNameId) + return qualifiedTypeNameId->identifierToken; + + return qualifiedId->identifierToken; + } + + virtual SourceLocation lastSourceLocation() const + { return initializer->rbraceToken; } + + virtual void accept0(Visitor *visitor); + + +// attributes + UiQualifiedId *qualifiedId; + UiQualifiedId *qualifiedTypeNameId; + UiObjectInitializer *initializer; + SourceLocation colonToken; + bool hasOnToken; +}; + +class QML_PARSER_EXPORT UiScriptBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiScriptBinding) + + UiScriptBinding(UiQualifiedId *qualifiedId, + Statement *statement) + : qualifiedId(qualifiedId), + statement(statement) + { kind = K; } + + virtual SourceLocation firstSourceLocation() const + { return qualifiedId->identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return statement->lastSourceLocation(); } + + virtual void accept0(Visitor *visitor); + +// attributes + UiQualifiedId *qualifiedId; + Statement *statement; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT UiArrayBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiArrayBinding) + + UiArrayBinding(UiQualifiedId *qualifiedId, + UiArrayMemberList *members) + : qualifiedId(qualifiedId), + members(members) + { kind = K; } + + virtual SourceLocation firstSourceLocation() const + { return qualifiedId->identifierToken; } + + virtual SourceLocation lastSourceLocation() const + { return rbracketToken; } + + virtual void accept0(Visitor *visitor); + +// attributes + UiQualifiedId *qualifiedId; + UiArrayMemberList *members; + SourceLocation colonToken; + SourceLocation lbracketToken; + SourceLocation rbracketToken; +}; + +} // namespace AST +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsastfwd_p.h b/src/lib/corelib/parser/qmljsastfwd_p.h new file mode 100644 index 000000000..f8dbeb7ad --- /dev/null +++ b/src/lib/corelib/parser/qmljsastfwd_p.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSAST_FWD_P_H +#define QMLJSAST_FWD_P_H + +#include "qmljsglobal_p.h" + +#include <QtCore/qglobal.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace QbsQmlJS { +namespace AST { + +class SourceLocation +{ +public: + SourceLocation(quint32 offset = 0, quint32 length = 0, quint32 line = 0, quint32 column = 0) + : offset(offset), length(length), + startLine(line), startColumn(column) + { } + + bool isValid() const { return length != 0; } + + quint32 begin() const { return offset; } + quint32 end() const { return offset + length; } + +// attributes + // ### encode + quint32 offset; + quint32 length; + quint32 startLine; + quint32 startColumn; +}; + +class Visitor; +class Node; +class ExpressionNode; +class Statement; +class ThisExpression; +class IdentifierExpression; +class NullExpression; +class TrueLiteral; +class FalseLiteral; +class NumericLiteral; +class StringLiteral; +class RegExpLiteral; +class ArrayLiteral; +class ObjectLiteral; +class ElementList; +class Elision; +class PropertyNameAndValueList; +class PropertyName; +class IdentifierPropertyName; +class StringLiteralPropertyName; +class NumericLiteralPropertyName; +class ArrayMemberExpression; +class FieldMemberExpression; +class NewMemberExpression; +class NewExpression; +class CallExpression; +class ArgumentList; +class PostIncrementExpression; +class PostDecrementExpression; +class DeleteExpression; +class VoidExpression; +class TypeOfExpression; +class PreIncrementExpression; +class PreDecrementExpression; +class UnaryPlusExpression; +class UnaryMinusExpression; +class TildeExpression; +class NotExpression; +class BinaryExpression; +class ConditionalExpression; +class Expression; // ### rename +class Block; +class StatementList; +class VariableStatement; +class VariableDeclarationList; +class VariableDeclaration; +class EmptyStatement; +class ExpressionStatement; +class IfStatement; +class DoWhileStatement; +class WhileStatement; +class ForStatement; +class LocalForStatement; +class ForEachStatement; +class LocalForEachStatement; +class ContinueStatement; +class BreakStatement; +class ReturnStatement; +class WithStatement; +class SwitchStatement; +class CaseBlock; +class CaseClauses; +class CaseClause; +class DefaultClause; +class LabelledStatement; +class ThrowStatement; +class TryStatement; +class Catch; +class Finally; +class FunctionDeclaration; +class FunctionExpression; +class FormalParameterList; +class FunctionBody; +class Program; +class SourceElements; +class SourceElement; +class FunctionSourceElement; +class StatementSourceElement; +class DebuggerStatement; +class NestedExpression; + +// ui elements +class UiProgram; +class UiImportList; +class UiImport; +class UiPublicMember; +class UiObjectDefinition; +class UiObjectInitializer; +class UiObjectBinding; +class UiScriptBinding; +class UiSourceElement; +class UiArrayBinding; +class UiObjectMember; +class UiObjectMemberList; +class UiArrayMemberList; +class UiQualifiedId; + +} // namespace AST +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsastvisitor.cpp b/src/lib/corelib/parser/qmljsastvisitor.cpp new file mode 100644 index 000000000..7cec7dcfc --- /dev/null +++ b/src/lib/corelib/parser/qmljsastvisitor.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 "qmljsastvisitor_p.h" + +namespace QbsQmlJS { +namespace AST { + +Visitor::Visitor() +{ +} + +Visitor::~Visitor() +{ +} + +} // namespace AST +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsastvisitor_p.h b/src/lib/corelib/parser/qmljsastvisitor_p.h new file mode 100644 index 000000000..135b07503 --- /dev/null +++ b/src/lib/corelib/parser/qmljsastvisitor_p.h @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSASTVISITOR_P_H +#define QMLJSASTVISITOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsastfwd_p.h" +#include "qmljsglobal_p.h" + +namespace QbsQmlJS { +namespace AST { + +class QML_PARSER_EXPORT Visitor +{ +public: + Visitor(); + virtual ~Visitor(); + + virtual bool preVisit(Node *) { return true; } + virtual void postVisit(Node *) {} + + // Ui + virtual bool visit(UiProgram *) { return true; } + virtual bool visit(UiImportList *) { return true; } + virtual bool visit(UiImport *) { return true; } + virtual bool visit(UiPublicMember *) { return true; } + virtual bool visit(UiSourceElement *) { return true; } + virtual bool visit(UiObjectDefinition *) { return true; } + virtual bool visit(UiObjectInitializer *) { return true; } + virtual bool visit(UiObjectBinding *) { return true; } + virtual bool visit(UiScriptBinding *) { return true; } + virtual bool visit(UiArrayBinding *) { return true; } + virtual bool visit(UiObjectMemberList *) { return true; } + virtual bool visit(UiArrayMemberList *) { return true; } + virtual bool visit(UiQualifiedId *) { return true; } + + virtual void endVisit(UiProgram *) {} + virtual void endVisit(UiImportList *) {} + virtual void endVisit(UiImport *) {} + virtual void endVisit(UiPublicMember *) {} + virtual void endVisit(UiSourceElement *) {} + virtual void endVisit(UiObjectDefinition *) {} + virtual void endVisit(UiObjectInitializer *) {} + virtual void endVisit(UiObjectBinding *) {} + virtual void endVisit(UiScriptBinding *) {} + virtual void endVisit(UiArrayBinding *) {} + virtual void endVisit(UiObjectMemberList *) {} + virtual void endVisit(UiArrayMemberList *) {} + virtual void endVisit(UiQualifiedId *) {} + + // QbsQmlJS + virtual bool visit(ThisExpression *) { return true; } + virtual void endVisit(ThisExpression *) {} + + virtual bool visit(IdentifierExpression *) { return true; } + virtual void endVisit(IdentifierExpression *) {} + + virtual bool visit(NullExpression *) { return true; } + virtual void endVisit(NullExpression *) {} + + virtual bool visit(TrueLiteral *) { return true; } + virtual void endVisit(TrueLiteral *) {} + + virtual bool visit(FalseLiteral *) { return true; } + virtual void endVisit(FalseLiteral *) {} + + virtual bool visit(StringLiteral *) { return true; } + virtual void endVisit(StringLiteral *) {} + + virtual bool visit(NumericLiteral *) { return true; } + virtual void endVisit(NumericLiteral *) {} + + virtual bool visit(RegExpLiteral *) { return true; } + virtual void endVisit(RegExpLiteral *) {} + + virtual bool visit(ArrayLiteral *) { return true; } + virtual void endVisit(ArrayLiteral *) {} + + virtual bool visit(ObjectLiteral *) { return true; } + virtual void endVisit(ObjectLiteral *) {} + + virtual bool visit(ElementList *) { return true; } + virtual void endVisit(ElementList *) {} + + virtual bool visit(Elision *) { return true; } + virtual void endVisit(Elision *) {} + + virtual bool visit(PropertyNameAndValueList *) { return true; } + virtual void endVisit(PropertyNameAndValueList *) {} + + virtual bool visit(NestedExpression *) { return true; } + virtual void endVisit(NestedExpression *) {} + + virtual bool visit(IdentifierPropertyName *) { return true; } + virtual void endVisit(IdentifierPropertyName *) {} + + virtual bool visit(StringLiteralPropertyName *) { return true; } + virtual void endVisit(StringLiteralPropertyName *) {} + + virtual bool visit(NumericLiteralPropertyName *) { return true; } + virtual void endVisit(NumericLiteralPropertyName *) {} + + virtual bool visit(ArrayMemberExpression *) { return true; } + virtual void endVisit(ArrayMemberExpression *) {} + + virtual bool visit(FieldMemberExpression *) { return true; } + virtual void endVisit(FieldMemberExpression *) {} + + virtual bool visit(NewMemberExpression *) { return true; } + virtual void endVisit(NewMemberExpression *) {} + + virtual bool visit(NewExpression *) { return true; } + virtual void endVisit(NewExpression *) {} + + virtual bool visit(CallExpression *) { return true; } + virtual void endVisit(CallExpression *) {} + + virtual bool visit(ArgumentList *) { return true; } + virtual void endVisit(ArgumentList *) {} + + virtual bool visit(PostIncrementExpression *) { return true; } + virtual void endVisit(PostIncrementExpression *) {} + + virtual bool visit(PostDecrementExpression *) { return true; } + virtual void endVisit(PostDecrementExpression *) {} + + virtual bool visit(DeleteExpression *) { return true; } + virtual void endVisit(DeleteExpression *) {} + + virtual bool visit(VoidExpression *) { return true; } + virtual void endVisit(VoidExpression *) {} + + virtual bool visit(TypeOfExpression *) { return true; } + virtual void endVisit(TypeOfExpression *) {} + + virtual bool visit(PreIncrementExpression *) { return true; } + virtual void endVisit(PreIncrementExpression *) {} + + virtual bool visit(PreDecrementExpression *) { return true; } + virtual void endVisit(PreDecrementExpression *) {} + + virtual bool visit(UnaryPlusExpression *) { return true; } + virtual void endVisit(UnaryPlusExpression *) {} + + virtual bool visit(UnaryMinusExpression *) { return true; } + virtual void endVisit(UnaryMinusExpression *) {} + + virtual bool visit(TildeExpression *) { return true; } + virtual void endVisit(TildeExpression *) {} + + virtual bool visit(NotExpression *) { return true; } + virtual void endVisit(NotExpression *) {} + + virtual bool visit(BinaryExpression *) { return true; } + virtual void endVisit(BinaryExpression *) {} + + virtual bool visit(ConditionalExpression *) { return true; } + virtual void endVisit(ConditionalExpression *) {} + + virtual bool visit(Expression *) { return true; } + virtual void endVisit(Expression *) {} + + virtual bool visit(Block *) { return true; } + virtual void endVisit(Block *) {} + + virtual bool visit(StatementList *) { return true; } + virtual void endVisit(StatementList *) {} + + virtual bool visit(VariableStatement *) { return true; } + virtual void endVisit(VariableStatement *) {} + + virtual bool visit(VariableDeclarationList *) { return true; } + virtual void endVisit(VariableDeclarationList *) {} + + virtual bool visit(VariableDeclaration *) { return true; } + virtual void endVisit(VariableDeclaration *) {} + + virtual bool visit(EmptyStatement *) { return true; } + virtual void endVisit(EmptyStatement *) {} + + virtual bool visit(ExpressionStatement *) { return true; } + virtual void endVisit(ExpressionStatement *) {} + + virtual bool visit(IfStatement *) { return true; } + virtual void endVisit(IfStatement *) {} + + virtual bool visit(DoWhileStatement *) { return true; } + virtual void endVisit(DoWhileStatement *) {} + + virtual bool visit(WhileStatement *) { return true; } + virtual void endVisit(WhileStatement *) {} + + virtual bool visit(ForStatement *) { return true; } + virtual void endVisit(ForStatement *) {} + + virtual bool visit(LocalForStatement *) { return true; } + virtual void endVisit(LocalForStatement *) {} + + virtual bool visit(ForEachStatement *) { return true; } + virtual void endVisit(ForEachStatement *) {} + + virtual bool visit(LocalForEachStatement *) { return true; } + virtual void endVisit(LocalForEachStatement *) {} + + virtual bool visit(ContinueStatement *) { return true; } + virtual void endVisit(ContinueStatement *) {} + + virtual bool visit(BreakStatement *) { return true; } + virtual void endVisit(BreakStatement *) {} + + virtual bool visit(ReturnStatement *) { return true; } + virtual void endVisit(ReturnStatement *) {} + + virtual bool visit(WithStatement *) { return true; } + virtual void endVisit(WithStatement *) {} + + virtual bool visit(SwitchStatement *) { return true; } + virtual void endVisit(SwitchStatement *) {} + + virtual bool visit(CaseBlock *) { return true; } + virtual void endVisit(CaseBlock *) {} + + virtual bool visit(CaseClauses *) { return true; } + virtual void endVisit(CaseClauses *) {} + + virtual bool visit(CaseClause *) { return true; } + virtual void endVisit(CaseClause *) {} + + virtual bool visit(DefaultClause *) { return true; } + virtual void endVisit(DefaultClause *) {} + + virtual bool visit(LabelledStatement *) { return true; } + virtual void endVisit(LabelledStatement *) {} + + virtual bool visit(ThrowStatement *) { return true; } + virtual void endVisit(ThrowStatement *) {} + + virtual bool visit(TryStatement *) { return true; } + virtual void endVisit(TryStatement *) {} + + virtual bool visit(Catch *) { return true; } + virtual void endVisit(Catch *) {} + + virtual bool visit(Finally *) { return true; } + virtual void endVisit(Finally *) {} + + virtual bool visit(FunctionDeclaration *) { return true; } + virtual void endVisit(FunctionDeclaration *) {} + + virtual bool visit(FunctionExpression *) { return true; } + virtual void endVisit(FunctionExpression *) {} + + virtual bool visit(FormalParameterList *) { return true; } + virtual void endVisit(FormalParameterList *) {} + + virtual bool visit(FunctionBody *) { return true; } + virtual void endVisit(FunctionBody *) {} + + virtual bool visit(Program *) { return true; } + virtual void endVisit(Program *) {} + + virtual bool visit(SourceElements *) { return true; } + virtual void endVisit(SourceElements *) {} + + virtual bool visit(FunctionSourceElement *) { return true; } + virtual void endVisit(FunctionSourceElement *) {} + + virtual bool visit(StatementSourceElement *) { return true; } + virtual void endVisit(StatementSourceElement *) {} + + virtual bool visit(DebuggerStatement *) { return true; } + virtual void endVisit(DebuggerStatement *) {} +}; + +} // namespace AST +} // namespace QbsQmlJS + +#endif // QMLJSASTVISITOR_P_H diff --git a/src/lib/corelib/parser/qmljsengine_p.cpp b/src/lib/corelib/parser/qmljsengine_p.cpp new file mode 100644 index 000000000..410de608f --- /dev/null +++ b/src/lib/corelib/parser/qmljsengine_p.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 "qmljsengine_p.h" +#include "qmljsglobal_p.h" + +#include <qnumeric.h> +#include <QHash> +#include <QDebug> + +namespace QbsQmlJS { + +static int toDigit(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'a') && (c <= 'z')) + return 10 + c - 'a'; + else if ((c >= 'A') && (c <= 'Z')) + return 10 + c - 'A'; + return -1; +} + +double integerFromString(const char *buf, int size, int radix) +{ + if (size == 0) + return qSNaN(); + + double sign = 1.0; + int i = 0; + if (buf[0] == '+') { + ++i; + } else if (buf[0] == '-') { + sign = -1.0; + ++i; + } + + if (((size-i) >= 2) && (buf[i] == '0')) { + if (((buf[i+1] == 'x') || (buf[i+1] == 'X')) + && (radix < 34)) { + if ((radix != 0) && (radix != 16)) + return 0; + radix = 16; + i += 2; + } else { + if (radix == 0) { + radix = 8; + ++i; + } + } + } else if (radix == 0) { + radix = 10; + } + + int j = i; + for ( ; i < size; ++i) { + int d = toDigit(buf[i]); + if ((d == -1) || (d >= radix)) + break; + } + double result; + if (j == i) { + if (!qstrcmp(buf, "Infinity")) + result = qInf(); + else + result = qSNaN(); + } else { + result = 0; + double multiplier = 1; + for (--i ; i >= j; --i, multiplier *= radix) + result += toDigit(buf[i]) * multiplier; + } + result *= sign; + return result; +} + +double integerFromString(const QString &str, int radix) +{ + QByteArray ba = str.trimmed().toLatin1(); + return integerFromString(ba.constData(), ba.size(), radix); +} + + +Engine::Engine() + : _lexer(0), _directives(0) +{ } + +Engine::~Engine() +{ } + +void Engine::setCode(const QString &code) +{ _code = code; } + +void Engine::addComment(int pos, int len, int line, int col) +{ if (len > 0) _comments.append(QbsQmlJS::AST::SourceLocation(pos, len, line, col)); } + +QList<QbsQmlJS::AST::SourceLocation> Engine::comments() const +{ return _comments; } + +Lexer *Engine::lexer() const +{ return _lexer; } + +void Engine::setLexer(Lexer *lexer) +{ _lexer = lexer; } + +void Engine::setDirectives(Directives *directives) +{ _directives = directives; } + +Directives *Engine::directives() const +{ return _directives; } + +MemoryPool *Engine::pool() +{ return &_pool; } + +QStringRef Engine::newStringRef(const QString &text) +{ + const int pos = _extraCode.length(); + _extraCode += text; + return _extraCode.midRef(pos, text.length()); +} + +QStringRef Engine::newStringRef(const QChar *chars, int size) +{ return newStringRef(QString(chars, size)); } + +} // end of namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsengine_p.h b/src/lib/corelib/parser/qmljsengine_p.h new file mode 100644 index 000000000..6965e5c20 --- /dev/null +++ b/src/lib/corelib/parser/qmljsengine_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSENGINE_P_H +#define QMLJSENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" +#include "qmljsastfwd_p.h" +#include "qmljsmemorypool_p.h" + +#include <QString> +#include <QSet> + +namespace QbsQmlJS { + +class Lexer; +class Directives; +class MemoryPool; + +class QML_PARSER_EXPORT DiagnosticMessage +{ +public: + enum Kind { Warning, Error }; + + DiagnosticMessage() + : kind(Error) {} + + DiagnosticMessage(Kind kind, const AST::SourceLocation &loc, const QString &message) + : kind(kind), loc(loc), message(message) {} + + bool isWarning() const + { return kind == Warning; } + + bool isError() const + { return kind == Error; } + + Kind kind; + AST::SourceLocation loc; + QString message; +}; + +class QML_PARSER_EXPORT Engine +{ + Lexer *_lexer; + Directives *_directives; + MemoryPool _pool; + QList<AST::SourceLocation> _comments; + QString _extraCode; + QString _code; + +public: + Engine(); + ~Engine(); + + void setCode(const QString &code); + + void addComment(int pos, int len, int line, int col); + QList<AST::SourceLocation> comments() const; + + Lexer *lexer() const; + void setLexer(Lexer *lexer); + + void setDirectives(Directives *directives); + Directives *directives() const; + + MemoryPool *pool(); + + inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); } + + QStringRef newStringRef(const QString &s); + QStringRef newStringRef(const QChar *chars, int size); +}; + +double integerFromString(const char *buf, int size, int radix); + +} // end of namespace QbsQmlJS + +#endif // QMLJSENGINE_P_H diff --git a/src/lib/corelib/parser/qmljsglobal_p.h b/src/lib/corelib/parser/qmljsglobal_p.h new file mode 100644 index 000000000..82b7f092e --- /dev/null +++ b/src/lib/corelib/parser/qmljsglobal_p.h @@ -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 Qt Creator. +** +** 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 QMLJSGLOBAL_P_H +#define QMLJSGLOBAL_P_H + +#include <QtCore/qglobal.h> + +#ifdef QT_CREATOR +# ifdef QMLJS_BUILD_DIR +# define QML_PARSER_EXPORT Q_DECL_EXPORT +# elif QML_BUILD_STATIC_LIB +# define QML_PARSER_EXPORT +# else +# define QML_PARSER_EXPORT Q_DECL_IMPORT +# endif // QMLJS_BUILD_DIR + +#else // !QT_CREATOR +# if defined(QT_BUILD_QMLDEVTOOLS_LIB) || defined(QT_QMLDEVTOOLS_LIB) + // QmlDevTools is a static library +# define QML_PARSER_EXPORT +# else +# define QML_PARSER_EXPORT Q_AUTOTEST_EXPORT +# endif +#endif // QT_CREATOR + +#endif // QMLJSGLOBAL_P_H diff --git a/src/lib/corelib/parser/qmljsgrammar.cpp b/src/lib/corelib/parser/qmljsgrammar.cpp new file mode 100644 index 000000000..534519fad --- /dev/null +++ b/src/lib/corelib/parser/qmljsgrammar.cpp @@ -0,0 +1,1001 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +// This file was generated by qlalr - DO NOT EDIT! +#include "qmljsgrammar_p.h" + +namespace QbsQmlJS { + +const char *const QmlJSGrammar::spell [] = { + "end of file", "&", "&&", "&=", "break", "case", "catch", ":", ",", "continue", + "default", "delete", "/", "/=", "do", ".", "else", "=", "==", "===", + "finally", "for", "function", ">=", ">", ">>", ">>=", ">>>", ">>>=", "identifier", + "if", "in", "instanceof", "{", "[", "<=", "(", "<", "<<", "<<=", + "-", "-=", "--", "new", "!", "!=", "!==", "numeric literal", "|", "|=", + "||", "+", "+=", "++", "?", "}", "]", "%", "%=", "return", + ")", ";", 0, "*", "*=", "string literal", "property", "signal", "readonly", "switch", + "this", "throw", "~", "try", "typeof", "var", "void", "while", "with", "^", + "^=", "null", "true", "false", "const", "debugger", "reserved word", "multiline string literal", "comment", "public", + "import", "as", "on", 0, 0, 0, 0, 0, 0, 0, + 0, 0}; + +const short QmlJSGrammar::lhs [] = { + 102, 102, 102, 102, 102, 102, 103, 109, 109, 112, + 112, 114, 113, 113, 113, 113, 113, 113, 113, 113, + 116, 111, 110, 119, 119, 120, 120, 121, 121, 118, + 107, 107, 107, 107, 123, 123, 123, 123, 123, 123, + 123, 107, 131, 131, 131, 132, 132, 133, 133, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 117, 117, 117, 117, + 117, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 122, + 138, 138, 138, 138, 137, 137, 140, 140, 142, 142, + 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 144, 144, 115, 115, 115, + 115, 115, 147, 147, 148, 148, 148, 148, 146, 146, + 149, 149, 150, 150, 151, 151, 151, 152, 152, 152, + 152, 152, 152, 152, 152, 152, 152, 153, 153, 153, + 153, 154, 154, 154, 155, 155, 155, 155, 156, 156, + 156, 156, 156, 156, 156, 157, 157, 157, 157, 157, + 157, 158, 158, 158, 158, 158, 159, 159, 159, 159, + 159, 160, 160, 161, 161, 162, 162, 163, 163, 164, + 164, 165, 165, 166, 166, 167, 167, 168, 168, 169, + 169, 170, 170, 171, 171, 141, 141, 172, 172, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 105, 105, 174, 174, 175, 175, 176, 176, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 124, 185, 185, 184, 184, 135, + 135, 186, 186, 187, 187, 189, 189, 188, 190, 193, + 191, 191, 194, 192, 192, 125, 126, 126, 127, 127, + 177, 177, 177, 177, 177, 177, 177, 178, 178, 178, + 178, 179, 179, 179, 179, 180, 180, 128, 129, 195, + 195, 198, 198, 196, 196, 199, 197, 181, 181, 181, + 182, 182, 130, 130, 130, 200, 201, 183, 183, 134, + 145, 205, 205, 202, 202, 203, 203, 206, 108, 108, + 207, 207, 106, 106, 204, 204, 139, 139, 208}; + +const short QmlJSGrammar::rhs [] = { + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 2, 1, 2, 2, 3, 3, 5, 5, 4, 4, + 2, 0, 1, 1, 2, 1, 3, 2, 3, 2, + 1, 5, 4, 4, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 0, 1, 2, 4, 6, + 6, 3, 3, 7, 7, 4, 4, 5, 5, 5, + 6, 6, 10, 6, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 3, 3, 4, 5, 3, 4, 3, 1, + 1, 2, 3, 4, 1, 2, 3, 5, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, + 3, 5, 1, 2, 4, 4, 4, 3, 0, 1, + 1, 3, 1, 1, 1, 2, 2, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, + 3, 1, 3, 3, 1, 3, 3, 3, 1, 3, + 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, + 3, 1, 3, 3, 3, 3, 1, 3, 3, 3, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 5, 1, 5, 1, 3, 1, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 3, 0, 1, 1, 3, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 1, 2, 0, 1, 3, + 3, 1, 1, 1, 3, 1, 3, 2, 2, 2, + 0, 1, 2, 0, 1, 1, 2, 2, 7, 5, + 7, 7, 5, 9, 10, 7, 8, 2, 2, 3, + 3, 2, 2, 3, 3, 3, 3, 5, 5, 3, + 5, 1, 2, 0, 1, 4, 3, 3, 3, 3, + 3, 3, 3, 3, 4, 5, 2, 2, 2, 8, + 8, 1, 3, 0, 1, 0, 1, 1, 1, 1, + 1, 2, 1, 1, 0, 1, 0, 1, 2}; + +const short QmlJSGrammar::action_default [] = { + 0, 0, 22, 0, 0, 0, 22, 0, 175, 242, + 206, 214, 210, 154, 226, 202, 3, 139, 73, 155, + 218, 222, 143, 172, 153, 158, 138, 192, 179, 0, + 80, 81, 76, 345, 67, 347, 0, 0, 0, 0, + 78, 0, 0, 74, 77, 71, 0, 0, 68, 70, + 69, 79, 72, 0, 75, 0, 0, 168, 0, 0, + 155, 174, 157, 156, 0, 0, 0, 170, 171, 169, + 173, 0, 203, 0, 0, 0, 0, 193, 0, 0, + 0, 0, 0, 0, 183, 0, 0, 0, 177, 178, + 176, 181, 185, 184, 182, 180, 195, 194, 196, 0, + 211, 0, 207, 0, 0, 149, 136, 148, 137, 105, + 106, 107, 132, 108, 133, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 134, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 135, + 0, 0, 147, 243, 150, 0, 151, 0, 152, 146, + 0, 239, 232, 230, 237, 238, 236, 235, 241, 234, + 233, 231, 240, 227, 0, 215, 0, 0, 219, 0, + 0, 223, 0, 0, 149, 141, 0, 140, 0, 145, + 159, 0, 346, 334, 335, 0, 332, 0, 333, 0, + 336, 250, 257, 256, 264, 252, 0, 253, 337, 0, + 344, 254, 255, 260, 258, 341, 338, 343, 261, 0, + 272, 0, 0, 0, 0, 345, 67, 0, 347, 68, + 244, 286, 69, 0, 0, 0, 273, 0, 0, 262, + 263, 0, 251, 259, 287, 288, 331, 342, 0, 302, + 303, 304, 305, 0, 298, 299, 300, 301, 328, 329, + 0, 0, 0, 0, 0, 291, 292, 248, 246, 208, + 216, 212, 228, 204, 249, 0, 155, 220, 224, 197, + 186, 0, 0, 205, 0, 0, 0, 0, 198, 0, + 0, 0, 0, 0, 190, 188, 191, 189, 187, 200, + 199, 201, 0, 213, 0, 209, 0, 247, 155, 0, + 229, 244, 245, 0, 244, 0, 0, 294, 0, 0, + 0, 296, 0, 217, 0, 0, 221, 0, 0, 225, + 284, 0, 276, 285, 279, 0, 283, 0, 244, 277, + 0, 244, 0, 0, 295, 0, 0, 0, 297, 346, + 334, 0, 0, 336, 0, 330, 0, 320, 0, 0, + 0, 290, 0, 289, 0, 348, 0, 104, 266, 269, + 0, 105, 272, 108, 133, 110, 111, 76, 115, 116, + 67, 117, 120, 74, 77, 68, 244, 69, 79, 123, + 72, 125, 75, 127, 128, 273, 130, 131, 135, 0, + 97, 0, 0, 99, 103, 101, 88, 100, 102, 0, + 98, 87, 267, 265, 143, 144, 149, 0, 142, 0, + 319, 0, 306, 307, 0, 318, 0, 0, 0, 309, + 314, 312, 315, 0, 0, 313, 314, 0, 310, 0, + 311, 268, 317, 0, 268, 316, 0, 321, 322, 0, + 268, 323, 324, 0, 0, 325, 0, 0, 0, 326, + 327, 161, 160, 0, 0, 0, 293, 0, 0, 0, + 308, 281, 274, 0, 282, 278, 0, 280, 270, 0, + 271, 275, 91, 0, 0, 95, 82, 0, 84, 93, + 0, 85, 94, 96, 86, 92, 83, 0, 89, 165, + 163, 167, 164, 162, 166, 339, 6, 340, 4, 2, + 65, 90, 0, 0, 68, 70, 69, 31, 5, 0, + 66, 0, 45, 44, 43, 0, 0, 58, 0, 59, + 35, 36, 37, 38, 40, 41, 62, 39, 0, 45, + 0, 0, 0, 0, 0, 54, 0, 55, 0, 0, + 26, 0, 0, 63, 27, 0, 30, 28, 24, 0, + 29, 25, 0, 56, 0, 57, 143, 0, 60, 64, + 0, 0, 0, 0, 61, 0, 52, 46, 53, 47, + 0, 0, 0, 0, 49, 0, 50, 51, 48, 0, + 0, 143, 268, 0, 0, 42, 105, 272, 108, 133, + 110, 111, 76, 115, 116, 67, 117, 120, 74, 77, + 68, 244, 69, 79, 123, 72, 125, 75, 127, 128, + 273, 130, 131, 135, 0, 32, 33, 0, 34, 8, + 0, 10, 0, 9, 0, 1, 21, 12, 0, 13, + 0, 14, 0, 19, 20, 0, 15, 16, 0, 17, + 18, 11, 23, 7, 349}; + +const short QmlJSGrammar::goto_default [] = { + 7, 625, 207, 196, 205, 508, 496, 624, 643, 495, + 623, 621, 626, 22, 622, 18, 507, 549, 539, 546, + 541, 526, 191, 195, 197, 201, 233, 208, 230, 530, + 570, 569, 200, 232, 26, 474, 473, 356, 355, 9, + 354, 357, 107, 17, 145, 24, 13, 144, 19, 25, + 57, 23, 8, 28, 27, 269, 15, 263, 10, 259, + 12, 261, 11, 260, 20, 267, 21, 268, 14, 262, + 258, 299, 411, 264, 265, 202, 193, 192, 204, 203, + 229, 194, 360, 359, 231, 463, 462, 321, 322, 465, + 324, 464, 323, 419, 423, 426, 422, 421, 441, 442, + 185, 199, 181, 184, 198, 206, 0}; + +const short QmlJSGrammar::action_index [] = { + 404, 1275, 2411, 2411, 2509, 1000, 68, 92, 90, -102, + 88, 62, 60, 256, -102, 298, 86, -102, -102, 638, + 83, 134, 172, 219, -102, -102, -102, 454, 194, 1275, + -102, -102, -102, 381, -102, 2215, 1555, 1275, 1275, 1275, + -102, 790, 1275, -102, -102, -102, 1275, 1275, -102, -102, + -102, -102, -102, 1275, -102, 1275, 1275, -102, 1275, 1275, + 102, 217, -102, -102, 1275, 1275, 1275, -102, -102, -102, + 204, 1275, 304, 1275, 1275, 1275, 1275, 539, 1275, 1275, + 1275, 1275, 1275, 1275, 308, 1275, 1275, 1275, 103, 131, + 135, 308, 210, 225, 216, 308, 444, 390, 434, 1275, + 82, 1275, 100, 2117, 1275, 1275, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + 139, 1275, -102, -102, 91, 10, -102, 1275, -102, -102, + 1275, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, 1275, 26, 1275, 1275, 69, 66, + 1275, -102, 2117, 1275, 1275, -102, 97, -102, 44, -102, + -102, 67, -102, 297, 78, 24, -102, 291, -102, 36, + 2411, -102, -102, -102, -102, -102, 234, -102, -102, 12, + -102, -102, -102, -102, -102, -102, 2411, -102, -102, 464, + -102, 461, 115, 2509, 42, 381, 58, 46, 2705, 70, + 1275, -102, 74, 57, 1275, 65, -102, 59, 61, -102, + -102, 367, -102, -102, -102, -102, -102, -102, 106, -102, + -102, -102, -102, 87, -102, -102, -102, -102, -102, -102, + 56, 55, 1275, 99, 84, -102, -102, 1461, -102, 75, + 48, 52, -102, 306, 72, 53, 579, 77, 110, 370, + 230, 381, 1275, 286, 1275, 1275, 1275, 1275, 380, 1275, + 1275, 1275, 1275, 1275, 184, 169, 166, 190, 198, 460, + 363, 353, 1275, 50, 1275, 63, 1275, -102, 638, 1275, + -102, 1275, 64, 39, 1275, 30, 2509, -102, 1275, 173, + 2509, -102, 1275, 79, 1275, 1275, 81, 80, 1275, -102, + 71, 149, 32, -102, -102, 1275, -102, 381, 1275, -102, + 73, 1275, 76, 2509, -102, 1275, 142, 2509, -102, -16, + 381, -42, -12, 2411, -39, -102, 2509, -102, 1275, 154, + 2509, 14, 2509, -102, 20, 16, -32, -102, -102, 2509, + -51, 519, -4, 511, 136, 1275, 2509, -2, -35, 395, + -1, -27, 908, 4, 6, -102, 1370, -102, 0, -36, + 27, 1275, 47, 22, 1275, 45, 1275, 21, 17, 1275, + -102, 2313, 144, -102, -102, -102, -102, -102, -102, 1275, + -102, -102, -102, -102, 274, -102, 1275, -21, -102, 2509, + -102, 138, -102, -102, 2509, -102, 1275, 132, 5, -102, + 40, -102, 41, 101, 1275, -102, 38, 34, -102, -38, + -102, 2509, -102, 105, 2509, -102, 245, -102, -102, 96, + 2509, 11, -102, -7, -11, -102, 352, 8, 18, -102, + -102, -102, -102, 1275, 129, 2509, -102, 1275, 130, 2509, + -102, 49, -102, 226, -102, -102, 1275, -102, -102, 362, + -102, -102, -102, 107, 1837, -102, -102, 1649, -102, -102, + 1743, -102, -102, -102, -102, -102, -102, 114, -102, -102, + -102, -102, -102, -102, -102, -102, -102, 2411, -102, -102, + -102, 94, 9, 818, 189, -10, 31, -102, -102, 223, + -102, 191, -102, -102, -102, 300, 178, -102, 1928, -102, + -102, -102, -102, -102, -102, -102, -102, -102, 257, -25, + 381, 195, -22, 305, 240, -102, -6, -102, 818, 127, + -102, -18, 818, -102, -102, 1184, -102, -102, -102, 1092, + -102, -102, 237, -102, 1928, -102, 294, -8, -102, -102, + 176, 381, 19, 1928, -102, 165, -102, 174, -102, 2, + -52, 381, 183, 381, -102, 117, -102, -102, -102, 2019, + 880, 285, 2607, 1555, 3, -102, 522, 35, 453, 108, + 1275, 2509, 51, 23, 475, 54, -17, 700, 7, 43, + -102, 1370, -102, 28, -3, 33, 1275, 37, 15, 1275, + 25, 1275, 1, 13, 124, -102, -102, 29, -102, -102, + 728, -102, 250, -43, 627, -102, -102, 231, 372, -102, + 222, -102, 111, -102, -102, 381, -102, -102, 104, -102, + -102, -102, -102, -102, -102, + + -107, 9, -103, 2, 5, 266, 1, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -39, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 86, + -107, -107, -107, 8, -107, -107, -22, 19, 71, 174, + -107, 186, 171, -107, -107, -107, 184, 178, -107, -107, + -107, -107, -107, 144, -107, 124, 150, -107, 165, 161, + -107, -107, -107, -107, 156, 160, 157, -107, -107, -107, + -107, 147, -107, 142, 135, 179, 166, -107, 177, 170, + 117, 72, 134, 92, -107, 75, 94, 66, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 181, + -107, 106, -107, 143, 78, 55, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -5, -107, -107, -107, -107, -107, 54, -107, -107, + 51, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, 114, -107, 113, 38, -107, -107, + 41, -107, 231, 63, 112, -107, -107, -107, -107, -107, + -107, -107, -107, 30, -107, -107, -107, 52, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, 36, -107, -107, 45, + -107, 42, -107, 40, -107, 80, -107, -107, 77, -107, + 88, -107, -107, -107, 83, 74, -107, -107, -107, -107, + -107, -10, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, 23, -107, -107, -107, -107, 100, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, 4, 223, -107, 230, 236, 222, 205, -107, 127, + 125, 115, 96, 102, -107, -107, -107, -107, -107, -107, + -107, -107, 234, -107, 215, -107, 199, -107, -107, 197, + -107, 190, -107, -107, 163, -107, 90, -107, 0, -107, + -1, -107, 203, -107, 189, 211, -107, -107, 195, -107, + -107, -107, -107, -107, -107, 191, -107, 98, 119, -107, + -107, 95, -107, 81, -107, 79, -107, 82, -107, -107, + 101, -107, -107, -16, -107, -107, 53, -107, 46, -107, + 57, -107, 59, -107, -107, -107, -107, -107, -107, 35, + -107, 33, -107, 39, -107, 89, 67, -107, -107, 58, + -107, -107, 84, -107, -107, -107, 73, -107, -107, -107, + -107, 65, -107, 43, 93, -107, 109, -107, -107, 49, + -107, 47, -107, -107, -107, -107, -107, -107, -107, 50, + -107, -107, -107, -107, -107, -107, 108, -107, -107, 61, + -107, -107, -107, -107, 62, -107, 68, -107, -107, -107, + -107, -107, -23, -107, 69, -107, -19, -107, -107, -107, + -107, 97, -107, -107, 99, -107, -107, -107, -107, -107, + 60, -61, -107, -107, 34, -107, 37, -107, 29, -107, + -107, -107, -107, 32, -107, 76, -107, 44, -107, 56, + -107, -107, -107, -107, -107, -107, 31, -107, -107, 116, + -107, -107, -107, -107, -6, -107, -107, 70, -107, -107, + 64, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, 193, -107, -107, + -107, -107, -107, 7, -107, -107, -107, -107, -107, -107, + -107, -20, -107, -107, -107, -7, -107, -107, 290, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -2, -25, -107, -15, -107, -107, -107, -107, 172, -107, + -107, -107, 287, -107, -107, 288, -107, -107, -107, 291, + -107, -107, -107, -107, 336, -107, -107, 20, -107, -107, + 15, 3, -107, 304, -107, -107, -107, 24, -107, -107, + -107, 28, 21, 26, -107, -107, -107, -107, -107, 320, + 104, -107, 13, 381, -3, -107, 6, -107, 10, -107, + 167, 22, -107, -107, 12, -107, -107, 87, -107, -107, + -107, 25, -107, -107, -107, -107, 11, -107, 14, 85, + -107, 121, -107, -107, -107, -107, -107, 27, -107, -107, + 17, -107, -107, 18, 91, -107, -107, -107, 16, -107, + -107, -107, -107, -107, -107, -4, -107, -107, -107, -107, + -107, -107, -107, -107, -107}; + +const short QmlJSGrammar::action_info [] = { + 416, 257, 533, -132, 403, -113, 346, -102, 575, 348, + 572, -121, 531, -103, -121, 545, 345, 430, 342, 348, + 340, 343, 440, 401, 391, 545, 563, 389, 538, 446, + 352, 444, -129, 416, -124, -102, 545, 453, 420, 408, + -124, 431, -132, 424, -126, 424, 424, 620, 440, 457, + -103, 440, -129, 457, -126, 440, 560, 453, -113, 257, + 565, 346, 545, 335, 272, 346, 466, 236, 448, 190, + 149, 164, 141, 170, 99, 511, 272, 409, 257, 312, + 296, 414, 348, 312, 189, 164, 187, 318, 325, 71, + 306, 252, 644, 416, 141, 453, 292, 457, 440, 147, + 304, 71, 443, 183, 179, 141, 0, 141, 0, 172, + 99, 427, 434, 141, 301, 477, 444, 0, 0, 0, + 0, 0, 141, 0, 0, 0, 0, 292, 173, 294, + 58, 294, 542, 251, 331, 542, 333, 141, 141, 101, + 141, 59, 0, 58, 62, 256, 255, 141, 247, 246, + 141, 399, 0, 177, 59, 63, 428, 327, 620, 254, + 314, 101, 141, 478, 315, 640, 639, 242, 241, 249, + 248, 58, 634, 633, 488, 58, 249, 248, 577, 576, + 615, 141, 59, 543, 166, 518, 59, 172, 167, 455, + 459, 85, 418, 86, 85, 142, 86, 249, 248, 413, + 412, 567, 337, 512, 87, 512, 173, 87, 174, 85, + 328, 86, 512, 0, 350, 85, 64, 86, 529, 85, + 512, 86, 87, 85, 512, 86, 568, 566, 87, 64, + 579, 64, 87, 310, 469, 85, 87, 86, 0, 519, + 517, 85, 141, 86, 554, 0, 172, 536, 87, 514, + 85, 514, 86, 141, 87, 85, 545, 86, 514, 0, + 513, 65, 513, 87, 514, 173, 514, 66, 87, 513, + 514, 103, 172, 0, 65, 513, 65, 513, 0, 0, + 66, 513, 66, 637, 636, 0, 0, 470, 468, 172, + 104, 173, 105, 406, 0, 235, 234, 630, 555, 553, + 172, 537, 535, 0, 274, 275, 438, 437, 173, 172, + 406, 631, 629, 635, 0, 580, 73, 74, -90, 173, + 34, 174, 73, 74, 274, 275, 34, -90, 173, 34, + 174, 276, 277, 85, 34, 86, 0, 0, 0, 0, + 0, 628, 0, 75, 76, 0, 87, 0, 0, 75, + 76, 276, 277, 0, 0, 0, 0, 48, 50, 49, + 0, 0, 0, 48, 50, 49, 48, 50, 49, 0, + 0, 48, 50, 49, 0, 0, 279, 280, 0, 0, + 0, 34, 0, 45, 0, 281, 279, 280, 282, 45, + 283, 34, 45, 279, 280, 281, 34, 45, 282, 0, + 283, 34, 281, 279, 280, 282, 0, 283, 0, 0, + 34, 0, 281, 78, 79, 282, 0, 283, 48, 50, + 49, 80, 81, 0, 34, 82, 0, 83, 48, 50, + 49, -345, 0, 48, 50, 49, 0, 0, 48, 50, + 49, 0, 0, 0, 45, 0, 0, 48, 50, 49, + 0, 0, 0, 0, 45, 0, 0, 78, 79, 45, + 0, 48, 50, 49, 45, 80, 81, 78, 79, 82, + 0, 83, 0, 45, 0, 80, 81, 78, 79, 82, + 0, 83, 34, 279, 280, 80, 81, 45, 0, 82, + 34, 83, 281, 34, 0, 282, 0, 283, 6, 5, + 4, 1, 3, 2, 34, 0, 0, 0, 0, 0, + 0, -345, 0, 0, 245, 244, 0, 0, 0, 48, + 50, 49, 245, 244, 0, 240, 239, 48, 50, 49, + 48, 50, 49, 0, 0, 0, 0, 0, 0, 0, + 34, 48, 50, 49, 0, 45, 0, 0, 34, 0, + 0, 34, 0, 45, 0, 0, 45, 0, 0, 0, + 0, 0, 78, 79, 0, 0, 0, 45, 0, 0, + 80, 81, 245, 244, 82, 0, 83, 48, 50, 49, + 240, 239, 151, 240, 239, 48, 50, 49, 48, 50, + 49, 0, 152, 0, 0, 0, 153, 0, 0, 0, + 0, 0, 0, 45, 0, 154, 0, 155, 0, 0, + 308, 45, 0, 0, 45, 0, 0, 0, 156, 0, + 157, 62, 0, 0, 0, 0, 0, 0, 158, 0, + 0, 159, 63, 0, 0, 0, 0, 160, 0, 30, + 31, 151, 0, 161, 0, 0, 0, 0, 0, 33, + 0, 152, 0, 0, 0, 153, 34, 0, 0, 162, + 35, 36, 0, 37, 154, 0, 155, 0, 0, 0, + 503, 0, 0, 0, 44, 0, 0, 156, 0, 157, + 62, 0, 0, 0, 0, 0, 0, 158, 0, 0, + 159, 63, 51, 48, 50, 49, 160, 52, 0, 0, + 0, 0, 161, 0, 0, 0, 0, 0, 43, 54, + 32, 0, 30, 31, 40, 0, 0, 0, 162, 45, + 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 41, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 503, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 30, 31, 0, 0, 0, 0, 0, 43, + 54, 32, 33, 0, 0, 40, 0, 0, 0, 34, + 45, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 41, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 503, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 30, 31, 0, 0, 0, 0, 0, 43, + 54, 32, 33, 0, 0, 40, 0, 0, 0, 34, + 45, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 503, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 41, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, + 54, 32, 0, 0, 0, 40, 0, 0, 0, 0, + 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 502, 0, 30, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 215, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 0, 0, 0, 503, 0, 0, 0, 44, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 504, 506, 505, 0, + 52, 0, 0, 0, 0, 226, 0, 0, 0, 0, + 0, 43, 54, 32, 210, 0, 0, 40, 0, 0, + 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 502, 0, 30, 31, 0, 0, 0, 0, + 0, 0, 0, 0, 215, 0, 0, 0, 0, 0, + 0, 34, 0, 0, 0, 35, 36, 0, 37, 0, + 0, 0, 0, 0, 0, 503, 0, 0, 0, 44, + 0, 0, 0, 0, 0, 0, 0, 550, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 51, 504, 506, + 505, 0, 52, 0, 0, 0, 0, 226, 0, 0, + 0, 0, 0, 43, 54, 32, 210, 0, 0, 40, + 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 502, 0, 30, 31, 0, 0, + 0, 0, 0, 0, 0, 0, 215, 0, 0, 0, + 0, 0, 0, 34, 0, 0, 0, 35, 36, 0, + 37, 0, 0, 0, 0, 0, 0, 503, 0, 0, + 0, 44, 0, 0, 0, 0, 0, 0, 0, 547, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, + 504, 506, 505, 0, 52, 0, 0, 0, 0, 226, + 0, 0, 0, 0, 0, 43, 54, 32, 210, 0, + 0, 40, 0, 0, 0, 0, 45, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 29, 30, 31, 0, + 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, + 0, 0, 0, 0, 34, 0, 0, 0, 35, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 41, 42, + 0, 0, 44, 0, 0, 0, 46, 0, 47, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 51, 48, 50, 49, 0, 52, 0, 53, 0, 55, + 0, 56, 0, 0, 0, 0, 43, 54, 32, 0, + 0, 0, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -122, 0, 0, + 0, 29, 30, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 38, 0, 39, 41, 42, 0, 0, 44, 0, 0, + 0, 46, 0, 47, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 48, 50, 49, 0, + 52, 0, 53, 0, 55, 0, 56, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 29, 30, 31, 0, 0, 0, 0, 0, + 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, + 34, 0, 0, 0, 35, 36, 0, 37, 0, 0, + 0, 38, 0, 39, 41, 42, 0, 0, 44, 0, + 0, 0, 46, 0, 47, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 51, 48, 50, 49, + 0, 52, 0, 53, 0, 55, 271, 56, 0, 0, + 0, 0, 43, 54, 32, 0, 0, 0, 40, 0, + 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 475, 0, 0, 29, 30, 31, 0, + 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, + 0, 0, 0, 0, 34, 0, 0, 0, 35, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 41, 42, + 0, 0, 44, 0, 0, 0, 46, 0, 47, 0, + 0, 476, 0, 0, 0, 0, 0, 0, 0, 0, + 51, 48, 50, 49, 0, 52, 0, 53, 0, 55, + 0, 56, 0, 0, 0, 0, 43, 54, 32, 0, + 0, 0, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 475, 0, 0, + 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, + 0, 33, 0, 0, 0, 0, 0, 0, 34, 0, + 0, 0, 35, 36, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 481, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 48, 50, 49, 0, 52, + 0, 53, 0, 55, 0, 56, 0, 0, 0, 0, + 43, 54, 32, 0, 0, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 483, 0, 0, 29, 30, 31, 0, 0, 0, + 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, + 0, 0, 34, 0, 0, 0, 35, 36, 0, 37, + 0, 0, 0, 38, 0, 39, 41, 42, 0, 0, + 44, 0, 0, 0, 46, 0, 47, 0, 0, 484, + 0, 0, 0, 0, 0, 0, 0, 0, 51, 48, + 50, 49, 0, 52, 0, 53, 0, 55, 0, 56, + 0, 0, 0, 0, 43, 54, 32, 0, 0, 0, + 40, 0, 0, 0, 0, 45, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 483, 0, 0, 29, 30, + 31, 0, 0, 0, 0, 0, 0, 0, 0, 33, + 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, + 35, 36, 0, 37, 0, 0, 0, 38, 0, 39, + 41, 42, 0, 0, 44, 0, 0, 0, 46, 0, + 47, 0, 0, 486, 0, 0, 0, 0, 0, 0, + 0, 0, 51, 48, 50, 49, 0, 52, 0, 53, + 0, 55, 0, 56, 0, 0, 0, 0, 43, 54, + 32, 0, 0, 0, 40, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, + 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 217, 0, + 0, 218, 36, 0, 37, 0, 0, 0, 38, 0, + 39, 41, 42, 0, 0, 44, 0, 0, 0, 46, + 0, 47, 0, 0, 0, 0, 0, 0, 0, 221, + 0, 0, 0, 51, 48, 50, 49, 223, 52, 0, + 53, 225, 55, 0, 56, 0, 228, 0, 0, 43, + 54, 32, 0, 0, 0, 40, 0, 0, 0, 0, + 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, + 0, 33, 0, 0, 0, 0, 0, 0, 34, 217, + 0, 0, 582, 583, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 0, 0, 0, 0, 0, + 221, 0, 0, 0, 51, 48, 50, 49, 223, 52, + 0, 53, 225, 55, 0, 56, 0, 228, 0, 0, + 43, 54, 32, 0, 0, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 109, 110, 111, 0, 0, 113, 115, 116, 0, + 0, 117, 0, 118, 0, 0, 0, 120, 121, 122, + 0, 0, 0, 0, 0, 0, 34, 123, 124, 125, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, + 0, 0, 0, 48, 50, 49, 130, 131, 132, 0, + 134, 135, 136, 137, 138, 139, 0, 0, 127, 133, + 119, 112, 114, 128, 0, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 110, 111, 0, 0, 113, 115, 116, 0, 0, 117, + 0, 118, 0, 0, 0, 120, 121, 122, 0, 0, + 0, 0, 0, 0, 393, 123, 124, 125, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, + 0, 0, 394, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, + 398, 395, 397, 0, 130, 131, 132, 0, 134, 135, + 136, 137, 138, 139, 0, 0, 127, 133, 119, 112, + 114, 128, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 109, 110, 111, + 0, 0, 113, 115, 116, 0, 0, 117, 0, 118, + 0, 0, 0, 120, 121, 122, 0, 0, 0, 0, + 0, 0, 393, 123, 124, 125, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, + 394, 0, 0, 0, 0, 0, 0, 0, 396, 0, + 0, 0, 129, 0, 0, 0, 0, 0, 398, 395, + 397, 0, 130, 131, 132, 0, 134, 135, 136, 137, + 138, 139, 0, 0, 127, 133, 119, 112, 114, 128, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 209, 0, 0, 0, 0, + 211, 0, 29, 30, 31, 213, 0, 0, 0, 0, + 0, 0, 214, 215, 0, 0, 0, 0, 0, 0, + 216, 217, 0, 0, 218, 36, 0, 37, 0, 0, + 0, 38, 0, 39, 41, 42, 0, 0, 44, 0, + 0, 0, 46, 0, 47, 0, 0, 0, 0, 0, + 220, 0, 221, 0, 0, 0, 51, 219, 222, 49, + 223, 52, 224, 53, 225, 55, 226, 56, 227, 228, + 0, 0, 43, 54, 32, 210, 212, 0, 40, 0, + 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 209, 0, 0, 0, 0, 211, 0, + 29, 30, 31, 213, 0, 0, 0, 0, 0, 0, + 214, 33, 0, 0, 0, 0, 0, 0, 216, 217, + 0, 0, 218, 36, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 0, 0, 0, 220, 0, + 221, 0, 0, 0, 51, 219, 222, 49, 223, 52, + 224, 53, 225, 55, 226, 56, 227, 228, 0, 0, + 43, 54, 32, 210, 212, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 586, 110, 111, 0, 0, 588, 115, 590, 30, + 31, 591, 0, 118, 0, 0, 0, 120, 593, 594, + 0, 0, 0, 0, 0, 0, 595, 596, 124, 125, + 218, 36, 0, 37, 0, 0, 0, 38, 0, 39, + 597, 42, 0, 0, 599, 0, 0, 0, 46, 0, + 47, 0, 0, 0, 0, 0, 601, 0, 221, 0, + 0, 0, 603, 600, 602, 49, 604, 605, 606, 53, + 608, 609, 610, 611, 612, 613, 0, 0, 598, 607, + 592, 587, 589, 128, 40, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 361, + 110, 111, 0, 0, 363, 115, 365, 30, 31, 366, + 0, 118, 0, 0, 0, 120, 368, 369, 0, 0, + 0, 0, 0, 0, 370, 371, 124, 125, 218, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 372, 42, + 0, 0, 374, 0, 0, 0, 46, 0, 47, 0, + -268, 0, 0, 0, 376, 0, 221, 0, 0, 0, + 378, 375, 377, 49, 379, 380, 381, 53, 383, 384, + 385, 386, 387, 388, 0, 0, 373, 382, 367, 362, + 364, 128, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + + 534, 311, 497, 309, 532, 461, 498, 499, 516, 515, + 619, 638, 16, 552, 436, 358, 616, 472, 562, 320, + 528, 238, 487, 182, 250, 243, 253, 182, 302, 641, + 627, 632, 150, 485, 143, 454, 439, 402, 445, 559, + 237, 574, 250, 578, 561, 186, 618, 458, 238, 349, + 573, 449, 447, 571, 243, 347, 450, 243, 460, 351, + 238, 353, 358, 410, 415, 439, 176, 188, 436, 250, + 467, 417, 433, 182, 425, 429, 302, 169, 456, 358, + 171, 140, 336, 334, 338, 344, 436, 392, 390, 400, + 163, 302, 307, 148, 146, 339, 439, 404, 302, 358, + 404, 358, 0, 482, 501, 480, 0, 642, 0, 479, + 0, 0, 0, 320, 60, 0, 186, 501, 90, 60, + 60, 489, 302, 60, 617, 93, 0, 88, 0, 405, + 0, 461, 405, 60, 60, 451, 180, 60, 0, 180, + 60, 60, 60, 451, 60, 95, 89, 146, 266, 287, + 60, 146, 407, 270, 60, 288, 178, 60, 106, 452, + 0, 60, 60, 60, 102, 60, 302, 332, 286, 60, + 92, 452, 60, 60, 451, 60, 165, 168, 285, 432, + 284, 435, 60, 60, 108, 501, 329, 94, 540, 96, + 60, 330, 60, 302, 494, 60, 77, 237, 60, 404, + 452, 341, 471, 72, 60, 60, 67, 69, 60, 60, + 68, 0, 70, 60, 60, 60, 61, 180, 60, 60, + 98, 491, 60, 91, 490, 60, 60, 60, 493, 60, + 84, 405, 60, 97, 492, 305, 0, 60, 0, 298, + 0, 100, 270, 298, 270, 298, 106, 298, 270, 0, + 270, 60, 270, 60, 316, 0, 270, 0, 270, 298, + 291, 326, 303, 60, 270, 319, 313, 300, 270, 297, + 60, 60, 108, 175, 295, 270, 270, 290, 60, 501, + 273, 317, 60, 270, 60, 278, 509, 270, 0, 270, + 0, 289, 0, 548, 0, 293, 551, 0, 500, 510, + 501, 501, 0, 544, 501, 0, 0, 0, 509, 0, + 0, 509, 520, 521, 522, 523, 527, 524, 525, 0, + 500, 510, 0, 500, 510, 564, 520, 521, 522, 523, + 527, 524, 525, 581, 0, 0, 0, 0, 0, 0, + 584, 585, 520, 521, 522, 523, 527, 524, 525, 556, + 0, 0, 0, 0, 0, 0, 557, 558, 520, 521, + 522, 523, 527, 524, 525, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 556, 0, 0, 540, 0, 614, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 472, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + +const short QmlJSGrammar::action_check [] = { + 36, 36, 24, 7, 55, 7, 7, 7, 60, 36, + 8, 7, 37, 7, 7, 33, 55, 55, 60, 36, + 36, 33, 33, 55, 8, 33, 7, 7, 34, 36, + 16, 20, 7, 36, 7, 7, 33, 36, 33, 60, + 7, 7, 7, 5, 7, 5, 5, 90, 33, 36, + 7, 33, 7, 36, 7, 33, 66, 36, 7, 36, + 29, 7, 33, 31, 1, 7, 17, 55, 60, 33, + 60, 2, 8, 7, 48, 66, 1, 7, 36, 2, + 8, 7, 36, 2, 60, 2, 8, 7, 17, 1, + 60, 36, 0, 36, 8, 36, 48, 36, 33, 8, + 61, 1, 6, 36, 60, 8, -1, 8, -1, 15, + 48, 10, 7, 8, 61, 8, 20, -1, -1, -1, + -1, -1, 8, -1, -1, -1, -1, 48, 34, 79, + 40, 79, 8, 77, 61, 8, 60, 8, 8, 79, + 8, 51, -1, 40, 42, 61, 62, 8, 61, 62, + 8, 7, -1, 56, 51, 53, 55, 8, 90, 60, + 50, 79, 8, 56, 54, 61, 62, 61, 62, 61, + 62, 40, 61, 62, 60, 40, 61, 62, 61, 62, + 56, 8, 51, 56, 50, 7, 51, 15, 54, 60, + 60, 25, 60, 27, 25, 56, 27, 61, 62, 61, + 62, 36, 60, 29, 38, 29, 34, 38, 36, 25, + 61, 27, 29, -1, 60, 25, 12, 27, 29, 25, + 29, 27, 38, 25, 29, 27, 61, 62, 38, 12, + 7, 12, 38, 60, 8, 25, 38, 27, -1, 61, + 62, 25, 8, 27, 7, -1, 15, 7, 38, 75, + 25, 75, 27, 8, 38, 25, 33, 27, 75, -1, + 86, 57, 86, 38, 75, 34, 75, 63, 38, 86, + 75, 15, 15, -1, 57, 86, 57, 86, -1, -1, + 63, 86, 63, 61, 62, -1, -1, 61, 62, 15, + 34, 34, 36, 36, -1, 61, 62, 47, 61, 62, + 15, 61, 62, -1, 18, 19, 61, 62, 34, 15, + 36, 61, 62, 91, -1, 92, 18, 19, 33, 34, + 29, 36, 18, 19, 18, 19, 29, 33, 34, 29, + 36, 45, 46, 25, 29, 27, -1, -1, -1, -1, + -1, 91, -1, 45, 46, -1, 38, -1, -1, 45, + 46, 45, 46, -1, -1, -1, -1, 66, 67, 68, + -1, -1, -1, 66, 67, 68, 66, 67, 68, -1, + -1, 66, 67, 68, -1, -1, 23, 24, -1, -1, + -1, 29, -1, 92, -1, 32, 23, 24, 35, 92, + 37, 29, 92, 23, 24, 32, 29, 92, 35, -1, + 37, 29, 32, 23, 24, 35, -1, 37, -1, -1, + 29, -1, 32, 23, 24, 35, -1, 37, 66, 67, + 68, 31, 32, -1, 29, 35, -1, 37, 66, 67, + 68, 36, -1, 66, 67, 68, -1, -1, 66, 67, + 68, -1, -1, -1, 92, -1, -1, 66, 67, 68, + -1, -1, -1, -1, 92, -1, -1, 23, 24, 92, + -1, 66, 67, 68, 92, 31, 32, 23, 24, 35, + -1, 37, -1, 92, -1, 31, 32, 23, 24, 35, + -1, 37, 29, 23, 24, 31, 32, 92, -1, 35, + 29, 37, 32, 29, -1, 35, -1, 37, 94, 95, + 96, 97, 98, 99, 29, -1, -1, -1, -1, -1, + -1, 36, -1, -1, 61, 62, -1, -1, -1, 66, + 67, 68, 61, 62, -1, 61, 62, 66, 67, 68, + 66, 67, 68, -1, -1, -1, -1, -1, -1, -1, + 29, 66, 67, 68, -1, 92, -1, -1, 29, -1, + -1, 29, -1, 92, -1, -1, 92, -1, -1, -1, + -1, -1, 23, 24, -1, -1, -1, 92, -1, -1, + 31, 32, 61, 62, 35, -1, 37, 66, 67, 68, + 61, 62, 3, 61, 62, 66, 67, 68, 66, 67, + 68, -1, 13, -1, -1, -1, 17, -1, -1, -1, + -1, -1, -1, 92, -1, 26, -1, 28, -1, -1, + 31, 92, -1, -1, 92, -1, -1, -1, 39, -1, + 41, 42, -1, -1, -1, -1, -1, -1, 49, -1, + -1, 52, 53, -1, -1, -1, -1, 58, -1, 12, + 13, 3, -1, 64, -1, -1, -1, -1, -1, 22, + -1, 13, -1, -1, -1, 17, 29, -1, -1, 80, + 33, 34, -1, 36, 26, -1, 28, -1, -1, -1, + 43, -1, -1, -1, 47, -1, -1, 39, -1, 41, + 42, -1, -1, -1, -1, -1, -1, 49, -1, -1, + 52, 53, 65, 66, 67, 68, 58, 70, -1, -1, + -1, -1, 64, -1, -1, -1, -1, -1, 81, 82, + 83, -1, 12, 13, 87, -1, -1, -1, 80, 92, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, 12, 13, -1, -1, -1, -1, -1, 81, + 82, 83, 22, -1, -1, 87, -1, -1, -1, 29, + 92, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, 12, 13, -1, -1, -1, -1, -1, 81, + 82, 83, 22, -1, -1, 87, -1, -1, -1, 29, + 92, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, -1, -1, -1, 87, -1, -1, -1, -1, + 92, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 10, -1, 12, 13, -1, -1, -1, -1, -1, -1, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + -1, -1, -1, 43, -1, -1, -1, 47, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 65, 66, 67, 68, -1, + 70, -1, -1, -1, -1, 75, -1, -1, -1, -1, + -1, 81, 82, 83, 84, -1, -1, 87, -1, -1, + -1, -1, 92, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 10, -1, 12, 13, -1, -1, -1, -1, + -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, + -1, 29, -1, -1, -1, 33, 34, -1, 36, -1, + -1, -1, -1, -1, -1, 43, -1, -1, -1, 47, + -1, -1, -1, -1, -1, -1, -1, 55, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 65, 66, 67, + 68, -1, 70, -1, -1, -1, -1, 75, -1, -1, + -1, -1, -1, 81, 82, 83, 84, -1, -1, 87, + -1, -1, -1, -1, 92, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 10, -1, 12, 13, -1, -1, + -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, + -1, -1, -1, 29, -1, -1, -1, 33, 34, -1, + 36, -1, -1, -1, -1, -1, -1, 43, -1, -1, + -1, 47, -1, -1, -1, -1, -1, -1, -1, 55, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 65, + 66, 67, 68, -1, 70, -1, -1, -1, -1, 75, + -1, -1, -1, -1, -1, 81, 82, 83, 84, -1, + -1, 87, -1, -1, -1, -1, 92, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 11, 12, 13, -1, + -1, -1, -1, -1, -1, -1, -1, 22, -1, -1, + -1, -1, -1, -1, 29, -1, -1, -1, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 65, 66, 67, 68, -1, 70, -1, 72, -1, 74, + -1, 76, -1, -1, -1, -1, 81, 82, 83, -1, + -1, -1, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, + -1, 11, 12, 13, -1, -1, -1, -1, -1, -1, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 65, 66, 67, 68, -1, + 70, -1, 72, -1, 74, -1, 76, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 11, 12, 13, -1, -1, -1, -1, -1, + -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, + 29, -1, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 65, 66, 67, 68, + -1, 70, -1, 72, -1, 74, 75, 76, -1, -1, + -1, -1, 81, 82, 83, -1, -1, -1, 87, -1, + -1, -1, -1, 92, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 8, -1, -1, 11, 12, 13, -1, + -1, -1, -1, -1, -1, -1, -1, 22, -1, -1, + -1, -1, -1, -1, 29, -1, -1, -1, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + -1, 56, -1, -1, -1, -1, -1, -1, -1, -1, + 65, 66, 67, 68, -1, 70, -1, 72, -1, 74, + -1, 76, -1, -1, -1, -1, 81, 82, 83, -1, + -1, -1, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, + 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, + -1, 22, -1, -1, -1, -1, -1, -1, 29, -1, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, 56, -1, -1, -1, -1, + -1, -1, -1, -1, 65, 66, 67, 68, -1, 70, + -1, 72, -1, 74, -1, 76, -1, -1, -1, -1, + 81, 82, 83, -1, -1, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 8, -1, -1, 11, 12, 13, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, + -1, -1, 29, -1, -1, -1, 33, 34, -1, 36, + -1, -1, -1, 40, -1, 42, 43, 44, -1, -1, + 47, -1, -1, -1, 51, -1, 53, -1, -1, 56, + -1, -1, -1, -1, -1, -1, -1, -1, 65, 66, + 67, 68, -1, 70, -1, 72, -1, 74, -1, 76, + -1, -1, -1, -1, 81, 82, 83, -1, -1, -1, + 87, -1, -1, -1, -1, 92, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 8, -1, -1, 11, 12, + 13, -1, -1, -1, -1, -1, -1, -1, -1, 22, + -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, + 33, 34, -1, 36, -1, -1, -1, 40, -1, 42, + 43, 44, -1, -1, 47, -1, -1, -1, 51, -1, + 53, -1, -1, 56, -1, -1, -1, -1, -1, -1, + -1, -1, 65, 66, 67, 68, -1, 70, -1, 72, + -1, 74, -1, 76, -1, -1, -1, -1, 81, 82, + 83, -1, -1, -1, 87, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, + 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, 30, -1, + -1, 33, 34, -1, 36, -1, -1, -1, 40, -1, + 42, 43, 44, -1, -1, 47, -1, -1, -1, 51, + -1, 53, -1, -1, -1, -1, -1, -1, -1, 61, + -1, -1, -1, 65, 66, 67, 68, 69, 70, -1, + 72, 73, 74, -1, 76, -1, 78, -1, -1, 81, + 82, 83, -1, -1, -1, 87, -1, -1, -1, -1, + 92, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, + -1, 22, -1, -1, -1, -1, -1, -1, 29, 30, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, -1, -1, -1, -1, -1, + 61, -1, -1, -1, 65, 66, 67, 68, 69, 70, + -1, 72, 73, 74, -1, 76, -1, 78, -1, -1, + 81, 82, 83, -1, -1, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 5, 6, -1, -1, 9, 10, 11, -1, + -1, 14, -1, 16, -1, -1, -1, 20, 21, 22, + -1, -1, -1, -1, -1, -1, 29, 30, 31, 32, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 43, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 59, -1, -1, -1, + -1, -1, -1, 66, 67, 68, 69, 70, 71, -1, + 73, 74, 75, 76, 77, 78, -1, -1, 81, 82, + 83, 84, 85, 86, -1, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, + 5, 6, -1, -1, 9, 10, 11, -1, -1, 14, + -1, 16, -1, -1, -1, 20, 21, 22, -1, -1, + -1, -1, -1, -1, 29, 30, 31, 32, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 43, -1, + -1, -1, 47, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 59, -1, -1, -1, -1, -1, + 65, 66, 67, -1, 69, 70, 71, -1, 73, 74, + 75, 76, 77, 78, -1, -1, 81, 82, 83, 84, + 85, 86, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 4, 5, 6, + -1, -1, 9, 10, 11, -1, -1, 14, -1, 16, + -1, -1, -1, 20, 21, 22, -1, -1, -1, -1, + -1, -1, 29, 30, 31, 32, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 43, -1, -1, -1, + 47, -1, -1, -1, -1, -1, -1, -1, 55, -1, + -1, -1, 59, -1, -1, -1, -1, -1, 65, 66, + 67, -1, 69, 70, 71, -1, 73, 74, 75, 76, + 77, 78, -1, -1, 81, 82, 83, 84, 85, 86, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, + 9, -1, 11, 12, 13, 14, -1, -1, -1, -1, + -1, -1, 21, 22, -1, -1, -1, -1, -1, -1, + 29, 30, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + 59, -1, 61, -1, -1, -1, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + -1, -1, 81, 82, 83, 84, 85, -1, 87, -1, + -1, -1, -1, 92, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 4, -1, -1, -1, -1, 9, -1, + 11, 12, 13, 14, -1, -1, -1, -1, -1, -1, + 21, 22, -1, -1, -1, -1, -1, -1, 29, 30, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, -1, -1, -1, 59, -1, + 61, -1, -1, -1, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, -1, -1, + 81, 82, 83, 84, 85, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 5, 6, -1, -1, 9, 10, 11, 12, + 13, 14, -1, 16, -1, -1, -1, 20, 21, 22, + -1, -1, -1, -1, -1, -1, 29, 30, 31, 32, + 33, 34, -1, 36, -1, -1, -1, 40, -1, 42, + 43, 44, -1, -1, 47, -1, -1, -1, 51, -1, + 53, -1, -1, -1, -1, -1, 59, -1, 61, -1, + -1, -1, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, -1, -1, 81, 82, + 83, 84, 85, 86, 87, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, + 5, 6, -1, -1, 9, 10, 11, 12, 13, 14, + -1, 16, -1, -1, -1, 20, 21, 22, -1, -1, + -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + 55, -1, -1, -1, 59, -1, 61, -1, -1, -1, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, -1, -1, 81, 82, 83, 84, + 85, 86, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + + 15, 2, 105, 3, 29, 15, 4, 2, 15, 29, + 9, 15, 3, 15, 3, 2, 19, 39, 15, 15, + 13, 15, 3, 15, 2, 15, 3, 15, 3, 11, + 13, 15, 71, 39, 39, 3, 22, 2, 99, 19, + 4, 15, 2, 15, 29, 15, 19, 3, 15, 3, + 29, 22, 15, 29, 15, 2, 22, 15, 2, 2, + 15, 2, 2, 2, 2, 22, 3, 15, 3, 2, + 39, 3, 3, 15, 97, 94, 3, 39, 2, 2, + 39, 3, 3, 2, 2, 101, 3, 40, 39, 39, + 39, 3, 2, 39, 39, 15, 22, 13, 3, 2, + 13, 2, -1, 39, 13, 35, -1, 16, -1, 39, + -1, -1, -1, 15, 48, -1, 15, 13, 52, 48, + 48, 50, 3, 48, 20, 53, -1, 52, -1, 45, + -1, 15, 45, 48, 48, 50, 50, 48, -1, 50, + 48, 48, 48, 50, 48, 53, 52, 39, 48, 53, + 48, 39, 44, 53, 48, 53, 44, 48, 15, 50, + -1, 48, 48, 48, 58, 48, 3, 72, 53, 48, + 53, 50, 48, 48, 50, 48, 62, 64, 53, 82, + 53, 82, 48, 48, 41, 13, 88, 53, 16, 54, + 48, 72, 48, 3, 50, 48, 54, 4, 48, 13, + 50, 100, 86, 56, 48, 48, 50, 50, 48, 48, + 50, -1, 51, 48, 48, 48, 51, 50, 48, 48, + 54, 50, 48, 53, 50, 48, 48, 48, 50, 48, + 53, 45, 48, 54, 50, 72, -1, 48, -1, 48, + -1, 60, 53, 48, 53, 48, 15, 48, 53, -1, + 53, 48, 53, 48, 65, -1, 53, -1, 53, 48, + 55, 70, 72, 48, 53, 70, 63, 70, 53, 70, + 48, 48, 41, 42, 59, 53, 53, 55, 48, 13, + 57, 70, 48, 53, 48, 55, 20, 53, -1, 53, + -1, 55, -1, 5, -1, 61, 5, -1, 32, 33, + 13, 13, -1, 16, 13, -1, -1, -1, 20, -1, + -1, 20, 22, 23, 24, 25, 26, 27, 28, -1, + 32, 33, -1, 32, 33, 21, 22, 23, 24, 25, + 26, 27, 28, 13, -1, -1, -1, -1, -1, -1, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 13, + -1, -1, -1, -1, -1, -1, 20, 21, 22, 23, + 24, 25, 26, 27, 28, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 13, -1, -1, 16, -1, 18, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1}; + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsgrammar_p.h b/src/lib/corelib/parser/qmljsgrammar_p.h new file mode 100644 index 000000000..545476e60 --- /dev/null +++ b/src/lib/corelib/parser/qmljsgrammar_p.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// This file was generated by qlalr - DO NOT EDIT! +#ifndef QMLJSGRAMMAR_P_H +#define QMLJSGRAMMAR_P_H + +#include "qmljsglobal_p.h" +#include <QtCore/qglobal.h> + +namespace QbsQmlJS { + +class QML_PARSER_EXPORT QmlJSGrammar +{ +public: + enum VariousConstants { + EOF_SYMBOL = 0, + REDUCE_HERE = 101, + SHIFT_THERE = 100, + T_AND = 1, + T_AND_AND = 2, + T_AND_EQ = 3, + T_AS = 91, + T_AUTOMATIC_SEMICOLON = 62, + T_BREAK = 4, + T_CASE = 5, + T_CATCH = 6, + T_COLON = 7, + T_COMMA = 8, + T_COMMENT = 88, + T_CONST = 84, + T_CONTINUE = 9, + T_DEBUGGER = 85, + T_DEFAULT = 10, + T_DELETE = 11, + T_DIVIDE_ = 12, + T_DIVIDE_EQ = 13, + T_DO = 14, + T_DOT = 15, + T_ELSE = 16, + T_EQ = 17, + T_EQ_EQ = 18, + T_EQ_EQ_EQ = 19, + T_ERROR = 93, + T_FALSE = 83, + T_FEED_JS_EXPRESSION = 97, + T_FEED_JS_PROGRAM = 99, + T_FEED_JS_SOURCE_ELEMENT = 98, + T_FEED_JS_STATEMENT = 96, + T_FEED_UI_OBJECT_MEMBER = 95, + T_FEED_UI_PROGRAM = 94, + T_FINALLY = 20, + T_FOR = 21, + T_FUNCTION = 22, + T_GE = 23, + T_GT = 24, + T_GT_GT = 25, + T_GT_GT_EQ = 26, + T_GT_GT_GT = 27, + T_GT_GT_GT_EQ = 28, + T_IDENTIFIER = 29, + T_IF = 30, + T_IMPORT = 90, + T_IN = 31, + T_INSTANCEOF = 32, + T_LBRACE = 33, + T_LBRACKET = 34, + T_LE = 35, + T_LPAREN = 36, + T_LT = 37, + T_LT_LT = 38, + T_LT_LT_EQ = 39, + T_MINUS = 40, + T_MINUS_EQ = 41, + T_MINUS_MINUS = 42, + T_MULTILINE_STRING_LITERAL = 87, + T_NEW = 43, + T_NOT = 44, + T_NOT_EQ = 45, + T_NOT_EQ_EQ = 46, + T_NULL = 81, + T_NUMERIC_LITERAL = 47, + T_ON = 92, + T_OR = 48, + T_OR_EQ = 49, + T_OR_OR = 50, + T_PLUS = 51, + T_PLUS_EQ = 52, + T_PLUS_PLUS = 53, + T_PROPERTY = 66, + T_PUBLIC = 89, + T_QUESTION = 54, + T_RBRACE = 55, + T_RBRACKET = 56, + T_READONLY = 68, + T_REMAINDER = 57, + T_REMAINDER_EQ = 58, + T_RESERVED_WORD = 86, + T_RETURN = 59, + T_RPAREN = 60, + T_SEMICOLON = 61, + T_SIGNAL = 67, + T_STAR = 63, + T_STAR_EQ = 64, + T_STRING_LITERAL = 65, + T_SWITCH = 69, + T_THIS = 70, + T_THROW = 71, + T_TILDE = 72, + T_TRUE = 82, + T_TRY = 73, + T_TYPEOF = 74, + T_VAR = 75, + T_VOID = 76, + T_WHILE = 77, + T_WITH = 78, + T_XOR = 79, + T_XOR_EQ = 80, + + ACCEPT_STATE = 644, + RULE_COUNT = 349, + STATE_COUNT = 645, + TERMINAL_COUNT = 102, + NON_TERMINAL_COUNT = 107, + + GOTO_INDEX_OFFSET = 645, + GOTO_INFO_OFFSET = 2807, + GOTO_CHECK_OFFSET = 2807 + }; + + static const char *const spell []; + static const short lhs []; + static const short rhs []; + static const short goto_default []; + static const short action_default []; + static const short action_index []; + static const short action_info []; + static const short action_check []; + + static inline int nt_action (int state, int nt) + { + const int yyn = action_index [GOTO_INDEX_OFFSET + state] + nt; + if (yyn < 0 || action_check [GOTO_CHECK_OFFSET + yyn] != nt) + return goto_default [nt]; + + return action_info [GOTO_INFO_OFFSET + yyn]; + } + + static inline int t_action (int state, int token) + { + const int yyn = action_index [state] + token; + + if (yyn < 0 || action_check [yyn] != token) + return - action_default [state]; + + return action_info [yyn]; + } +}; + + +} // namespace QbsQmlJS + +#endif // QMLJSGRAMMAR_P_H + diff --git a/src/lib/corelib/parser/qmljskeywords_p.h b/src/lib/corelib/parser/qmljskeywords_p.h new file mode 100644 index 000000000..373d2ad01 --- /dev/null +++ b/src/lib/corelib/parser/qmljskeywords_p.h @@ -0,0 +1,852 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSKEYWORDS_P_H +#define QMLJSKEYWORDS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace QbsQmlJS { + +static inline int classify2(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'a') { + if (s[1].unicode() == 's') { + return qmlMode ? Lexer::T_AS : Lexer::T_RESERVED_WORD; + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'o') { + return Lexer::T_DO; + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'f') { + return Lexer::T_IF; + } + else if (s[1].unicode() == 'n') { + return Lexer::T_IN; + } + } + else if (qmlMode && s[0].unicode() == 'o') { + if (s[1].unicode() == 'n') { + return Lexer::T_ON; + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify3(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'r') { + return Lexer::T_FOR; + } + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'n') { + if (s[2].unicode() == 't') { + return Lexer::T_INT; + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'w') { + return Lexer::T_NEW; + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'y') { + return Lexer::T_TRY; + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'r') { + return Lexer::T_VAR; + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify4(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'y') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + return Lexer::T_BYTE; + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 'e') { + return Lexer::T_CASE; + } + } + } + else if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'r') { + return Lexer::T_CHAR; + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'l') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 'e') { + return Lexer::T_ELSE; + } + } + } + else if (s[1].unicode() == 'n') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'm') { + return Lexer::T_ENUM; + } + } + } + } + else if (s[0].unicode() == 'g') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'o') { + return Lexer::T_GOTO; + } + } + } + } + else if (s[0].unicode() == 'l') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'g') { + return Lexer::T_LONG; + } + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'l') { + return Lexer::T_NULL; + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 's') { + return Lexer::T_THIS; + } + } + } + else if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'e') { + return Lexer::T_TRUE; + } + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'd') { + return Lexer::T_VOID; + } + } + } + } + else if (s[0].unicode() == 'w') { + if (s[1].unicode() == 'i') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'h') { + return Lexer::T_WITH; + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify5(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'e') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'k') { + return Lexer::T_BREAK; + } + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 'h') { + return Lexer::T_CATCH; + } + } + } + } + else if (s[1].unicode() == 'l') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 's') { + return Lexer::T_CLASS; + } + } + } + } + else if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 't') { + return Lexer::T_CONST; + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 'e') { + return Lexer::T_FALSE; + } + } + } + } + else if (s[1].unicode() == 'i') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'l') { + return Lexer::T_FINAL; + } + } + } + } + else if (s[1].unicode() == 'l') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 't') { + return Lexer::T_FLOAT; + } + } + } + } + } + else if (s[0].unicode() == 's') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'r') { + if (s[4].unicode() == 't') { + return Lexer::T_SHORT; + } + } + } + } + else if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'r') { + return Lexer::T_SUPER; + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'r') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'w') { + return Lexer::T_THROW; + } + } + } + } + } + else if (s[0].unicode() == 'w') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + return Lexer::T_WHILE; + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify6(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'e') { + return Lexer::T_DELETE; + } + } + } + } + } + else if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'b') { + if (s[4].unicode() == 'l') { + if (s[5].unicode() == 'e') { + return Lexer::T_DOUBLE; + } + } + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'x') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 't') { + return Lexer::T_EXPORT; + } + } + } + } + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'm') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 't') { + return qmlMode ? Lexer::T_IMPORT : Lexer::T_RESERVED_WORD; + } + } + } + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'i') { + if (s[4].unicode() == 'v') { + if (s[5].unicode() == 'e') { + return Lexer::T_NATIVE; + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'b') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'c') { + return qmlMode ? Lexer::T_PUBLIC : Lexer::T_RESERVED_WORD; + } + } + } + } + } + } + else if (s[0].unicode() == 'r') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'u') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'n') { + return Lexer::T_RETURN; + } + } + } + } + } + } + else if (s[0].unicode() == 's') { + if (qmlMode && s[1].unicode() == 'i') { + if (s[2].unicode() == 'g') { + if (s[3].unicode() == 'n') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'l') { + return Lexer::T_SIGNAL; + } + } + } + } + } + else if (s[1].unicode() == 't') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'c') { + return Lexer::T_STATIC; + } + } + } + } + } + else if (s[1].unicode() == 'w') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'c') { + if (s[5].unicode() == 'h') { + return Lexer::T_SWITCH; + } + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'r') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'w') { + if (s[5].unicode() == 's') { + return Lexer::T_THROWS; + } + } + } + } + } + else if (s[1].unicode() == 'y') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'o') { + if (s[5].unicode() == 'f') { + return Lexer::T_TYPEOF; + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify7(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'a') { + if (s[6].unicode() == 'n') { + return Lexer::T_BOOLEAN; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'f') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'u') { + if (s[5].unicode() == 'l') { + if (s[6].unicode() == 't') { + return Lexer::T_DEFAULT; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'x') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'n') { + if (s[5].unicode() == 'd') { + if (s[6].unicode() == 's') { + return Lexer::T_EXTENDS; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'i') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'l') { + if (s[5].unicode() == 'l') { + if (s[6].unicode() == 'y') { + return Lexer::T_FINALLY; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'c') { + if (s[3].unicode() == 'k') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'g') { + if (s[6].unicode() == 'e') { + return Lexer::T_PACKAGE; + } + } + } + } + } + } + else if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'v') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 't') { + if (s[6].unicode() == 'e') { + return Lexer::T_PRIVATE; + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify8(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'a') { + if (s[1].unicode() == 'b') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'a') { + if (s[6].unicode() == 'c') { + if (s[7].unicode() == 't') { + return Lexer::T_ABSTRACT; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'u') { + if (s[7].unicode() == 'e') { + return Lexer::T_CONTINUE; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'b') { + if (s[3].unicode() == 'u') { + if (s[4].unicode() == 'g') { + if (s[5].unicode() == 'g') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'r') { + return Lexer::T_DEBUGGER; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'o') { + if (s[7].unicode() == 'n') { + return Lexer::T_FUNCTION; + } + } + } + } + } + } + } + } + else if (qmlMode && s[0].unicode() == 'p') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'p') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'r') { + if (s[6].unicode() == 't') { + if (s[7].unicode() == 'y') { + return Lexer::T_PROPERTY; + } + } + } + } + } + } + } + } + else if (qmlMode && s[0].unicode() == 'r') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'd') { + if (s[4].unicode() == 'o') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'l') { + if (s[7].unicode() == 'y') { + return Lexer::T_READONLY; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'l') { + if (s[7].unicode() == 'e') { + return Lexer::T_VOLATILE; + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify9(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'n') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'f') { + if (s[6].unicode() == 'a') { + if (s[7].unicode() == 'c') { + if (s[8].unicode() == 'e') { + return Lexer::T_INTERFACE; + } + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'c') { + if (s[6].unicode() == 't') { + if (s[7].unicode() == 'e') { + if (s[8].unicode() == 'd') { + return Lexer::T_PROTECTED; + } + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'n') { + if (s[4].unicode() == 's') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 't') { + return Lexer::T_TRANSIENT; + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify10(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'm') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'm') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 't') { + if (s[9].unicode() == 's') { + return Lexer::T_IMPLEMENTS; + } + } + } + } + } + } + } + } + } + else if (s[1].unicode() == 'n') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'c') { + if (s[7].unicode() == 'e') { + if (s[8].unicode() == 'o') { + if (s[9].unicode() == 'f') { + return Lexer::T_INSTANCEOF; + } + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify12(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 's') { + if (s[1].unicode() == 'y') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 'h') { + if (s[5].unicode() == 'r') { + if (s[6].unicode() == 'o') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 'i') { + if (s[9].unicode() == 'z') { + if (s[10].unicode() == 'e') { + if (s[11].unicode() == 'd') { + return Lexer::T_SYNCHRONIZED; + } + } + } + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +int Lexer::classify(const QChar *s, int n, bool qmlMode) { + switch (n) { + case 2: return classify2(s, qmlMode); + case 3: return classify3(s, qmlMode); + case 4: return classify4(s, qmlMode); + case 5: return classify5(s, qmlMode); + case 6: return classify6(s, qmlMode); + case 7: return classify7(s, qmlMode); + case 8: return classify8(s, qmlMode); + case 9: return classify9(s, qmlMode); + case 10: return classify10(s, qmlMode); + case 12: return classify12(s, qmlMode); + default: return Lexer::T_IDENTIFIER; + } // switch +} + +} // namespace QbsQmlJS + +#endif // QMLJSKEYWORDS_P_H diff --git a/src/lib/corelib/parser/qmljslexer.cpp b/src/lib/corelib/parser/qmljslexer.cpp new file mode 100644 index 000000000..515de2654 --- /dev/null +++ b/src/lib/corelib/parser/qmljslexer.cpp @@ -0,0 +1,1141 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 "qmljslexer_p.h" +#include "qmljsengine_p.h" +#include "qmljsmemorypool_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QVarLengthArray> +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE +Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); +QT_END_NAMESPACE + +namespace QbsQmlJS { + +static int regExpFlagFromChar(const QChar &ch) +{ + switch (ch.unicode()) { + case 'g': return Lexer::RegExp_Global; + case 'i': return Lexer::RegExp_IgnoreCase; + case 'm': return Lexer::RegExp_Multiline; + } + return 0; +} + +static unsigned char convertHex(ushort c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (c - 'A' + 10); +} + +static QChar convertHex(QChar c1, QChar c2) +{ + return QChar((convertHex(c1.unicode()) << 4) + convertHex(c2.unicode())); +} + +static QChar convertUnicode(QChar c1, QChar c2, QChar c3, QChar c4) +{ + return QChar((convertHex(c3.unicode()) << 4) + convertHex(c4.unicode()), + (convertHex(c1.unicode()) << 4) + convertHex(c2.unicode())); +} + +Lexer::Lexer(Engine *engine) + : _engine(engine) + , _codePtr(0) + , _lastLinePtr(0) + , _tokenLinePtr(0) + , _tokenStartPtr(0) + , _char(QLatin1Char('\n')) + , _errorCode(NoError) + , _currentLineNumber(0) + , _tokenValue(0) + , _parenthesesState(IgnoreParentheses) + , _parenthesesCount(0) + , _stackToken(-1) + , _patternFlags(0) + , _tokenKind(0) + , _tokenLength(0) + , _tokenLine(0) + , _validTokenText(false) + , _prohibitAutomaticSemicolon(false) + , _restrictedKeyword(false) + , _terminator(false) + , _followsClosingBrace(false) + , _delimited(true) + , _qmlMode(true) +{ + if (engine) + engine->setLexer(this); +} + +bool Lexer::qmlMode() const +{ + return _qmlMode; +} + +QString Lexer::code() const +{ + return _code; +} + +void Lexer::setCode(const QString &code, int lineno, bool qmlMode) +{ + if (_engine) + _engine->setCode(code); + + _qmlMode = qmlMode; + _code = code; + _tokenText.clear(); + _tokenText.reserve(1024); + _errorMessage.clear(); + _tokenSpell = QStringRef(); + + _codePtr = code.unicode(); + _lastLinePtr = _codePtr; + _tokenLinePtr = _codePtr; + _tokenStartPtr = _codePtr; + + _char = QLatin1Char('\n'); + _errorCode = NoError; + + _currentLineNumber = lineno; + _tokenValue = 0; + + // parentheses state + _parenthesesState = IgnoreParentheses; + _parenthesesCount = 0; + + _stackToken = -1; + + _patternFlags = 0; + _tokenLength = 0; + _tokenLine = lineno; + + _validTokenText = false; + _prohibitAutomaticSemicolon = false; + _restrictedKeyword = false; + _terminator = false; + _followsClosingBrace = false; + _delimited = true; +} + +void Lexer::scanChar() +{ + _char = *_codePtr++; + + if (_char == QLatin1Char('\n')) { + _lastLinePtr = _codePtr; // points to the first character after the newline + ++_currentLineNumber; + } +} + +int Lexer::lex() +{ + const int previousTokenKind = _tokenKind; + + _tokenSpell = QStringRef(); + _tokenKind = scanToken(); + _tokenLength = _codePtr - _tokenStartPtr - 1; + + _delimited = false; + _restrictedKeyword = false; + _followsClosingBrace = (previousTokenKind == T_RBRACE); + + // update the flags + switch (_tokenKind) { + case T_LBRACE: + case T_SEMICOLON: + case T_COLON: + _delimited = true; + break; + + case T_IF: + case T_FOR: + case T_WHILE: + case T_WITH: + _parenthesesState = CountParentheses; + _parenthesesCount = 0; + break; + + case T_DO: + _parenthesesState = BalancedParentheses; + break; + + case T_CONTINUE: + case T_BREAK: + case T_RETURN: + case T_THROW: + _restrictedKeyword = true; + break; + } // switch + + // update the parentheses state + switch (_parenthesesState) { + case IgnoreParentheses: + break; + + case CountParentheses: + if (_tokenKind == T_RPAREN) { + --_parenthesesCount; + if (_parenthesesCount == 0) + _parenthesesState = BalancedParentheses; + } else if (_tokenKind == T_LPAREN) { + ++_parenthesesCount; + } + break; + + case BalancedParentheses: + _parenthesesState = IgnoreParentheses; + break; + } // switch + + return _tokenKind; +} + +bool Lexer::isUnicodeEscapeSequence(const QChar *chars) +{ + if (isHexDigit(chars[0]) && isHexDigit(chars[1]) && isHexDigit(chars[2]) && isHexDigit(chars[3])) + return true; + + return false; +} + +QChar Lexer::decodeUnicodeEscapeCharacter(bool *ok) +{ + if (_char == QLatin1Char('u') && isUnicodeEscapeSequence(&_codePtr[0])) { + scanChar(); // skip u + + const QChar c1 = _char; + scanChar(); + + const QChar c2 = _char; + scanChar(); + + const QChar c3 = _char; + scanChar(); + + const QChar c4 = _char; + scanChar(); + + if (ok) + *ok = true; + + return convertUnicode(c1, c2, c3, c4); + } + + *ok = false; + return QChar(); +} + +int Lexer::scanToken() +{ + if (_stackToken != -1) { + int tk = _stackToken; + _stackToken = -1; + return tk; + } + + _terminator = false; + +again: + _validTokenText = false; + _tokenLinePtr = _lastLinePtr; + + while (_char.isSpace()) { + if (_char == QLatin1Char('\n')) { + _tokenLinePtr = _codePtr; + + if (_restrictedKeyword) { + // automatic semicolon insertion + _tokenLine = _currentLineNumber; + _tokenStartPtr = _codePtr - 1; // ### TODO: insert it before the optional \r sequence. + return T_SEMICOLON; + } else { + _terminator = true; + syncProhibitAutomaticSemicolon(); + } + } + + scanChar(); + } + + _tokenStartPtr = _codePtr - 1; + _tokenLine = _currentLineNumber; + + if (_char.isNull()) + return EOF_SYMBOL; + + const QChar ch = _char; + scanChar(); + + switch (ch.unicode()) { + case '~': return T_TILDE; + case '}': return T_RBRACE; + + case '|': + if (_char == QLatin1Char('|')) { + scanChar(); + return T_OR_OR; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_OR_EQ; + } + return T_OR; + + case '{': return T_LBRACE; + + case '^': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_XOR_EQ; + } + return T_XOR; + + case ']': return T_RBRACKET; + case '[': return T_LBRACKET; + case '?': return T_QUESTION; + + case '>': + if (_char == QLatin1Char('>')) { + scanChar(); + if (_char == QLatin1Char('>')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_GT_GT_GT_EQ; + } + return T_GT_GT_GT; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_GT_GT_EQ; + } + return T_GT_GT; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_GE; + } + return T_GT; + + case '=': + if (_char == QLatin1Char('=')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_EQ_EQ_EQ; + } + return T_EQ_EQ; + } + return T_EQ; + + case '<': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_LE; + } else if (_char == QLatin1Char('<')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_LT_LT_EQ; + } + return T_LT_LT; + } + return T_LT; + + case ';': return T_SEMICOLON; + case ':': return T_COLON; + + case '/': + if (_char == QLatin1Char('*')) { + scanChar(); + while (!_char.isNull()) { + if (_char == QLatin1Char('*')) { + scanChar(); + if (_char == QLatin1Char('/')) { + scanChar(); + + if (_engine) { + _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 4, + tokenStartLine(), tokenStartColumn() + 2); + } + + goto again; + } + } else { + scanChar(); + } + } + } else if (_char == QLatin1Char('/')) { + while (!_char.isNull() && _char != QLatin1Char('\n')) { + scanChar(); + } + if (_engine) { + _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 2, + tokenStartLine(), tokenStartColumn() + 2); + } + goto again; + } if (_char == QLatin1Char('=')) { + scanChar(); + return T_DIVIDE_EQ; + } + return T_DIVIDE_; + + case '.': + if (_char.isDigit()) { + QVarLengthArray<char,32> chars; + + chars.append(ch.unicode()); // append the `.' + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + + if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + + chars.append('\0'); + + const char *begin = chars.constData(); + const char *end = 0; + bool ok = false; + + _tokenValue = qstrtod(begin, &end, &ok); + + if (end - begin != chars.size() - 1) { + _errorCode = IllegalExponentIndicator; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); + return T_ERROR; + } + + return T_NUMERIC_LITERAL; + } + return T_DOT; + + case '-': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_MINUS_EQ; + } else if (_char == QLatin1Char('-')) { + scanChar(); + + if (_terminator && !_delimited && !_prohibitAutomaticSemicolon) { + _stackToken = T_MINUS_MINUS; + return T_SEMICOLON; + } + + return T_MINUS_MINUS; + } + return T_MINUS; + + case ',': return T_COMMA; + + case '+': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_PLUS_EQ; + } else if (_char == QLatin1Char('+')) { + scanChar(); + + if (_terminator && !_delimited && !_prohibitAutomaticSemicolon) { + _stackToken = T_PLUS_PLUS; + return T_SEMICOLON; + } + + return T_PLUS_PLUS; + } + return T_PLUS; + + case '*': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_STAR_EQ; + } + return T_STAR; + + case ')': return T_RPAREN; + case '(': return T_LPAREN; + + case '&': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_AND_EQ; + } else if (_char == QLatin1Char('&')) { + scanChar(); + return T_AND_AND; + } + return T_AND; + + case '%': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_REMAINDER_EQ; + } + return T_REMAINDER; + + case '!': + if (_char == QLatin1Char('=')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_NOT_EQ_EQ; + } + return T_NOT_EQ; + } + return T_NOT; + + case '\'': + case '"': { + const QChar quote = ch; + bool multilineStringLiteral = false; + + const QChar *startCode = _codePtr; + + if (_engine) { + while (!_char.isNull()) { + if (_char == QLatin1Char('\n') || _char == QLatin1Char('\\')) { + break; + } else if (_char == quote) { + _tokenSpell = _engine->midRef(startCode - _code.unicode() - 1, _codePtr - startCode); + scanChar(); + + return T_STRING_LITERAL; + } + scanChar(); + } + } + + _validTokenText = true; + _tokenText.resize(0); + startCode--; + while (startCode != _codePtr - 1) + _tokenText += *startCode++; + + while (! _char.isNull()) { + if (_char == QLatin1Char('\n')) { + multilineStringLiteral = true; + _tokenText += _char; + scanChar(); + } else if (_char == quote) { + scanChar(); + + if (_engine) + _tokenSpell = _engine->newStringRef(_tokenText); + + return multilineStringLiteral ? T_MULTILINE_STRING_LITERAL : T_STRING_LITERAL; + } else if (_char == QLatin1Char('\\')) { + scanChar(); + + QChar u; + bool ok = false; + + switch (_char.unicode()) { + // unicode escape sequence + case 'u': + u = decodeUnicodeEscapeCharacter(&ok); + if (! ok) + u = _char; + break; + + // hex escape sequence + case 'x': + case 'X': + if (isHexDigit(_codePtr[0]) && isHexDigit(_codePtr[1])) { + scanChar(); + + const QChar c1 = _char; + scanChar(); + + const QChar c2 = _char; + scanChar(); + + u = convertHex(c1, c2); + } else { + u = _char; + } + break; + + // single character escape sequence + case '\\': u = QLatin1Char('\\'); scanChar(); break; + case '\'': u = QLatin1Char('\''); scanChar(); break; + case '\"': u = QLatin1Char('\"'); scanChar(); break; + case 'b': u = QLatin1Char('\b'); scanChar(); break; + case 'f': u = QLatin1Char('\f'); scanChar(); break; + case 'n': u = QLatin1Char('\n'); scanChar(); break; + case 'r': u = QLatin1Char('\r'); scanChar(); break; + case 't': u = QLatin1Char('\t'); scanChar(); break; + case 'v': u = QLatin1Char('\v'); scanChar(); break; + + case '0': + if (! _codePtr[1].isDigit()) { + scanChar(); + u = QLatin1Char('\0'); + } else { + // ### parse deprecated octal escape sequence ? + u = _char; + } + break; + + case '\r': + while (_char == QLatin1Char('\r')) + scanChar(); + + if (_char == QLatin1Char('\n')) { + u = _char; + scanChar(); + } else { + u = QLatin1Char('\n'); + } + + break; + + case '\n': + u = _char; + scanChar(); + break; + + default: + // non escape character + u = _char; + scanChar(); + } + + _tokenText += u; + } else { + _tokenText += _char; + scanChar(); + } + } + + _errorCode = UnclosedStringLiteral; + _errorMessage = QCoreApplication::translate("QmlParser", "Unclosed string at end of line"); + return T_ERROR; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scanNumber(ch); + + default: + if (ch.isLetter() || ch == QLatin1Char('$') || ch == QLatin1Char('_') || (ch == QLatin1Char('\\') && _char == QLatin1Char('u'))) { + bool identifierWithEscapeChars = false; + if (ch == QLatin1Char('\\')) { + identifierWithEscapeChars = true; + _tokenText.resize(0); + bool ok = false; + _tokenText += decodeUnicodeEscapeCharacter(&ok); + _validTokenText = true; + if (! ok) { + _errorCode = IllegalUnicodeEscapeSequence; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal unicode escape sequence"); + return T_ERROR; + } + } + while (true) { + if (_char.isLetterOrNumber() || _char == QLatin1Char('$') || _char == QLatin1Char('_')) { + if (identifierWithEscapeChars) + _tokenText += _char; + + scanChar(); + } else if (_char == QLatin1Char('\\') && _codePtr[0] == QLatin1Char('u')) { + if (! identifierWithEscapeChars) { + identifierWithEscapeChars = true; + _tokenText.resize(0); + _tokenText.insert(0, _tokenStartPtr, _codePtr - _tokenStartPtr - 1); + _validTokenText = true; + } + + scanChar(); // skip '\\' + bool ok = false; + _tokenText += decodeUnicodeEscapeCharacter(&ok); + if (! ok) { + _errorCode = IllegalUnicodeEscapeSequence; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal unicode escape sequence"); + return T_ERROR; + } + } else { + _tokenLength = _codePtr - _tokenStartPtr - 1; + + int kind = T_IDENTIFIER; + + if (! identifierWithEscapeChars) + kind = classify(_tokenStartPtr, _tokenLength, _qmlMode); + + if (_engine) { + if (kind == T_IDENTIFIER && identifierWithEscapeChars) + _tokenSpell = _engine->newStringRef(_tokenText); + else + _tokenSpell = _engine->midRef(_tokenStartPtr - _code.unicode(), _tokenLength); + } + + return kind; + } + } + } + + break; + } + + return T_ERROR; +} + +int Lexer::scanNumber(QChar ch) +{ + if (ch != QLatin1Char('0')) { + double integer = ch.unicode() - '0'; + + QChar n = _char; + const QChar *code = _codePtr; + while (n.isDigit()) { + integer = integer * 10 + (n.unicode() - '0'); + n = *code++; + } + + if (n != QLatin1Char('.') && n != QLatin1Char('e') && n != QLatin1Char('E')) { + if (code != _codePtr) { + _codePtr = code - 1; + scanChar(); + } + _tokenValue = integer; + return T_NUMERIC_LITERAL; + } + } + + QVarLengthArray<char,32> chars; + chars.append(ch.unicode()); + + if (ch == QLatin1Char('0') && (_char == QLatin1Char('x') || _char == QLatin1Char('X'))) { + // parse hex integer literal + + chars.append(_char.unicode()); + scanChar(); // consume `x' + + while (isHexDigit(_char)) { + chars.append(_char.unicode()); + scanChar(); + } + + _tokenValue = integerFromString(chars.constData(), chars.size(), 16); + return T_NUMERIC_LITERAL; + } + + // decimal integer literal + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); // consume the digit + } + + if (_char == QLatin1Char('.')) { + chars.append(_char.unicode()); + scanChar(); // consume `.' + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + + if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + } else if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + + if (chars.size() == 1) { + // if we ended up with a single digit, then it was a '0' + _tokenValue = 0; + return T_NUMERIC_LITERAL; + } + + chars.append('\0'); + + const char *begin = chars.constData(); + const char *end = 0; + bool ok = false; + + _tokenValue = qstrtod(begin, &end, &ok); + + if (end - begin != chars.size() - 1) { + _errorCode = IllegalExponentIndicator; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); + return T_ERROR; + } + + return T_NUMERIC_LITERAL; +} + +bool Lexer::scanRegExp(RegExpBodyPrefix prefix) +{ + _tokenText.resize(0); + _validTokenText = true; + _patternFlags = 0; + + if (prefix == EqualPrefix) + _tokenText += QLatin1Char('='); + + while (true) { + switch (_char.unicode()) { + case 0: // eof + case '\n': case '\r': // line terminator + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression literal"); + return false; + + case '/': + scanChar(); + + // scan the flags + _patternFlags = 0; + while (isIdentLetter(_char)) { + int flag = regExpFlagFromChar(_char); + if (flag == 0) { + _errorMessage = QCoreApplication::translate("QmlParser", "Invalid regular expression flag '%0'") + .arg(QChar(_char)); + return false; + } + _patternFlags |= flag; + scanChar(); + } + + _tokenLength = _codePtr - _tokenStartPtr - 1; + return true; + + case '\\': + // regular expression backslash sequence + _tokenText += _char; + scanChar(); + + if (_char.isNull() || isLineTerminator()) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression backslash sequence"); + return false; + } + + _tokenText += _char; + scanChar(); + break; + + case '[': + // regular expression class + _tokenText += _char; + scanChar(); + + while (! _char.isNull() && ! isLineTerminator()) { + if (_char == QLatin1Char(']')) + break; + else if (_char == QLatin1Char('\\')) { + // regular expression backslash sequence + _tokenText += _char; + scanChar(); + + if (_char.isNull() || isLineTerminator()) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression backslash sequence"); + return false; + } + + _tokenText += _char; + scanChar(); + } else { + _tokenText += _char; + scanChar(); + } + } + + if (_char != QLatin1Char(']')) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression class"); + return false; + } + + _tokenText += _char; + scanChar(); // skip ] + break; + + default: + _tokenText += _char; + scanChar(); + } // switch + } // while + + return false; +} + +bool Lexer::isLineTerminator() const +{ + return (_char == QLatin1Char('\n') || _char == QLatin1Char('\r')); +} + +bool Lexer::isIdentLetter(QChar ch) +{ + // ASCII-biased, since all reserved words are ASCII, aand hence the + // bulk of content to be parsed. + if ((ch >= QLatin1Char('a') && ch <= QLatin1Char('z')) + || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')) + || ch == QLatin1Char('$') + || ch == QLatin1Char('_')) + return true; + if (ch.unicode() < 128) + return false; + return ch.isLetterOrNumber(); +} + +bool Lexer::isDecimalDigit(ushort c) +{ + return (c >= '0' && c <= '9'); +} + +bool Lexer::isHexDigit(QChar c) +{ + return ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) + || (c >= QLatin1Char('a') && c <= QLatin1Char('f')) + || (c >= QLatin1Char('A') && c <= QLatin1Char('F'))); +} + +bool Lexer::isOctalDigit(ushort c) +{ + return (c >= '0' && c <= '7'); +} + +int Lexer::tokenEndLine() const +{ + return _currentLineNumber; +} + +int Lexer::tokenEndColumn() const +{ + return _codePtr - _lastLinePtr; +} + +QString Lexer::tokenText() const +{ + if (_validTokenText) + return _tokenText; + + if (_tokenKind == T_STRING_LITERAL) + return QString(_tokenStartPtr + 1, _tokenLength - 2); + + return QString(_tokenStartPtr, _tokenLength); +} + +Lexer::Error Lexer::errorCode() const +{ + return _errorCode; +} + +QString Lexer::errorMessage() const +{ + return _errorMessage; +} + +void Lexer::syncProhibitAutomaticSemicolon() +{ + if (_parenthesesState == BalancedParentheses) { + // we have seen something like "if (foo)", which means we should + // never insert an automatic semicolon at this point, since it would + // then be expanded into an empty statement (ECMA-262 7.9.1) + _prohibitAutomaticSemicolon = true; + _parenthesesState = IgnoreParentheses; + } else { + _prohibitAutomaticSemicolon = false; + } +} + +bool Lexer::prevTerminator() const +{ + return _terminator; +} + +bool Lexer::followsClosingBrace() const +{ + return _followsClosingBrace; +} + +bool Lexer::canInsertAutomaticSemicolon(int token) const +{ + return token == T_RBRACE + || token == EOF_SYMBOL + || _terminator + || _followsClosingBrace; +} + +bool Lexer::scanDirectives(Directives *directives) +{ + if (_qmlMode) { + // the directives are a Javascript-only extension. + return false; + } + + lex(); // fetch the first token + + if (_tokenKind != T_DOT) + return true; + + do { + lex(); // skip T_DOT + + const int lineNumber = tokenStartLine(); + + if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD)) + return false; // expected a valid QML/JS directive + + const QString directiveName = tokenText(); + + if (! (directiveName == QLatin1String("pragma") || + directiveName == QLatin1String("import"))) + return false; // not a valid directive name + + // it must be a pragma or an import directive. + if (directiveName == QLatin1String("pragma")) { + // .pragma library + if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) + return false; // expected `library + + // we found a .pragma library directive + directives->pragmaLibrary(); + + } else { + Q_ASSERT(directiveName == QLatin1String("import")); + lex(); // skip .import + + QString pathOrUri; + QString version; + bool fileImport = false; // file or uri import + + if (_tokenKind == T_STRING_LITERAL) { + // .import T_STRING_LITERAL as T_IDENTIFIER + + fileImport = true; + pathOrUri = tokenText(); + + } else if (_tokenKind == T_IDENTIFIER) { + // .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER + + pathOrUri = tokenText(); + + lex(); // skip the first T_IDENTIFIER + for (; _tokenKind == T_DOT; lex()) { + if (lex() != T_IDENTIFIER) + return false; + + pathOrUri += QLatin1Char('.'); + pathOrUri += tokenText(); + } + + if (_tokenKind != T_NUMERIC_LITERAL) + return false; // expected the module version number + + version = tokenText(); + } + + // + // recognize the mandatory `as' followed by the module name + // + if (! (lex() == T_RESERVED_WORD && tokenText() == QLatin1String("as"))) + return false; // expected `as' + + if (lex() != T_IDENTIFIER) + return false; // expected module name + + const QString module = tokenText(); + + if (fileImport) + directives->importFile(pathOrUri, module); + else + directives->importModule(pathOrUri, version, module); + } + + if (tokenStartLine() != lineNumber) + return false; // the directives cannot span over multiple lines + + // fetch the first token after the .pragma/.import directive + lex(); + } while (_tokenKind == T_DOT); + + return true; +} + +} // namespace QbsQmlJS + +#include "qmljskeywords_p.h" diff --git a/src/lib/corelib/parser/qmljslexer_p.h b/src/lib/corelib/parser/qmljslexer_p.h new file mode 100644 index 000000000..692659637 --- /dev/null +++ b/src/lib/corelib/parser/qmljslexer_p.h @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSLEXER_P_H +#define QMLJSLEXER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include <QtCore/QString> + +namespace QbsQmlJS { + +class Engine; + +class QML_PARSER_EXPORT Directives { +public: + virtual ~Directives() {} + + virtual void pragmaLibrary() + { + } + + virtual void importFile(const QString &jsfile, const QString &module) + { + Q_UNUSED(jsfile); + Q_UNUSED(module); + } + + virtual void importModule(const QString &uri, const QString &version, const QString &module) + { + Q_UNUSED(uri); + Q_UNUSED(version); + Q_UNUSED(module); + } +}; + +class QML_PARSER_EXPORT Lexer: public QmlJSGrammar +{ +public: + enum { + T_ABSTRACT = T_RESERVED_WORD, + T_BOOLEAN = T_RESERVED_WORD, + T_BYTE = T_RESERVED_WORD, + T_CHAR = T_RESERVED_WORD, + T_CLASS = T_RESERVED_WORD, + T_DOUBLE = T_RESERVED_WORD, + T_ENUM = T_RESERVED_WORD, + T_EXPORT = T_RESERVED_WORD, + T_EXTENDS = T_RESERVED_WORD, + T_FINAL = T_RESERVED_WORD, + T_FLOAT = T_RESERVED_WORD, + T_GOTO = T_RESERVED_WORD, + T_IMPLEMENTS = T_RESERVED_WORD, + T_INT = T_RESERVED_WORD, + T_INTERFACE = T_RESERVED_WORD, + T_LET = T_RESERVED_WORD, + T_LONG = T_RESERVED_WORD, + T_NATIVE = T_RESERVED_WORD, + T_PACKAGE = T_RESERVED_WORD, + T_PRIVATE = T_RESERVED_WORD, + T_PROTECTED = T_RESERVED_WORD, + T_SHORT = T_RESERVED_WORD, + T_STATIC = T_RESERVED_WORD, + T_SUPER = T_RESERVED_WORD, + T_SYNCHRONIZED = T_RESERVED_WORD, + T_THROWS = T_RESERVED_WORD, + T_TRANSIENT = T_RESERVED_WORD, + T_VOLATILE = T_RESERVED_WORD, + T_YIELD = T_RESERVED_WORD + }; + + enum Error { + NoError, + IllegalCharacter, + UnclosedStringLiteral, + IllegalEscapeSequence, + IllegalUnicodeEscapeSequence, + UnclosedComment, + IllegalExponentIndicator, + IllegalIdentifier + }; + + enum RegExpBodyPrefix { + NoPrefix, + EqualPrefix + }; + + enum RegExpFlag { + RegExp_Global = 0x01, + RegExp_IgnoreCase = 0x02, + RegExp_Multiline = 0x04 + }; + +public: + Lexer(Engine *engine); + + bool qmlMode() const; + + QString code() const; + void setCode(const QString &code, int lineno, bool qmlMode = true); + + int lex(); + + bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); + bool scanDirectives(Directives *directives); + + int regExpFlags() const { return _patternFlags; } + QString regExpPattern() const { return _tokenText; } + + int tokenKind() const { return _tokenKind; } + int tokenOffset() const { return _tokenStartPtr - _code.unicode(); } + int tokenLength() const { return _tokenLength; } + + int tokenStartLine() const { return _tokenLine; } + int tokenStartColumn() const { return _tokenStartPtr - _tokenLinePtr + 1; } + + int tokenEndLine() const; + int tokenEndColumn() const; + + inline QStringRef tokenSpell() const { return _tokenSpell; } + double tokenValue() const { return _tokenValue; } + QString tokenText() const; + + Error errorCode() const; + QString errorMessage() const; + + bool prevTerminator() const; + bool followsClosingBrace() const; + bool canInsertAutomaticSemicolon(int token) const; + + enum ParenthesesState { + IgnoreParentheses, + CountParentheses, + BalancedParentheses + }; + +protected: + int classify(const QChar *s, int n, bool qmlMode); + +private: + inline void scanChar(); + int scanToken(); + int scanNumber(QChar ch); + + bool isLineTerminator() const; + static bool isIdentLetter(QChar c); + static bool isDecimalDigit(ushort c); + static bool isHexDigit(QChar c); + static bool isOctalDigit(ushort c); + static bool isUnicodeEscapeSequence(const QChar *chars); + + void syncProhibitAutomaticSemicolon(); + QChar decodeUnicodeEscapeCharacter(bool *ok); + +private: + Engine *_engine; + + QString _code; + QString _tokenText; + QString _errorMessage; + QStringRef _tokenSpell; + + const QChar *_codePtr; + const QChar *_lastLinePtr; + const QChar *_tokenLinePtr; + const QChar *_tokenStartPtr; + + QChar _char; + Error _errorCode; + + int _currentLineNumber; + double _tokenValue; + + // parentheses state + ParenthesesState _parenthesesState; + int _parenthesesCount; + + int _stackToken; + + int _patternFlags; + int _tokenKind; + int _tokenLength; + int _tokenLine; + + bool _validTokenText; + bool _prohibitAutomaticSemicolon; + bool _restrictedKeyword; + bool _terminator; + bool _followsClosingBrace; + bool _delimited; + bool _qmlMode; +}; + +} // namespace QbsQmlJS + +#endif // LEXER_H diff --git a/src/lib/corelib/parser/qmljsmemorypool_p.h b/src/lib/corelib/parser/qmljsmemorypool_p.h new file mode 100644 index 000000000..f644cd59d --- /dev/null +++ b/src/lib/corelib/parser/qmljsmemorypool_p.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 QMLJSMEMORYPOOL_P_H +#define QMLJSMEMORYPOOL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qshareddata.h> +#include <QtCore/qdebug.h> + +#include <cstring> + +namespace QbsQmlJS { + +class QML_PARSER_EXPORT MemoryPool : public QSharedData +{ + MemoryPool(const MemoryPool &other); + void operator =(const MemoryPool &other); + +public: + MemoryPool() + : _blocks(0), + _allocatedBlocks(0), + _blockCount(-1), + _ptr(0), + _end(0) + { } + + ~MemoryPool() + { + if (_blocks) { + for (int i = 0; i < _allocatedBlocks; ++i) { + if (char *b = _blocks[i]) + free(b); + } + + free(_blocks); + } + } + + inline void *allocate(size_t size) + { + size = (size + 7) & ~7; + if (_ptr && (_ptr + size < _end)) { + void *addr = _ptr; + _ptr += size; + return addr; + } + return allocate_helper(size); + } + + void reset() + { + _blockCount = -1; + _ptr = _end = 0; + } + +private: + void *allocate_helper(size_t size) + { + Q_ASSERT(size < BLOCK_SIZE); + + if (++_blockCount == _allocatedBlocks) { + if (! _allocatedBlocks) + _allocatedBlocks = DEFAULT_BLOCK_COUNT; + else + _allocatedBlocks *= 2; + + _blocks = (char **) realloc(_blocks, sizeof(char *) * _allocatedBlocks); + + for (int index = _blockCount; index < _allocatedBlocks; ++index) + _blocks[index] = 0; + } + + char *&block = _blocks[_blockCount]; + + if (! block) + block = (char *) malloc(BLOCK_SIZE); + + _ptr = block; + _end = _ptr + BLOCK_SIZE; + + void *addr = _ptr; + _ptr += size; + return addr; + } + +private: + char **_blocks; + int _allocatedBlocks; + int _blockCount; + char *_ptr; + char *_end; + + enum + { + BLOCK_SIZE = 8 * 1024, + DEFAULT_BLOCK_COUNT = 8 + }; +}; + +class QML_PARSER_EXPORT Managed +{ + Managed(const Managed &other); + void operator = (const Managed &other); + +public: + Managed() {} + ~Managed() {} + + void *operator new(size_t size, MemoryPool *pool) { return pool->allocate(size); } + void operator delete(void *) {} + void operator delete(void *, MemoryPool *) {} +}; + +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsparser.cpp b/src/lib/corelib/parser/qmljsparser.cpp new file mode 100644 index 000000000..afd9beeee --- /dev/null +++ b/src/lib/corelib/parser/qmljsparser.cpp @@ -0,0 +1,1810 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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 <QtCore/QDebug> +#include <QtCore/QCoreApplication> + +#include <string.h> + +#include "qmljsengine_p.h" +#include "qmljslexer_p.h" +#include "qmljsast_p.h" +#include "qmljsmemorypool_p.h" + + + +#include "qmljsparser_p.h" +#include <QVarLengthArray> + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +namespace QbsQmlJS { + +void Parser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack = reinterpret_cast<Value*> (realloc(sym_stack, stack_size * sizeof(Value))); + state_stack = reinterpret_cast<int*> (realloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast<AST::SourceLocation*> (realloc(location_stack, stack_size * sizeof(AST::SourceLocation))); + string_stack = reinterpret_cast<QStringRef*> (realloc(string_stack, stack_size * sizeof(QStringRef))); +} + +Parser::Parser(Engine *engine): + driver(engine), + pool(engine->pool()), + tos(0), + stack_size(0), + sym_stack(0), + state_stack(0), + location_stack(0), + string_stack(0), + program(0), + first_token(0), + last_token(0) +{ +} + +Parser::~Parser() +{ + if (stack_size) { + free(sym_stack); + free(state_stack); + free(location_stack); + free(string_stack); + } +} + +static inline AST::SourceLocation location(Lexer *lexer) +{ + AST::SourceLocation loc; + loc.offset = lexer->tokenOffset(); + loc.length = lexer->tokenLength(); + loc.startLine = lexer->tokenStartLine(); + loc.startColumn = lexer->tokenStartColumn(); + return loc; +} + +AST::UiQualifiedId *Parser::reparseAsQualifiedId(AST::ExpressionNode *expr) +{ + QVarLengthArray<QStringRef, 4> nameIds; + QVarLengthArray<AST::SourceLocation, 4> locations; + + AST::ExpressionNode *it = expr; + while (AST::FieldMemberExpression *m = AST::cast<AST::FieldMemberExpression *>(it)) { + nameIds.append(m->name); + locations.append(m->identifierToken); + it = m->base; + } + + if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(it)) { + AST::UiQualifiedId *q = new (pool) AST::UiQualifiedId(idExpr->name); + q->identifierToken = idExpr->identifierToken; + + AST::UiQualifiedId *currentId = q; + for (int i = nameIds.size() - 1; i != -1; --i) { + currentId = new (pool) AST::UiQualifiedId(currentId, nameIds[i]); + currentId->identifierToken = locations[i]; + } + + return currentId->finish(); + } + + return 0; +} + +bool Parser::parse(int startToken) +{ + Lexer *lexer = driver->lexer(); + bool hadErrors = false; + int yytoken = -1; + int action = 0; + + token_buffer[0].token = startToken; + first_token = &token_buffer[0]; + if (startToken == T_FEED_JS_PROGRAM) { + Directives ignoreDirectives; + Directives *directives = driver->directives(); + if (!directives) + directives = &ignoreDirectives; + lexer->scanDirectives(directives); + token_buffer[1].token = lexer->tokenKind(); + token_buffer[1].dval = lexer->tokenValue(); + token_buffer[1].loc = location(lexer); + token_buffer[1].spell = lexer->tokenSpell(); + last_token = &token_buffer[2]; + } else { + last_token = &token_buffer[1]; + } + + tos = -1; + program = 0; + + do { + if (++tos == stack_size) + reallocateStack(); + + state_stack[tos] = action; + + _Lcheck_token: + if (yytoken == -1 && -TERMINAL_COUNT != action_index[action]) { + yyprevlloc = yylloc; + + if (first_token == last_token) { + yytoken = lexer->lex(); + yylval = lexer->tokenValue(); + yytokenspell = lexer->tokenSpell(); + yylloc = location(lexer); + } else { + yytoken = first_token->token; + yylval = first_token->dval; + yytokenspell = first_token->spell; + yylloc = first_token->loc; + ++first_token; + } + } + + action = t_action(action, yytoken); + if (action > 0) { + if (action != ACCEPT_STATE) { + yytoken = -1; + sym(1).dval = yylval; + stringRef(1) = yytokenspell; + loc(1) = yylloc; + } else { + --tos; + return ! hadErrors; + } + } else if (action < 0) { + const int r = -action - 1; + tos -= rhs[r]; + + switch (r) { + +case 0: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 1: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 2: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 3: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 4: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 5: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 6: { + sym(1).UiProgram = new (pool) AST::UiProgram(sym(1).UiImportList, + sym(2).UiObjectMemberList->finish()); +} break; + +case 8: { + sym(1).Node = sym(1).UiImportList->finish(); +} break; + +case 9: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImport); +} break; + +case 10: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImportList, sym(2).UiImport); +} break; + +case 13: { + sym(1).UiImport->semicolonToken = loc(2); +} break; + +case 15: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->semicolonToken = loc(3); +} break; + +case 17: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->asToken = loc(3); + sym(1).UiImport->importIdToken = loc(4); + sym(1).UiImport->importId = stringRef(4); + sym(1).UiImport->semicolonToken = loc(5); +} break; + +case 19: { + sym(1).UiImport->asToken = loc(2); + sym(1).UiImport->importIdToken = loc(3); + sym(1).UiImport->importId = stringRef(3); + sym(1).UiImport->semicolonToken = loc(4); +} break; + +case 20: { + AST::UiImport *node = 0; + + if (AST::StringLiteral *importIdLiteral = AST::cast<AST::StringLiteral *>(sym(2).Expression)) { + node = new (pool) AST::UiImport(importIdLiteral->value); + node->fileNameToken = loc(2); + } else if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(2).Expression)) { + node = new (pool) AST::UiImport(qualifiedId); + node->fileNameToken = loc(2); + } + + sym(1).Node = node; + + if (node) { + node->importToken = loc(1); + } else { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id or a string literal"))); + + return false; // ### remove me + } +} break; + +case 21: { + sym(1).Node = 0; +} break; + +case 22: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; + +case 23: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; + +case 24: { + AST::UiObjectMemberList *node = new (pool) AST:: UiObjectMemberList( + sym(1).UiObjectMemberList, sym(2).UiObjectMember); + sym(1).Node = node; +} break; + +case 25: { + sym(1).Node = new (pool) AST::UiArrayMemberList(sym(1).UiObjectMember); +} break; + +case 26: { + AST::UiArrayMemberList *node = new (pool) AST::UiArrayMemberList( + sym(1).UiArrayMemberList, sym(3).UiObjectMember); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 27: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer((AST::UiObjectMemberList*)0); + node->lbraceToken = loc(1); + node->rbraceToken = loc(2); + sym(1).Node = node; +} break; + +case 28: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer(sym(2).UiObjectMemberList->finish()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 29: { + AST::UiObjectDefinition *node = new (pool) AST::UiObjectDefinition(sym(1).UiQualifiedId, + sym(2).UiObjectInitializer); + sym(1).Node = node; +} break; + +case 31: { + AST::UiArrayBinding *node = new (pool) AST::UiArrayBinding( + sym(1).UiQualifiedId, sym(4).UiArrayMemberList->finish()); + node->colonToken = loc(2); + node->lbracketToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; + +case 32: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(1).UiQualifiedId, sym(3).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 33: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(3).UiQualifiedId, sym(1).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + node->hasOnToken = true; + sym(1).Node = node; +} break; + +case 41: +{ + AST::UiScriptBinding *node = new (pool) AST::UiScriptBinding( + sym(1).UiQualifiedId, sym(3).Statement); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 45: { + sym(1).Node = 0; +} break; + +case 46: { + sym(1).Node = sym(1).UiParameterList->finish (); +} break; + +case 47: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(stringRef(1), stringRef(2)); + node->propertyTypeToken = loc(1); + node->identifierToken = loc(2); + sym(1).Node = node; +} break; + +case 48: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(sym(1).UiParameterList, stringRef(3), stringRef(4)); + node->commaToken = loc(2); + node->identifierToken = loc(4); + sym(1).Node = node; +} break; + +case 50: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->parameters = sym(4).UiParameterList; + node->semicolonToken = loc(6); + sym(1).Node = node; +} break; + +case 52: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 54: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; + +case 56: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); + sym(1).Node = node; +} break; + +case 58: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4)); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->semicolonToken = loc(5); + sym(1).Node = node; +} break; + +case 59: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3), + sym(5).Statement); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 60: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isReadonlyMember = true; + node->readonlyToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; + +case 61: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; + +case 62: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(6)); + propertyName->identifierToken = loc(6); + propertyName->next = 0; + + AST::UiArrayBinding *binding = new (pool) AST::UiArrayBinding( + propertyName, sym(9).UiArrayMemberList->finish()); + binding->colonToken = loc(7); + binding->lbracketToken = loc(8); + binding->rbracketToken = loc(10); + + node->binding = binding; + + sym(1).Node = node; +} break; + +case 63: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(3)); + propertyName->identifierToken = loc(3); + propertyName->next = 0; + + AST::UiObjectBinding *binding = new (pool) AST::UiObjectBinding( + propertyName, sym(5).UiQualifiedId, sym(6).UiObjectInitializer); + binding->colonToken = loc(4); + + node->binding = binding; + + sym(1).Node = node; +} break; + +case 64: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; + +case 65: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; + +case 71: { + AST::ThisExpression *node = new (pool) AST::ThisExpression(); + node->thisToken = loc(1); + sym(1).Node = node; +} break; + +case 72: { + AST::IdentifierExpression *node = new (pool) AST::IdentifierExpression(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 73: { + AST::NullExpression *node = new (pool) AST::NullExpression(); + node->nullToken = loc(1); + sym(1).Node = node; +} break; + +case 74: { + AST::TrueLiteral *node = new (pool) AST::TrueLiteral(); + node->trueToken = loc(1); + sym(1).Node = node; +} break; + +case 75: { + AST::FalseLiteral *node = new (pool) AST::FalseLiteral(); + node->falseToken = loc(1); + sym(1).Node = node; +} break; + +case 76: { + AST::NumericLiteral *node = new (pool) AST::NumericLiteral(sym(1).dval); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +case 77: +case 78: { + AST::StringLiteral *node = new (pool) AST::StringLiteral(stringRef(1)); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 79: { + bool rx = lexer->scanRegExp(Lexer::NoPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; // ### remove me + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 80: { + bool rx = lexer->scanRegExp(Lexer::EqualPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 81: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral((AST::Elision *) 0); + node->lbracketToken = loc(1); + node->rbracketToken = loc(2); + sym(1).Node = node; +} break; + +case 82: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).Elision->finish()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; + +case 83: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish ()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; + +case 84: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + (AST::Elision *) 0); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 85: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + sym(4).Elision->finish()); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; + +case 86: { + AST::ObjectLiteral *node = 0; + if (sym(2).Node) + node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + else + node = new (pool) AST::ObjectLiteral(); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 87: { + AST::ObjectLiteral *node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(4); + sym(1).Node = node; +} break; + +case 88: { + AST::NestedExpression *node = new (pool) AST::NestedExpression(sym(2).Expression); + node->lparenToken = loc(1); + node->rparenToken = loc(3); + sym(1).Node = node; +} break; + +case 89: { + if (AST::ArrayMemberExpression *mem = AST::cast<AST::ArrayMemberExpression *>(sym(1).Expression)) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, mem->lbracketToken, + QLatin1String("Ignored annotation"))); + + sym(1).Expression = mem->base; + } + + if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(1).Expression)) { + sym(1).UiQualifiedId = qualifiedId; + } else { + sym(1).UiQualifiedId = 0; + + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id"))); + + return false; // ### recover + } +} break; + +case 90: { + sym(1).Node = new (pool) AST::ElementList((AST::Elision *) 0, sym(1).Expression); +} break; + +case 91: { + sym(1).Node = new (pool) AST::ElementList(sym(1).Elision->finish(), sym(2).Expression); +} break; + +case 92: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, + (AST::Elision *) 0, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 93: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, sym(3).Elision->finish(), + sym(4).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 94: { + AST::Elision *node = new (pool) AST::Elision(); + node->commaToken = loc(1); + sym(1).Node = node; +} break; + +case 95: { + AST::Elision *node = new (pool) AST::Elision(sym(1).Elision); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 96: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyName, sym(3).Expression); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 97: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyNameAndValueList, sym(3).PropertyName, sym(5).Expression); + node->commaToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 98: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +case 99: +case 100: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 101: { + AST::StringLiteralPropertyName *node = new (pool) AST::StringLiteralPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 102: { + AST::NumericLiteralPropertyName *node = new (pool) AST::NumericLiteralPropertyName(sym(1).dval); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 103: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 139: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 140: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 141: { + AST::NewMemberExpression *node = new (pool) AST::NewMemberExpression(sym(2).Expression, sym(4).ArgumentList); + node->newToken = loc(1); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + sym(1).Node = node; +} break; + +case 143: { + AST::NewExpression *node = new (pool) AST::NewExpression(sym(2).Expression); + node->newToken = loc(1); + sym(1).Node = node; +} break; + +case 144: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 145: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 146: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 147: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 148: { + sym(1).Node = 0; +} break; + +case 149: { + sym(1).Node = sym(1).ArgumentList->finish(); +} break; + +case 150: { + sym(1).Node = new (pool) AST::ArgumentList(sym(1).Expression); +} break; + +case 151: { + AST::ArgumentList *node = new (pool) AST::ArgumentList(sym(1).ArgumentList, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 155: { + AST::PostIncrementExpression *node = new (pool) AST::PostIncrementExpression(sym(1).Expression); + node->incrementToken = loc(2); + sym(1).Node = node; +} break; + +case 156: { + AST::PostDecrementExpression *node = new (pool) AST::PostDecrementExpression(sym(1).Expression); + node->decrementToken = loc(2); + sym(1).Node = node; +} break; + +case 158: { + AST::DeleteExpression *node = new (pool) AST::DeleteExpression(sym(2).Expression); + node->deleteToken = loc(1); + sym(1).Node = node; +} break; + +case 159: { + AST::VoidExpression *node = new (pool) AST::VoidExpression(sym(2).Expression); + node->voidToken = loc(1); + sym(1).Node = node; +} break; + +case 160: { + AST::TypeOfExpression *node = new (pool) AST::TypeOfExpression(sym(2).Expression); + node->typeofToken = loc(1); + sym(1).Node = node; +} break; + +case 161: { + AST::PreIncrementExpression *node = new (pool) AST::PreIncrementExpression(sym(2).Expression); + node->incrementToken = loc(1); + sym(1).Node = node; +} break; + +case 162: { + AST::PreDecrementExpression *node = new (pool) AST::PreDecrementExpression(sym(2).Expression); + node->decrementToken = loc(1); + sym(1).Node = node; +} break; + +case 163: { + AST::UnaryPlusExpression *node = new (pool) AST::UnaryPlusExpression(sym(2).Expression); + node->plusToken = loc(1); + sym(1).Node = node; +} break; + +case 164: { + AST::UnaryMinusExpression *node = new (pool) AST::UnaryMinusExpression(sym(2).Expression); + node->minusToken = loc(1); + sym(1).Node = node; +} break; + +case 165: { + AST::TildeExpression *node = new (pool) AST::TildeExpression(sym(2).Expression); + node->tildeToken = loc(1); + sym(1).Node = node; +} break; + +case 166: { + AST::NotExpression *node = new (pool) AST::NotExpression(sym(2).Expression); + node->notToken = loc(1); + sym(1).Node = node; +} break; + +case 168: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mul, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 169: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Div, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 170: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mod, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 172: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Add, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 173: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Sub, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 175: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::LShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 176: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::RShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 177: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::URShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 179: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 180: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 181: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 182: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 183: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 184: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::In, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 186: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 187: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 188: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 189: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 190: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 192: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 193: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 194: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 195: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 197: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 198: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 199: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 200: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 202: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 204: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 206: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 208: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 210: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 212: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 214: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 216: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 218: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 220: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 222: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 224: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 226: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 228: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 229: { + sym(1).ival = QSOperator::Assign; +} break; + +case 230: { + sym(1).ival = QSOperator::InplaceMul; +} break; + +case 231: { + sym(1).ival = QSOperator::InplaceDiv; +} break; + +case 232: { + sym(1).ival = QSOperator::InplaceMod; +} break; + +case 233: { + sym(1).ival = QSOperator::InplaceAdd; +} break; + +case 234: { + sym(1).ival = QSOperator::InplaceSub; +} break; + +case 235: { + sym(1).ival = QSOperator::InplaceLeftShift; +} break; + +case 236: { + sym(1).ival = QSOperator::InplaceRightShift; +} break; + +case 237: { + sym(1).ival = QSOperator::InplaceURightShift; +} break; + +case 238: { + sym(1).ival = QSOperator::InplaceAnd; +} break; + +case 239: { + sym(1).ival = QSOperator::InplaceXor; +} break; + +case 240: { + sym(1).ival = QSOperator::InplaceOr; +} break; + +case 242: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 243: { + sym(1).Node = 0; +} break; + +case 246: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 247: { + sym(1).Node = 0; +} break; + +case 264: { + AST::Block *node = new (pool) AST::Block(sym(2).StatementList); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 265: { + sym(1).Node = new (pool) AST::StatementList(sym(1).Statement); +} break; + +case 266: { + sym(1).Node = new (pool) AST::StatementList(sym(1).StatementList, sym(2).Statement); +} break; + +case 267: { + sym(1).Node = 0; +} break; + +case 268: { + sym(1).Node = sym(1).StatementList->finish (); +} break; + +case 270: { + AST::VariableStatement *node = new (pool) AST::VariableStatement( + sym(2).VariableDeclarationList->finish (/*readOnly=*/sym(1).ival == T_CONST)); + node->declarationKindToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 271: { + sym(1).ival = T_CONST; +} break; + +case 272: { + sym(1).ival = T_VAR; +} break; + +case 273: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; + +case 274: { + AST::VariableDeclarationList *node = new (pool) AST::VariableDeclarationList( + sym(1).VariableDeclarationList, sym(3).VariableDeclaration); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 275: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; + +case 276: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclarationList, sym(3).VariableDeclaration); +} break; + +case 277: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 278: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 279: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; + +case 280: { + sym(1).Node = 0; +} break; + +case 282: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; + +case 283: { + sym(1).Node = 0; +} break; + +case 285: { + AST::EmptyStatement *node = new (pool) AST::EmptyStatement(); + node->semicolonToken = loc(1); + sym(1).Node = node; +} break; + +case 287: { + AST::ExpressionStatement *node = new (pool) AST::ExpressionStatement(sym(1).Expression); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 288: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement, sym(7).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->elseToken = loc(6); + sym(1).Node = node; +} break; + +case 289: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 291: { + AST::DoWhileStatement *node = new (pool) AST::DoWhileStatement(sym(2).Statement, sym(5).Expression); + node->doToken = loc(1); + node->whileToken = loc(3); + node->lparenToken = loc(4); + node->rparenToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; + +case 292: { + AST::WhileStatement *node = new (pool) AST::WhileStatement(sym(3).Expression, sym(5).Statement); + node->whileToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 293: { + AST::ForStatement *node = new (pool) AST::ForStatement(sym(3).Expression, + sym(5).Expression, sym(7).Expression, sym(9).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->firstSemicolonToken = loc(4); + node->secondSemicolonToken = loc(6); + node->rparenToken = loc(8); + sym(1).Node = node; +} break; + +case 294: { + AST::LocalForStatement *node = new (pool) AST::LocalForStatement( + sym(4).VariableDeclarationList->finish (/*readOnly=*/false), sym(6).Expression, + sym(8).Expression, sym(10).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->firstSemicolonToken = loc(5); + node->secondSemicolonToken = loc(7); + node->rparenToken = loc(9); + sym(1).Node = node; +} break; + +case 295: { + AST:: ForEachStatement *node = new (pool) AST::ForEachStatement(sym(3).Expression, + sym(5).Expression, sym(7).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->inToken = loc(4); + node->rparenToken = loc(6); + sym(1).Node = node; +} break; + +case 296: { + AST::LocalForEachStatement *node = new (pool) AST::LocalForEachStatement( + sym(4).VariableDeclaration, sym(6).Expression, sym(8).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->inToken = loc(5); + node->rparenToken = loc(7); + sym(1).Node = node; +} break; + +case 298: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(); + node->continueToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 300: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(stringRef(2)); + node->continueToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 302: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(QStringRef()); + node->breakToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 304: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(stringRef(2)); + node->breakToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 306: { + AST::ReturnStatement *node = new (pool) AST::ReturnStatement(sym(2).Expression); + node->returnToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 307: { + AST::WithStatement *node = new (pool) AST::WithStatement(sym(3).Expression, sym(5).Statement); + node->withToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 308: { + AST::SwitchStatement *node = new (pool) AST::SwitchStatement(sym(3).Expression, sym(5).CaseBlock); + node->switchToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 309: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 310: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses, sym(3).DefaultClause, sym(4).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(5); + sym(1).Node = node; +} break; + +case 311: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClause); +} break; + +case 312: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClauses, sym(2).CaseClause); +} break; + +case 313: { + sym(1).Node = 0; +} break; + +case 314: { + sym(1).Node = sym(1).CaseClauses->finish (); +} break; + +case 315: { + AST::CaseClause *node = new (pool) AST::CaseClause(sym(2).Expression, sym(4).StatementList); + node->caseToken = loc(1); + node->colonToken = loc(3); + sym(1).Node = node; +} break; + +case 316: { + AST::DefaultClause *node = new (pool) AST::DefaultClause(sym(3).StatementList); + node->defaultToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +case 317: +case 318: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 319: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 321: { + AST::ThrowStatement *node = new (pool) AST::ThrowStatement(sym(2).Expression); + node->throwToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 322: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 323: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 324: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch, sym(4).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 325: { + AST::Catch *node = new (pool) AST::Catch(stringRef(3), sym(5).Block); + node->catchToken = loc(1); + node->lparenToken = loc(2); + node->identifierToken = loc(3); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 326: { + AST::Finally *node = new (pool) AST::Finally(sym(2).Block); + node->finallyToken = loc(1); + sym(1).Node = node; +} break; + +case 328: { + AST::DebuggerStatement *node = new (pool) AST::DebuggerStatement(); + node->debuggerToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 329: { + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; + +case 330: { + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + if (! stringRef(2).isNull()) + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; + +case 331: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 332: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(sym(1).FormalParameterList, stringRef(3)); + node->commaToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 333: { + sym(1).Node = 0; +} break; + +case 334: { + sym(1).Node = sym(1).FormalParameterList->finish (); +} break; + +case 335: { + sym(1).Node = 0; +} break; + +case 337: { + sym(1).Node = new (pool) AST::FunctionBody(sym(1).SourceElements->finish ()); +} break; + +case 339: { + sym(1).Node = new (pool) AST::Program(sym(1).SourceElements->finish ()); +} break; + +case 340: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElement); +} break; + +case 341: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElements, sym(2).SourceElement); +} break; + +case 342: { + sym(1).Node = new (pool) AST::StatementSourceElement(sym(1).Statement); +} break; + +case 343: { + sym(1).Node = new (pool) AST::FunctionSourceElement(sym(1).FunctionDeclaration); +} break; + +case 344: { + stringRef(1) = QStringRef(); +} break; + +case 346: { + sym(1).Node = 0; +} break; + + } // switch + action = nt_action(state_stack[tos], lhs[r] - TERMINAL_COUNT); + } // if + } while (action != 0); + + if (first_token == last_token) { + const int errorState = state_stack[tos]; + + // automatic insertion of `;' + if (yytoken != -1 && t_action(errorState, T_AUTOMATIC_SEMICOLON) && lexer->canInsertAutomaticSemicolon(yytoken)) { + SavedToken &tk = token_buffer[0]; + tk.token = yytoken; + tk.dval = yylval; + tk.spell = yytokenspell; + tk.loc = yylloc; + + yylloc = yyprevlloc; + yylloc.offset += yylloc.length; + yylloc.startColumn += yylloc.length; + yylloc.length = 0; + + //const QString msg = qApp->translate("QmlParser", "Missing `;'"); + //diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, yylloc, msg)); + + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + yytoken = T_SEMICOLON; + yylval = 0; + + action = errorState; + + goto _Lcheck_token; + } + + hadErrors = true; + + token_buffer[0].token = yytoken; + token_buffer[0].dval = yylval; + token_buffer[0].spell = yytokenspell; + token_buffer[0].loc = yylloc; + + token_buffer[1].token = yytoken = lexer->lex(); + token_buffer[1].dval = yylval = lexer->tokenValue(); + token_buffer[1].spell = yytokenspell = lexer->tokenSpell(); + token_buffer[1].loc = yylloc = location(lexer); + + if (t_action(errorState, yytoken)) { + QString msg; + int token = token_buffer[0].token; + if (token < 0 || token >= TERMINAL_COUNT) + msg = qApp->translate("QmlParser", "Syntax error"); + else + msg = qApp->translate("QmlParser", "Unexpected token `%1'").arg(QLatin1String(spell[token])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + action = errorState; + goto _Lcheck_token; + } + + static int tokens[] = { + T_PLUS, + T_EQ, + + T_COMMA, + T_COLON, + T_SEMICOLON, + + T_RPAREN, T_RBRACKET, T_RBRACE, + + T_NUMERIC_LITERAL, + T_IDENTIFIER, + + T_LPAREN, T_LBRACKET, T_LBRACE, + + EOF_SYMBOL + }; + + for (int *tk = tokens; *tk != EOF_SYMBOL; ++tk) { + int a = t_action(errorState, *tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[*tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = *tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + first_token = &token_buffer[0]; + last_token = &token_buffer[2]; + + action = errorState; + goto _Lcheck_token; + } + } + + for (int tk = 1; tk < TERMINAL_COUNT; ++tk) { + if (tk == T_AUTOMATIC_SEMICOLON || tk == T_FEED_UI_PROGRAM || + tk == T_FEED_JS_STATEMENT || tk == T_FEED_JS_EXPRESSION || + tk == T_FEED_JS_SOURCE_ELEMENT) + continue; + + int a = t_action(errorState, tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + action = errorState; + goto _Lcheck_token; + } + } + + const QString msg = qApp->translate("QmlParser", "Syntax error"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + } + + return false; +} + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsparser_p.h b/src/lib/corelib/parser/qmljsparser_p.h new file mode 100644 index 000000000..090369fae --- /dev/null +++ b/src/lib/corelib/parser/qmljsparser_p.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +#ifndef QMLJSPARSER_P_H +#define QMLJSPARSER_P_H + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include "qmljsast_p.h" +#include "qmljsengine_p.h" + +#include <QtCore/QList> +#include <QtCore/QString> + +namespace QbsQmlJS { + +class Engine; + +class QML_PARSER_EXPORT Parser: protected QmlJSGrammar +{ +public: + union Value { + int ival; + double dval; + AST::ArgumentList *ArgumentList; + AST::CaseBlock *CaseBlock; + AST::CaseClause *CaseClause; + AST::CaseClauses *CaseClauses; + AST::Catch *Catch; + AST::DefaultClause *DefaultClause; + AST::ElementList *ElementList; + AST::Elision *Elision; + AST::ExpressionNode *Expression; + AST::Finally *Finally; + AST::FormalParameterList *FormalParameterList; + AST::FunctionBody *FunctionBody; + AST::FunctionDeclaration *FunctionDeclaration; + AST::Node *Node; + AST::PropertyName *PropertyName; + AST::PropertyNameAndValueList *PropertyNameAndValueList; + AST::SourceElement *SourceElement; + AST::SourceElements *SourceElements; + AST::Statement *Statement; + AST::StatementList *StatementList; + AST::Block *Block; + AST::VariableDeclaration *VariableDeclaration; + AST::VariableDeclarationList *VariableDeclarationList; + + AST::UiProgram *UiProgram; + AST::UiImportList *UiImportList; + AST::UiImport *UiImport; + AST::UiParameterList *UiParameterList; + AST::UiPublicMember *UiPublicMember; + AST::UiObjectDefinition *UiObjectDefinition; + AST::UiObjectInitializer *UiObjectInitializer; + AST::UiObjectBinding *UiObjectBinding; + AST::UiScriptBinding *UiScriptBinding; + AST::UiArrayBinding *UiArrayBinding; + AST::UiObjectMember *UiObjectMember; + AST::UiObjectMemberList *UiObjectMemberList; + AST::UiArrayMemberList *UiArrayMemberList; + AST::UiQualifiedId *UiQualifiedId; + }; + +public: + Parser(Engine *engine); + ~Parser(); + + // parse a UI program + bool parse() { return parse(T_FEED_UI_PROGRAM); } + bool parseStatement() { return parse(T_FEED_JS_STATEMENT); } + bool parseExpression() { return parse(T_FEED_JS_EXPRESSION); } + bool parseSourceElement() { return parse(T_FEED_JS_SOURCE_ELEMENT); } + bool parseUiObjectMember() { return parse(T_FEED_UI_OBJECT_MEMBER); } + bool parseProgram() { return parse(T_FEED_JS_PROGRAM); } + + AST::UiProgram *ast() const + { return AST::cast<AST::UiProgram *>(program); } + + AST::Statement *statement() const + { + if (! program) + return 0; + + return program->statementCast(); + } + + AST::ExpressionNode *expression() const + { + if (! program) + return 0; + + return program->expressionCast(); + } + + AST::UiObjectMember *uiObjectMember() const + { + if (! program) + return 0; + + return program->uiObjectMemberCast(); + } + + AST::Node *rootNode() const + { return program; } + + QList<DiagnosticMessage> diagnosticMessages() const + { return diagnostic_messages; } + + inline DiagnosticMessage diagnosticMessage() const + { + foreach (const DiagnosticMessage &d, diagnostic_messages) { + if (d.kind != DiagnosticMessage::Warning) + return d; + } + + return DiagnosticMessage(); + } + + inline QString errorMessage() const + { return diagnosticMessage().message; } + + inline int errorLineNumber() const + { return diagnosticMessage().loc.startLine; } + + inline int errorColumnNumber() const + { return diagnosticMessage().loc.startColumn; } + +protected: + bool parse(int startToken); + + void reallocateStack(); + + inline Value &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline QStringRef &stringRef(int index) + { return string_stack [tos + index - 1]; } + + inline AST::SourceLocation &loc(int index) + { return location_stack [tos + index - 1]; } + + AST::UiQualifiedId *reparseAsQualifiedId(AST::ExpressionNode *expr); + +protected: + Engine *driver; + MemoryPool *pool; + int tos; + int stack_size; + Value *sym_stack; + int *state_stack; + AST::SourceLocation *location_stack; + QStringRef *string_stack; + + AST::Node *program; + + // error recovery + enum { TOKEN_BUFFER_SIZE = 3 }; + + struct SavedToken { + int token; + double dval; + AST::SourceLocation loc; + QStringRef spell; + }; + + double yylval; + QStringRef yytokenspell; + AST::SourceLocation yylloc; + AST::SourceLocation yyprevlloc; + + SavedToken token_buffer[TOKEN_BUFFER_SIZE]; + SavedToken *first_token; + SavedToken *last_token; + + QList<DiagnosticMessage> diagnostic_messages; +}; + +} // end of namespace QbsQmlJS + + + +#define J_SCRIPT_REGEXPLITERAL_RULE1 79 + +#define J_SCRIPT_REGEXPLITERAL_RULE2 80 + +#endif // QMLJSPARSER_P_H diff --git a/src/lib/corelib/qbs.h b/src/lib/corelib/qbs.h new file mode 100644 index 000000000..56ae00990 --- /dev/null +++ b/src/lib/corelib/qbs.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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_H +#define QBS_H + +#include "api/jobs.h" +#include "api/project.h" +#include "api/projectdata.h" +#include "logging/ilogsink.h" +#include "tools/buildoptions.h" +#include "tools/cleanoptions.h" +#include "tools/error.h" +#include "tools/installoptions.h" +#include "tools/preferences.h" +#include "tools/processresult.h" +#include "tools/settings.h" +#include "tools/setupprojectparameters.h" + +#endif // QBS_H diff --git a/src/lib/corelib/tools/buildoptions.cpp b/src/lib/corelib/tools/buildoptions.cpp new file mode 100644 index 000000000..2261124d8 --- /dev/null +++ b/src/lib/corelib/tools/buildoptions.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** 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 "buildoptions.h" + +#include <QSharedData> +#include <QThread> + +namespace qbs { +namespace Internal { + +class BuildOptionsPrivate : public QSharedData +{ +public: + BuildOptionsPrivate() : maxJobCount(0), dryRun(false), keepGoing(false), logElapsedTime(false) + { + } + + QStringList changedFiles; + QStringList activeFileTags; + int maxJobCount; + bool dryRun; + bool keepGoing; + bool forceTimestampCheck; + bool logElapsedTime; +}; + +} // namespace Internal + +/*! + * \class BuildOptions + * \brief The \c BuildOptions class comprises parameters that influence the behavior of + * build and clean operations. + */ + +/*! + * \brief Creates a \c BuildOptions object and initializes its members to sensible default values. + */ +BuildOptions::BuildOptions() : d(new Internal::BuildOptionsPrivate) +{ +} + +BuildOptions::BuildOptions(const BuildOptions &other) : d(other.d) +{ +} + +BuildOptions &BuildOptions::operator=(const BuildOptions &other) +{ + d = other.d; + return *this; +} + +BuildOptions::~BuildOptions() +{ +} + +/*! + * \brief If non-empty, qbs pretends that only these files have changed. + * By default, this list is empty. + */ +QStringList BuildOptions::changedFiles() const +{ + return d->changedFiles; +} + +/*! + * \brief If the given list is empty, qbs will pretend only the listed files are changed. + * \note The list elements must be absolute file paths. + */ +void BuildOptions::setChangedFiles(const QStringList &changedFiles) +{ + d->changedFiles = changedFiles; +} + +/*! + * \brief The list of active file tags. + * \sa setActiveFileTags + */ +QStringList BuildOptions::activeFileTags() const +{ + return d->activeFileTags; +} + +/*! + * \brief Set the list of active file tags. + * If this list is non-empty, then every transformer with non-matching output file tags is skipped. + * E.g. set changed files to "foo.cpp" and activeFileTags to ["obj"] to run the compiler + * on foo.cpp without further processing like linking. + * \sa activeFileTags + */ +void BuildOptions::setActiveFileTags(const QStringList &fileTags) +{ + d->activeFileTags = fileTags; +} + +/*! + * \brief Returns the default value for \c maxJobCount. + * This value will be used when \c maxJobCount has not been set explicitly. + */ +int BuildOptions::defaultMaxJobCount() +{ + return QThread::idealThreadCount(); +} + +/*! + * \brief Returns the maximum number of build commands to run concurrently. + * If the value is not valid (i.e. <= 0), a sensible one will be derived at build time + * from the number of available processor cores at build time. + * The default is 0. + * \sa BuildOptions::defaultMaxJobCount + */ +int BuildOptions::maxJobCount() const +{ + return d->maxJobCount; +} + +/*! + * \brief Controls how many build commands can be run in parallel. + * A value <= 0 leaves the decision to qbs. + */ +void BuildOptions::setMaxJobCount(int jobCount) +{ + d->maxJobCount = jobCount; +} + +/*! + * \brief Returns true iff qbs will not actually execute any commands, but just show what + * would happen. + * The default is false. + */ +bool BuildOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether qbs will actually build something. + * If the argument is true, qbs will just emit information about what it would do. Otherwise, + * the build is actually done. + * \note After you build with this setting enabled, the next call to \c build() on the same + * \c Project object will do nothing, since the internal state needs to be updated the same way + * as if an actual build had happened. You'll need to create a new \c Project object to do + * a real build afterwards. + */ +void BuildOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * \brief Returns true iff a build will continue after an error. + * E.g. a failed compile command will result in a warning message being printed, instead of + * stopping the build process right away. However, there might still be fatal errors after which the + * build process cannot continue. + * The default is \c false. + */ +bool BuildOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether a qbs will try to continue building after an error has occurred. + */ +void BuildOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true if qbs is to use physical timestamps instead of the timestamps stored in the + * build graph. + * The default is \c false. + */ +bool BuildOptions::forceTimestampCheck() const +{ + return d->forceTimestampCheck; +} + +/*! + * \brief Controls whether qbs should use physical timestamps for up-to-date checks. + */ +void BuildOptions::setForceTimestampCheck(bool enabled) +{ + d->forceTimestampCheck = enabled; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is \c false. + */ +bool BuildOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the build time will be measured and logged. + */ +void BuildOptions::setLogElapsedTime(bool log) +{ + d->logElapsedTime = log; +} + + +bool operator==(const BuildOptions &bo1, const BuildOptions &bo2) +{ + return bo1.changedFiles() == bo2.changedFiles() + && bo1.dryRun() == bo2.dryRun() + && bo1.keepGoing() == bo2.keepGoing() + && bo1.logElapsedTime() == bo2.logElapsedTime() + && bo1.maxJobCount() == bo2.maxJobCount(); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/buildoptions.h b/src/lib/corelib/tools/buildoptions.h new file mode 100644 index 000000000..2c277fc3b --- /dev/null +++ b/src/lib/corelib/tools/buildoptions.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_BUILDOPTIONS_H +#define QBS_BUILDOPTIONS_H + +#include "qbs_export.h" + +#include <QSharedDataPointer> +#include <QStringList> + +namespace qbs { +namespace Internal { class BuildOptionsPrivate; } + +class QBS_EXPORT BuildOptions +{ +public: + BuildOptions(); + BuildOptions(const BuildOptions &other); + BuildOptions &operator=(const BuildOptions &other); + ~BuildOptions(); + + QStringList changedFiles() const; + void setChangedFiles(const QStringList &changedFiles); + + QStringList activeFileTags() const; + void setActiveFileTags(const QStringList &fileTags); + + static int defaultMaxJobCount(); + int maxJobCount() const; + void setMaxJobCount(int jobCount); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool forceTimestampCheck() const; + void setForceTimestampCheck(bool enabled); + + bool logElapsedTime() const; + void setLogElapsedTime(bool log); + +private: + QSharedDataPointer<Internal::BuildOptionsPrivate> d; +}; + +bool operator==(const BuildOptions &bo1, const BuildOptions &bo2); +inline bool operator!=(const BuildOptions &bo1, const BuildOptions &bo2) { return !(bo1 == bo2); } + +} // namespace qbs + +#endif // QBS_BUILDOPTIONS_H diff --git a/src/lib/corelib/tools/cleanoptions.cpp b/src/lib/corelib/tools/cleanoptions.cpp new file mode 100644 index 000000000..359fd47dc --- /dev/null +++ b/src/lib/corelib/tools/cleanoptions.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 "cleanoptions.h" + +#include <QSharedData> + +namespace qbs { +namespace Internal { + +class CleanOptionsPrivate : public QSharedData +{ +public: + CleanOptionsPrivate() + : cleanType(CleanOptions::CleanupAll), dryRun(false), + keepGoing(false), logElapsedTime(false) + { } + + CleanOptions::CleanType cleanType; + bool dryRun; + bool keepGoing; + bool logElapsedTime; +}; + +} + +/*! + * \class CleanOptions + * \brief The \c CleanOptions class comprises parameters that influence the behavior of + * cleaning operations. + */ + +/*! + * \enum CleanOptions::CleanType + * This enum type specifies which kind of build artifacts to remove. + * \value CleanupAll Indicates that all files created by the build process should be removed. + * \value CleanupTemporaries Indicates that only intermediate build artifacts should be removed. + * If, for example, the product to clean up for is a Linux shared library, the .so file + * would be left on the disk, but the .o files would be removed. + */ + +CleanOptions::CleanOptions() : d(new Internal::CleanOptionsPrivate) +{ +} + +CleanOptions::CleanOptions(const CleanOptions &other) : d(other.d) +{ +} + +CleanOptions &CleanOptions::operator=(const CleanOptions &other) +{ + d = other.d; + return *this; +} + +CleanOptions::~CleanOptions() +{ +} + +/*! + * \brief Returns information about which type of artifacts will be removed. + */ +CleanOptions::CleanType CleanOptions::cleanType() const +{ + return d->cleanType; +} + +/*! + * \brief Controls which kind of artifacts to remove. + * \sa CleanOptions::CleanType + */ +void CleanOptions::setCleanType(CleanOptions::CleanType cleanType) +{ + d->cleanType = cleanType; +} + +/*! + * \brief Returns true iff qbs will not actually remove any files, but just show what would happen. + * The default is false. + */ +bool CleanOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether clean-up will actually take place. + * If the argument is true, then qbs will emit information about which files would be removed + * instead of actually doing it. + */ +void CleanOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * Returns true iff clean-up will continue if an error occurs. + * The default is false. + */ +bool CleanOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether to abort on errors. + * If the argument is true, then if a file cannot be removed e.g. due to a permission problem, + * a warning will be printed and the clean-up will continue. If the argument is false, + * then the clean-up will abort immediately in case of an error. + */ +void CleanOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is false. + */ +bool CleanOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the clean-up time will be measured and logged. + */ +void CleanOptions::setLogElapsedTime(bool log) +{ + d->logElapsedTime = log; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/cleanoptions.h b/src/lib/corelib/tools/cleanoptions.h new file mode 100644 index 000000000..0e3a0a564 --- /dev/null +++ b/src/lib/corelib/tools/cleanoptions.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_CLEANOPTIONS_H +#define QBS_CLEANOPTIONS_H + +#include "qbs_export.h" + +#include <QSharedDataPointer> +#include <QString> + +namespace qbs { +namespace Internal { class CleanOptionsPrivate; } + +class QBS_EXPORT CleanOptions +{ +public: + CleanOptions(); + CleanOptions(const CleanOptions &other); + CleanOptions &operator=(const CleanOptions &other); + ~CleanOptions(); + + enum CleanType { CleanupAll, CleanupTemporaries }; + CleanType cleanType() const; + void setCleanType(CleanType cleanType); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool logElapsedTime() const; + void setLogElapsedTime(bool log); + +private: + QSharedDataPointer<Internal::CleanOptionsPrivate> d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp new file mode 100644 index 000000000..70a1814cd --- /dev/null +++ b/src/lib/corelib/tools/codelocation.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 "codelocation.h" + +#include <QDataStream> +#include <QDir> +#include <QRegExp> +#include <QSharedData> +#include <QString> + +namespace qbs { + +class CodeLocation::CodeLocationPrivate : public QSharedData +{ +public: + QString fileName; + int line; + int column; +}; + +CodeLocation::CodeLocation() : d(new CodeLocationPrivate) +{ + d->line = d->column = -1; +} + +CodeLocation::CodeLocation(const QString &aFileName, int aLine, int aColumn) + : d(new CodeLocationPrivate) +{ + d->fileName = aFileName; + d->line = aLine; + d->column = aColumn; +} + +CodeLocation::CodeLocation(const CodeLocation &other) : d(other.d) +{ +} + +CodeLocation &CodeLocation::operator=(const CodeLocation &other) +{ + d = other.d; + return *this; +} + +CodeLocation::~CodeLocation() +{ +} + +QString CodeLocation::fileName() const +{ + return d->fileName; +} + +int CodeLocation::line() const +{ + return d->line; +} + +int CodeLocation::column() const +{ + return d->column; +} + +bool CodeLocation::isValid() const +{ + return !fileName().isEmpty(); +} + +QString CodeLocation::toString() const +{ + QString str; + if (isValid()) { + str = QDir::toNativeSeparators(fileName()); + QString lineAndColumn; + if (line() > 0 && !str.contains(QRegExp(QLatin1String(":[0-9]+$")))) + lineAndColumn += QLatin1Char(':') + QString::number(line()); + if (column() > 0 && !str.contains(QRegExp(QLatin1String(":[0-9]+:[0-9]+$")))) + lineAndColumn += QLatin1Char(':') + QString::number(column()); + str += lineAndColumn; + } + return str; +} + +bool operator==(const CodeLocation &cl1, const CodeLocation &cl2) +{ + return cl1.fileName() == cl2.fileName() && cl1.line() == cl2.line() + && cl1.column() == cl2.column(); +} + +bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2) +{ + return !(cl1 == cl2); +} + +QDataStream &operator<<(QDataStream &s, const CodeLocation &o) +{ + s << o.fileName(); + s << o.line(); + s << o.column(); + return s; +} + +QDataStream &operator>>(QDataStream &s, CodeLocation &o) +{ + QString fileName; + int line; + int column; + s >> fileName; + s >> line; + s >> column; + o = CodeLocation(fileName, line, column); + return s; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/codelocation.h b/src/lib/corelib/tools/codelocation.h new file mode 100644 index 000000000..204192b5a --- /dev/null +++ b/src/lib/corelib/tools/codelocation.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_SOURCELOCATION_H +#define QBS_SOURCELOCATION_H + +#include "qbs_export.h" + +#include <QExplicitlySharedDataPointer> + +QT_BEGIN_NAMESPACE +class QDataStream; +class QString; +QT_END_NAMESPACE + +namespace qbs { + +class QBS_EXPORT CodeLocation +{ +public: + CodeLocation(); + CodeLocation(const QString &aFileName, int aLine = -1, int aColumn = -1); + CodeLocation(const CodeLocation &other); + CodeLocation &operator=(const CodeLocation &other); + ~CodeLocation(); + + QString fileName() const; + int line() const; + int column() const; + + bool isValid() const; + QString toString() const; +private: + class CodeLocationPrivate; + QExplicitlySharedDataPointer<CodeLocationPrivate> d; +}; + +QBS_EXPORT bool operator==(const CodeLocation &cl1, const CodeLocation &cl2); +QBS_EXPORT bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2); + +QDataStream &operator<<(QDataStream &s, const CodeLocation &o); +QDataStream &operator>>(QDataStream &s, CodeLocation &o); + +} // namespace qbs + +#endif // QBS_SOURCELOCATION_H diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp new file mode 100644 index 000000000..4bcdc620c --- /dev/null +++ b/src/lib/corelib/tools/error.cpp @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** 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 "error.h" + +#include <QSharedData> +#include <QStringList> + +namespace qbs { + +class ErrorItem::ErrorItemPrivate : public QSharedData +{ +public: + QString description; + CodeLocation codeLocation; +}; + +/*! + * \class ErrorData + * \brief The \c ErrorData class describes (part of) an error resulting from a qbs operation. + * It is always delivered as part of an \c Error. + * \sa Error + */ + +ErrorItem::ErrorItem() : d(new ErrorItemPrivate) +{ +} + +ErrorItem::ErrorItem(const QString &description, const CodeLocation &codeLocation) + : d(new ErrorItemPrivate) +{ + d->description = description; + d->codeLocation = codeLocation; +} + +ErrorItem::ErrorItem(const ErrorItem &rhs) : d(rhs.d) +{ +} + +ErrorItem &ErrorItem::operator=(const ErrorItem &other) +{ + d = other.d; + return *this; +} + +ErrorItem::~ErrorItem() +{ +} + +QString ErrorItem::description() const +{ + return d->description; +} + +CodeLocation ErrorItem::codeLocation() const +{ + return d->codeLocation; +} + +/*! + * \fn const QString &ErrorData::description() const + * \brief A general description of the error. + */ + + /*! + * \fn const QString &ErrorData::codeLocation() const + * \brief The location at which file in which the error occurred. + * \note This information might not be applicable, in which case location().isValid() returns false + */ + +/*! + * \brief A full textual description of the error using all available information. + */ +QString ErrorItem::toString() const +{ + QString str = codeLocation().toString(); + if (!str.isEmpty()) + str += QLatin1Char(' '); + return str += description(); +} + + +class ErrorInfo::ErrorInfoPrivate : public QSharedData +{ +public: + ErrorInfoPrivate() : internalError(false) { } + + QList<ErrorItem> items; + bool internalError; +}; + +/*! + * \class Error + * \brief Represents an error resulting from a qbs operation. + * It is made up of one or more \c ErrorData objects. + * \sa ErrorData + */ + +ErrorInfo::ErrorInfo() : d(new ErrorInfoPrivate) +{ +} + +ErrorInfo::ErrorInfo(const ErrorInfo &rhs) : d(rhs.d) +{ +} + +ErrorInfo::ErrorInfo(const QString &description, const CodeLocation &location, bool internalError) + : d(new ErrorInfoPrivate) +{ + append(description, location); + d->internalError = internalError; +} + +ErrorInfo &ErrorInfo::operator =(const ErrorInfo &other) +{ + d = other.d; + return *this; +} + +ErrorInfo::~ErrorInfo() +{ +} + +void ErrorInfo::append(const QString &description, const CodeLocation &location) +{ + d->items.append(ErrorItem(description, location)); +} + +void ErrorInfo::prepend(const QString &description, const CodeLocation &location) +{ + d->items.prepend(ErrorItem(description, location)); +} + +/*! + * \brief A list of concrete error description. + * Most often, there will be one element in this list, but there can be more e.g. to illustrate + * how an error condition propagates through several source files. + */ +QList<ErrorItem> ErrorInfo::items() const +{ + return d->items; +} + +void ErrorInfo::clear() +{ + d->items.clear(); +} + +/*! + * \brief A complete textual description of the error. + * All "sub-errors" will be represented. + * \sa Error::entries() + */ +QString ErrorInfo::toString() const +{ + QStringList lines; + foreach (const ErrorItem &e, d->items) + lines.append(e.toString()); + return lines.join(QLatin1String("\n")); +} + +/*! + * \brief Returns true if this error represents a bug in qbs, false otherwise. + */ +bool ErrorInfo::isInternalError() const +{ + return d->internalError; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h new file mode 100644 index 000000000..3aae9e239 --- /dev/null +++ b/src/lib/corelib/tools/error.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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_ERROR +#define QBS_ERROR + +#include "codelocation.h" + +#include <QExplicitlySharedDataPointer> +#include <QList> +#include <QMetaType> +#include <QSharedDataPointer> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +class CodeLocation; + +class QBS_EXPORT ErrorItem +{ + friend class ErrorInfo; +public: + ErrorItem(); + ErrorItem(const ErrorItem &rhs); + ErrorItem &operator=(const ErrorItem &other); + ~ErrorItem(); + + QString description() const; + CodeLocation codeLocation() const; + QString toString() const; + +private: + ErrorItem(const QString &description, const CodeLocation &codeLocation); + + class ErrorItemPrivate; + QExplicitlySharedDataPointer<ErrorItemPrivate> d; +}; + +class QBS_EXPORT ErrorInfo +{ +public: + ErrorInfo(); + ErrorInfo(const ErrorInfo &rhs); + ErrorInfo(const QString &description, const CodeLocation &location = CodeLocation(), + bool internalError = false); + ErrorInfo &operator=(const ErrorInfo &other); + ~ErrorInfo(); + + void append(const QString &description, const CodeLocation &location = CodeLocation()); + void prepend(const QString &description, const CodeLocation &location = CodeLocation()); + QList<ErrorItem> items() const; + bool hasError() const { return !items().isEmpty(); } + void clear(); + QString toString() const; + bool isInternalError() const; + +private: + class ErrorInfoPrivate; + QSharedDataPointer<ErrorInfoPrivate> d; +}; + +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::ErrorInfo) + +#endif // QBS_ERROR diff --git a/src/lib/corelib/tools/fileinfo.cpp b/src/lib/corelib/tools/fileinfo.cpp new file mode 100644 index 000000000..9eca8c2ca --- /dev/null +++ b/src/lib/corelib/tools/fileinfo.cpp @@ -0,0 +1,477 @@ +/**************************************************************************** +** +** 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 "fileinfo.h" + +#include <logging/translator.h> +#include <tools/hostosinfo.h> +#include <tools/qbsassert.h> + +#include <QCoreApplication> +#include <QDateTime> +#include <QDir> +#include <QFileInfo> + +#if defined(Q_OS_UNIX) +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#elif defined(Q_OS_WIN) +#include <qt_windows.h> +#endif + +namespace qbs { +namespace Internal { + +QString FileInfo::fileName(const QString &fp) +{ + int last = fp.lastIndexOf(QLatin1Char('/')); + if (last < 0) + return fp; + return fp.mid(last + 1); +} + +QString FileInfo::baseName(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.indexOf(QLatin1Char('.')); + if (dot < 0) + return fp; + return fn.mid(0, dot); +} + +QString FileInfo::completeBaseName(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.lastIndexOf(QLatin1Char('.')); + if (dot < 0) + return fp; + return fn.mid(0, dot); +} + +QString FileInfo::path(const QString &fp) +{ + if (fp.isEmpty()) + return QString(); + if (fp.at(fp.size() - 1) == QLatin1Char('/')) + return fp; + int last = fp.lastIndexOf(QLatin1Char('/')); + if (last < 0) + return QLatin1String("."); + return QDir::cleanPath(fp.mid(0, last)); +} + +void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QString *dirPath, QString *fileName) +{ + int idx = filePath.lastIndexOf(QLatin1Char('/')); + if (idx < 0) { + dirPath->clear(); + *fileName = filePath; + return; + } + *dirPath = filePath.left(idx); + *fileName = filePath.mid(idx + 1); +} + +void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName) +{ + int idx = filePath.lastIndexOf(QLatin1Char('/')); + if (idx < 0) { + dirPath->clear(); + *fileName = QStringRef(&filePath); + return; + } + *dirPath = filePath.leftRef(idx); + *fileName = filePath.midRef(idx + 1); +} + +bool FileInfo::exists(const QString &fp) +{ + return FileInfo(fp).exists(); +} + +// from creator/src/shared/proparser/ioutils.cpp +bool FileInfo::isAbsolute(const QString &path) +{ + const int n = path.size(); + if (n == 0) + return false; + const QChar at0 = path.at(0); + if (at0 == QLatin1Char('/')) + return true; + if (HostOsInfo::isWindowsHost()) { + if (at0 == QLatin1Char('\\')) + return true; + // Unlike QFileInfo, this won't accept a relative path with a drive letter. + // Such paths result in a royal mess anyway ... + if (n >= 3 && path.at(1) == QLatin1Char(':') && at0.isLetter() + && (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\'))) + return true; + } + return false; +} + +bool FileInfo::isPattern(const QString &str) +{ + return isPattern(QStringRef(&str)); +} + +bool FileInfo::isPattern(const QStringRef &str) +{ + for (int i = 0; i < str.size(); ++i) { + const QChar ch = str.at(i); + if (ch == QLatin1Char('*') || ch == QLatin1Char('?') + || ch == QLatin1Char(']') || ch == QLatin1Char('[')) { + return true; + } + } + return false; +} + +/** + * Concatenates the paths \a base and \a rel. + * Base must be an absolute path. + * Double dots at the start of \a rel are handled. + * This function assumes that both paths are clean, that is they don't contain + * double slashes or redundant dot parts. + */ +QString FileInfo::resolvePath(const QString &base, const QString &rel) +{ + QBS_ASSERT(isAbsolute(base), return QString()); + if (isAbsolute(rel)) + return rel; + if (rel.size() == 1 && rel.at(0) == QLatin1Char('.')) + return base; + if (rel.size() == 1 && rel.at(0) == QLatin1Char('~')) + return QDir::homePath(); + if (rel.startsWith(QLatin1String("~/"))) + return QDir::homePath() + rel.mid(1); + + QString r = base; + if (r.endsWith(QLatin1Char('/'))) + r.chop(1); + + QString s = rel; + while (s.startsWith(QLatin1String("../"))) { + s.remove(0, 3); + int idx = r.lastIndexOf(QLatin1Char('/')); + if (idx >= 0) + r.truncate(idx); + } + + r.reserve(r.length() + 1 + s.length()); + r += QLatin1Char('/'); + r += s; + return r; +} + +bool FileInfo::globMatches(const QRegExp ®exp, const QString &fileName) +{ + const QString pattern = regexp.pattern(); + // May be it's simple wildcard, i.e. "*.cpp"? + if (pattern.startsWith(QLatin1Char('*')) && !isPattern(pattern.midRef(1))) { + // Yes, it's rather simple to just check the extension + return fileName.endsWith(pattern.midRef(1)); + } + return regexp.exactMatch(fileName); +} + +bool FileInfo::isFileCaseCorrect(const QString &filePath) +{ +#if defined(Q_OS_WIN) + // QFileInfo::canonicalFilePath() does not return the real case of the file path on Windows. + QFileInfo fi(filePath); + const QString absolute = fi.absoluteFilePath(); + WIN32_FIND_DATA fd; + HANDLE hFindFile = ::FindFirstFile((wchar_t*)absolute.utf16(), &fd); + if (hFindFile == INVALID_HANDLE_VALUE) + return false; + const QString actualFileName = QString::fromWCharArray(fd.cFileName); + FindClose(hFindFile); + return actualFileName == fi.fileName(); +#elif defined(Q_OS_DARWIN) + QFileInfo fi(filePath); + return fi.absoluteFilePath() == fi.canonicalFilePath(); +#else + Q_UNUSED(filePath) + return true; +#endif +} + +#if defined(Q_OS_WIN) + +#define z(x) reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA*>(const_cast<FileInfo::InternalStatType*>(&x)) + +template<bool> struct CompileTimeAssert; +template<> struct CompileTimeAssert<true> {}; + +FileInfo::FileInfo(const QString &fileName) +{ + static CompileTimeAssert< + sizeof(FileInfo::InternalStatType) == sizeof(WIN32_FILE_ATTRIBUTE_DATA) + > internal_type_has_wrong_size; + Q_UNUSED(internal_type_has_wrong_size); + if (!GetFileAttributesEx(reinterpret_cast<const WCHAR*>(fileName.utf16()), + GetFileExInfoStandard, &m_stat)) + { + ZeroMemory(z(m_stat), sizeof(WIN32_FILE_ATTRIBUTE_DATA)); + z(m_stat)->dwFileAttributes = INVALID_FILE_ATTRIBUTES; + } +} + +bool FileInfo::exists() const +{ + return z(m_stat)->dwFileAttributes != INVALID_FILE_ATTRIBUTES; +} + +FileTime FileInfo::lastModified() const +{ + const FileTime::InternalType* ft_it; + ft_it = reinterpret_cast<const FileTime::InternalType*>(&z(m_stat)->ftLastWriteTime); + return FileTime(*ft_it); +} + +FileTime FileInfo::lastStatusChange() const +{ + return lastModified(); +} + +bool FileInfo::isDir() const +{ + return z(m_stat)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +} + +static QString resolveSymlinks(const QString &fileName) +{ + QFileInfo fi(fileName); + while (fi.isSymLink()) + fi.setFile(fi.symLinkTarget()); + return fi.absoluteFilePath(); +} + +QString applicationDirPath() +{ + static const QString appDirPath = FileInfo::path(resolveSymlinks(QCoreApplication::applicationFilePath())); + return appDirPath; +} + +#elif defined(Q_OS_UNIX) + +FileInfo::FileInfo(const QString &fileName) +{ + if (stat(fileName.toLocal8Bit(), &m_stat) == -1) + m_stat.st_mtime = 0; +} + +bool FileInfo::exists() const +{ + return m_stat.st_mtime != 0; +} + +FileTime FileInfo::lastModified() const +{ + return m_stat.st_mtime; +} + +FileTime FileInfo::lastStatusChange() const +{ + return m_stat.st_ctime; +} + +bool FileInfo::isDir() const +{ + return S_ISDIR(m_stat.st_mode); +} + +#endif + +// adapted from qtc/plugins/vcsbase/cleandialog.cpp +bool removeFileRecursion(const QFileInfo &f, QString *errorMessage) +{ + if (!f.exists()) + return true; + if (f.isDir()) { + const QDir dir(f.absoluteFilePath()); + foreach(const QFileInfo &fi, dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Hidden)) + removeFileRecursion(fi, errorMessage); + QDir parent = f.absoluteDir(); + if (!parent.rmdir(f.fileName())) { + errorMessage->append(Tr::tr("The directory %1 could not be deleted."). + arg(QDir::toNativeSeparators(f.absoluteFilePath()))); + return false; + } + } else { + QFile file(f.absoluteFilePath()); + file.setPermissions(f.permissions() | QFile::WriteUser); + if (!file.remove()) { + if (!errorMessage->isEmpty()) + errorMessage->append(QLatin1Char('\n')); + errorMessage->append(Tr::tr("The file %1 could not be deleted."). + arg(QDir::toNativeSeparators(f.absoluteFilePath()))); + return false; + } + } + return true; +} + +bool removeDirectoryWithContents(const QString &path, QString *errorMessage) +{ + QFileInfo f(path); + if (f.exists() && !f.isDir()) { + *errorMessage = Tr::tr("%1 is not a directory.").arg(QDir::toNativeSeparators(path)); + return false; + } + return removeFileRecursion(f, errorMessage); +} + +/*! + * Returns the stored link target of the symbolic link \a{filePath}. + * Unlike QFileInfo::symLinkTarget, this will not make the link target an absolute path. + */ +static QByteArray storedLinkTarget(const QString &filePath) +{ + QByteArray result; + +#ifdef Q_OS_UNIX + const QByteArray nativeFilePath = QFile::encodeName(filePath); + ssize_t len; + while (true) { + struct stat sb; + if (lstat(nativeFilePath.constData(), &sb)) { + qWarning("storedLinkTarget: lstat for %s failed with error code %d", + nativeFilePath.constData(), errno); + return QByteArray(); + } + + result.resize(sb.st_size); + len = readlink(nativeFilePath.constData(), result.data(), sb.st_size + 1); + if (len < 0) { + qWarning("storedLinkTarget: readlink for %s failed with error code %d", + nativeFilePath.constData(), errno); + return QByteArray(); + } + + if (len < sb.st_size) { + result.resize(len); + break; + } + if (len == sb.st_size) + break; + } +#else + Q_UNUSED(filePath); +#endif // Q_OS_UNIX + + return result; +} + +static bool createSymLink(const QByteArray &path1, const QString &path2) +{ +#ifdef Q_OS_UNIX + const QByteArray newPath = QFile::encodeName(path2); + unlink(newPath.constData()); + return symlink(path1.constData(), newPath.constData()) == 0; +#else + Q_UNUSED(path1); + Q_UNUSED(path2); + return false; +#endif // Q_OS_UNIX +} + +/*! + Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. + \a tgtFilePath will contain the target directory, which will be created. Example usage: + + \code + QString error; + book ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); + if (!ok) + qDebug() << error; + \endcode + + This will copy the contents of /foo/bar into to the baz directory under /foo, + which will be created in the process. + + \return Whether the operation succeeded. + \note Function was adapted from qtc/src/libs/fileutils.cpp +*/ + +bool copyFileRecursion(const QString &srcFilePath, const QString &tgtFilePath, + bool preserveSymLinks, QString *errorMessage) +{ + QFileInfo srcFileInfo(srcFilePath); + QFileInfo tgtFileInfo(tgtFilePath); + const QString targetDirPath = tgtFileInfo.absoluteDir().path(); + if (!QDir::root().mkpath(targetDirPath)) { + *errorMessage = Tr::tr("The directory '%1' could not be created.") + .arg(QDir::toNativeSeparators(targetDirPath)); + return false; + } + if (HostOsInfo::isAnyUnixHost() && preserveSymLinks && srcFileInfo.isSymLink()) { + // For now, disable symlink preserving copying on Windows. + // MS did a good job to prevent people from using symlinks - even if they are supported. + if (!createSymLink(storedLinkTarget(srcFilePath), tgtFilePath)) { + *errorMessage = Tr::tr("The symlink '%1' could not be created.") + .arg(tgtFilePath); + return false; + } + } else if (srcFileInfo.isDir()) { + QDir sourceDir(srcFilePath); + QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot + | QDir::Hidden | QDir::System); + foreach (const QString &fileName, fileNames) { + const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName; + const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName; + if (!copyFileRecursion(newSrcFilePath, newTgtFilePath, preserveSymLinks, errorMessage)) + return false; + } + } else { + if (tgtFileInfo.exists() && srcFileInfo.lastModified() <= tgtFileInfo.lastModified()) + return true; + QFile file(srcFilePath); + QFile targetFile(tgtFilePath); + if (targetFile.exists()) { + targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser); + if (!targetFile.remove()) { + *errorMessage = Tr::tr("Could not remove file '%1'. %2") + .arg(QDir::toNativeSeparators(tgtFilePath), targetFile.errorString()); + } + } + if (!file.copy(tgtFilePath)) { + *errorMessage = Tr::tr("Could not copy file '%1' to '%2'. %3") + .arg(QDir::toNativeSeparators(srcFilePath), QDir::toNativeSeparators(tgtFilePath), + file.errorString()); + return false; + } + } + return true; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/fileinfo.h b/src/lib/corelib/tools/fileinfo.h new file mode 100644 index 000000000..b5731cedd --- /dev/null +++ b/src/lib/corelib/tools/fileinfo.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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_FILEINFO_H +#define QBS_FILEINFO_H + +#include "filetime.h" +#include "qbs_export.h" + +#if defined(Q_OS_UNIX) +#include <sys/stat.h> +#endif + +#include <QString> + +QT_FORWARD_DECLARE_CLASS(QFileInfo) + +namespace qbs { +namespace Internal { + +class FileInfo +{ +public: + FileInfo(const QString &fileName); + + bool exists() const; + FileTime lastModified() const; + FileTime lastStatusChange() const; + bool isDir() const; + + static QString fileName(const QString &fp); + static QString baseName(const QString &fp); + static QString completeBaseName(const QString &fp); + static QString path(const QString &fp); + static void splitIntoDirectoryAndFileName(const QString &filePath, QString *dirPath, QString *fileName); + static void splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName); + static bool exists(const QString &fp); + static bool isAbsolute(const QString &fp); + static bool isPattern(const QStringRef &str); + static bool isPattern(const QString &str); + static QString resolvePath(const QString &base, const QString &rel); + static bool globMatches(const QRegExp &pattern, const QString &subject); + static bool isFileCaseCorrect(const QString &filePath); + +private: +#if defined(Q_OS_WIN) + struct InternalStatType + { + quint8 z[36]; + }; +#elif defined(Q_OS_UNIX) + typedef struct stat InternalStatType; +#else +# error unknown platform +#endif + InternalStatType m_stat; +}; + +bool removeFileRecursion(const QFileInfo &f, QString *errorMessage); + +// FIXME: Used by tests. +bool QBS_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage); +bool QBS_EXPORT copyFileRecursion(const QString &sourcePath, const QString &targetPath, + bool preserveSymLinks, QString *errorMessage); + +} // namespace Internal +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/filetime.h b/src/lib/corelib/tools/filetime.h new file mode 100644 index 000000000..0dc0524df --- /dev/null +++ b/src/lib/corelib/tools/filetime.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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_FILETIME_H +#define QBS_FILETIME_H + +#include <QDataStream> +#include <QDebug> + +#if defined(Q_OS_UNIX) +#include <time.h> +#endif + +namespace qbs { +namespace Internal { + +class FileTime +{ +public: +#if defined(Q_OS_UNIX) + typedef time_t InternalType; +#elif defined(Q_OS_WIN) + typedef quint64 InternalType; +#else +# error unknown platform +#endif + + FileTime(); + FileTime(const InternalType &ft) + : m_fileTime(ft) + { } + + bool operator < (const FileTime &rhs) const; + bool operator > (const FileTime &rhs) const; + bool operator <= (const FileTime &rhs) const; + bool operator >= (const FileTime &rhs) const; + bool operator == (const FileTime &rhs) const; + bool operator != (const FileTime &rhs) const; + + void clear(); + bool isValid() const; + QString toString() const; + + static FileTime currentTime(); + + friend class FileInfo; + InternalType m_fileTime; +}; + +inline bool FileTime::operator > (const FileTime &rhs) const +{ + return rhs < *this; +} + +inline bool FileTime::operator <= (const FileTime &rhs) const +{ + return operator < (rhs) || operator == (rhs); +} + +inline bool FileTime::operator >= (const FileTime &rhs) const +{ + return operator > (rhs) || operator == (rhs); +} + +inline bool FileTime::operator == (const FileTime &rhs) const +{ + return m_fileTime == rhs.m_fileTime; +} + +inline bool FileTime::operator != (const FileTime &rhs) const +{ + return !operator==(rhs); +} + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE + +inline QDataStream& operator>>(QDataStream &stream, qbs::Internal::FileTime &ft) +{ + quint64 u; + stream >> u; + ft.m_fileTime = u; + return stream; +} + +inline QDataStream& operator<<(QDataStream &stream, const qbs::Internal::FileTime &ft) +{ + stream << (quint64)ft.m_fileTime; + return stream; +} + +inline QDebug operator<<(QDebug dbg, const qbs::Internal::FileTime &t) +{ + dbg.nospace() << t.toString(); + return dbg.space(); +} + +QT_END_NAMESPACE + +#endif // QBS_FILETIME_H diff --git a/src/lib/corelib/tools/filetime_unix.cpp b/src/lib/corelib/tools/filetime_unix.cpp new file mode 100644 index 000000000..945be8f44 --- /dev/null +++ b/src/lib/corelib/tools/filetime_unix.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 "filetime.h" + +#include <QDateTime> +#include <QString> + +#include <time.h> + +namespace qbs { +namespace Internal { + +FileTime::FileTime() + : m_fileTime(0) +{ +} + +bool FileTime::operator < (const FileTime &rhs) const +{ + return m_fileTime < rhs.m_fileTime; +} + +void FileTime::clear() +{ + m_fileTime = 0; +} + +bool FileTime::isValid() const +{ + return m_fileTime != 0; +} + +FileTime FileTime::currentTime() +{ + return time(0); +} + +QString FileTime::toString() const +{ + QDateTime dt; + dt.setTime_t(m_fileTime); + return dt.toString(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/filetime_win.cpp b/src/lib/corelib/tools/filetime_win.cpp new file mode 100644 index 000000000..b3a7fab67 --- /dev/null +++ b/src/lib/corelib/tools/filetime_win.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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 "filetime.h" + +#include <QString> +#include <qt_windows.h> +#ifdef Q_CC_MSVC +#include <strsafe.h> +#endif // Q_CC_MSVC + +namespace qbs { +namespace Internal { + +template<bool> struct CompileTimeAssert; +template<> struct CompileTimeAssert<true> {}; + +FileTime::FileTime() + : m_fileTime(0) +{ + static CompileTimeAssert<sizeof(FileTime::InternalType) == sizeof(FILETIME)> internal_type_has_wrong_size; + Q_UNUSED(internal_type_has_wrong_size); +} + +bool FileTime::operator < (const FileTime &rhs) const +{ + const FILETIME *const t1 = reinterpret_cast<const FILETIME *>(&m_fileTime); + const FILETIME *const t2 = reinterpret_cast<const FILETIME *>(&rhs.m_fileTime); + return CompareFileTime(t1, t2) < 0; +} + +void FileTime::clear() +{ + m_fileTime = 0; +} + +bool FileTime::isValid() const +{ + return m_fileTime != 0; +} + +FileTime FileTime::currentTime() +{ + FileTime result; + SYSTEMTIME st; + GetSystemTime(&st); + FILETIME *const ft = reinterpret_cast<FILETIME *>(&result.m_fileTime); + SystemTimeToFileTime(&st, ft); + return result; +} + +QString FileTime::toString() const +{ + const FILETIME *const ft = reinterpret_cast<const FILETIME *>(&m_fileTime); + SYSTEMTIME stUTC, stLocal; + FileTimeToSystemTime(ft, &stUTC); + SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal); +#ifdef Q_CC_MSVC + WCHAR szString[512]; + HRESULT hr = StringCchPrintf(szString, sizeof(szString) / sizeof(WCHAR), + L"%02d.%02d.%d %02d:%02d:%02d:%02d", + stLocal.wDay, stLocal.wMonth, stLocal.wYear, + stLocal.wHour, stLocal.wMinute, stLocal.wSecond, + stLocal.wMilliseconds); + return SUCCEEDED(hr) ? QString::fromWCharArray(szString) : QString(); +#else // Q_CC_MSVC + const QString result = QString("%1.%2.%3 %4:%5:%6") + .arg(stLocal.wDay, 2, 10, QLatin1Char('0')).arg(stLocal.wMonth, 2, 10, QLatin1Char('0')).arg(stLocal.wYear) + .arg(stLocal.wHour, 2, 10, QLatin1Char('0')).arg(stLocal.wMinute, 2, 10, QLatin1Char('0')).arg(stLocal.wSecond, 2, 10, QLatin1Char('0')); + return result; +#endif // Q_CC_MSVC +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/hostosinfo.h b/src/lib/corelib/tools/hostosinfo.h new file mode 100644 index 000000000..0bf05cbfd --- /dev/null +++ b/src/lib/corelib/tools/hostosinfo.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** 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_HOSTOSINFO_H +#define QBS_HOSTOSINFO_H + +#include "qbs_export.h" + +#include <QtGlobal> +#include <QMap> +#include <QString> +#include <QStringList> + +#if defined(Q_OS_WIN) +#define QTC_HOST_EXE_SUFFIX ".exe" +#define QTC_HOST_DYNAMICLIB_PREFIX "" +#define QTC_HOST_DYNAMICLIB_SUFFIX ".dll" +#define QTC_HOST_OBJECT_SUFFIX ".obj" +#elif defined(Q_OS_DARWIN) +#define QTC_HOST_EXE_SUFFIX "" +#define QTC_HOST_DYNAMICLIB_PREFIX "lib" +#define QTC_HOST_DYNAMICLIB_SUFFIX ".dylib" +#define QTC_HOST_OBJECT_SUFFIX ".o" +#else +#define QTC_HOST_EXE_SUFFIX "" +#define QTC_HOST_DYNAMICLIB_PREFIX "lib" +#define QTC_HOST_DYNAMICLIB_SUFFIX ".so" +#define QTC_HOST_OBJECT_SUFFIX ".o" +#endif // Q_OS_WIN + +namespace qbs { +namespace Internal { + +class QBS_EXPORT HostOsInfo // Exported for use by command-line tools. +{ +public: + // Add more as needed. + enum HostOs { HostOsWindows, HostOsLinux, HostOsOsx, HostOsOtherUnix, HostOsOther }; + + static inline HostOs hostOs(); + + static bool isWindowsHost() { return hostOs() == HostOsWindows; } + static bool isLinuxHost() { return hostOs() == HostOsLinux; } + static bool isOsxHost() { return hostOs() == HostOsOsx; } + static inline bool isAnyUnixHost(); + static inline QString canonicalArchitecture(const QString &architecture); + static inline QString defaultEndianness(const QString &architecture); + + static QString appendExecutableSuffix(const QString &executable) + { + QString finalName = executable; + if (isWindowsHost()) + finalName += QLatin1String(QTC_HOST_EXE_SUFFIX); + return finalName; + } + + static QString dynamicLibraryName(const QString &libraryBaseName) + { + return QLatin1String(QTC_HOST_DYNAMICLIB_PREFIX) + libraryBaseName + + QLatin1String(QTC_HOST_DYNAMICLIB_SUFFIX); + } + + static QString objectName(const QString &baseName) + { + return baseName + QLatin1String(QTC_HOST_OBJECT_SUFFIX); + } + + static Qt::CaseSensitivity fileNameCaseSensitivity() + { + return isWindowsHost() ? Qt::CaseInsensitive: Qt::CaseSensitive; + } + + static QChar pathListSeparator() + { + return isWindowsHost() ? QLatin1Char(';') : QLatin1Char(':'); + } + + static Qt::KeyboardModifier controlModifier() + { + return isOsxHost() ? Qt::MetaModifier : Qt::ControlModifier; + } +}; + +HostOsInfo::HostOs HostOsInfo::hostOs() +{ +#if defined(Q_OS_WIN) + return HostOsWindows; +#elif defined(Q_OS_LINUX) + return HostOsLinux; +#elif defined(Q_OS_DARWIN) + return HostOsOsx; +#elif defined(Q_OS_UNIX) + return HostOsOtherUnix; +#else + return HostOsOther; +#endif +} + +bool HostOsInfo::isAnyUnixHost() +{ +#ifdef Q_OS_UNIX + return true; +#else + return false; +#endif +} + +QString HostOsInfo::canonicalArchitecture(const QString &architecture) +{ + QMap<QString, QStringList> archMap; + archMap.insert(QLatin1String("x86"), QStringList() + << QLatin1String("i386") + << QLatin1String("i486") + << QLatin1String("i586") + << QLatin1String("i686") + << QLatin1String("ia32") + << QLatin1String("ia-32") + << QLatin1String("x86_32") + << QLatin1String("x86-32") + << QLatin1String("intel32")); + + archMap.insert(QLatin1String("x86_64"), QStringList() + << QLatin1String("x86-64") + << QLatin1String("x64") + << QLatin1String("amd64") + << QLatin1String("ia32e") + << QLatin1String("em64t") + << QLatin1String("intel64")); + + archMap.insert(QLatin1String("ia64"), QStringList() + << QLatin1String("ia-64") + << QLatin1String("itanium")); + + QMapIterator<QString, QStringList> i(archMap); + while (i.hasNext()) { + i.next(); + if (i.value().contains(architecture.toLower())) + return i.key(); + } + + return architecture; +} + +QString HostOsInfo::defaultEndianness(const QString &architecture) +{ + const QString canonicalArch = canonicalArchitecture(architecture); + + QStringList little = QStringList() + << QLatin1String("x86") + << QLatin1String("x86_64") + << QLatin1String("arm") + << QLatin1String("arm64"); + + if (little.contains(canonicalArch)) + return QLatin1String("little"); + + QStringList big = QStringList() + << QLatin1String("ppc") + << QLatin1String("ppc64"); + + if (big.contains(canonicalArch)) + return QLatin1String("big"); + + return QString(); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_HOSTOSINFO_H diff --git a/src/lib/corelib/tools/id.cpp b/src/lib/corelib/tools/id.cpp new file mode 100644 index 000000000..a9dc07cbc --- /dev/null +++ b/src/lib/corelib/tools/id.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** 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 "id.h" +#include "qbsassert.h" + +#include <QByteArray> +#include <QHash> +#include <QVector> + +namespace qbs { +namespace Internal { + +/*! + \class qbs::Internal::Id + + \brief The class Id encapsulates an identifier that is unique + within a specific running process, using the qbs library. + + \c{Id} is used as facility to identify objects of interest + in a more typesafe and faster manner than a plain \c QString or + \c QByteArray would provide. + + An id is internally represented as a 32 bit integer (its \c UID) + and associated with a plain 7-bit-clean ASCII name used + for display and persistency. + + This class is copied from Qt Creator. +*/ + +class StringHolder +{ +public: + StringHolder() + : n(0), str(0) + {} + + StringHolder(const char *s, int length) + : n(length), str(s) + { + if (!n) + length = n = qstrlen(s); + h = 0; + while (length--) { + h = (h << 4) + *s++; + h ^= (h & 0xf0000000) >> 23; + h &= 0x0fffffff; + } + } + int n; + const char *str; + uint h; +}; + +static bool operator==(const StringHolder &sh1, const StringHolder &sh2) +{ + // sh.n is unlikely to discriminate better than the hash. + return sh1.h == sh2.h && sh1.str && sh2.str && strcmp(sh1.str, sh2.str) == 0; +} + + +static uint qHash(const StringHolder &sh) +{ + return sh.h; +} + +struct IdCache : public QHash<StringHolder, int> +{ +#ifndef QBS_ALLOW_STATIC_LEAKS + ~IdCache() + { + for (IdCache::iterator it = begin(); it != end(); ++it) + delete[](const_cast<char *>(it.key().str)); + } +#endif +}; + + +static int firstUnusedId = Id::IdsPerPlugin * Id::ReservedPlugins; + +static QHash<int, StringHolder> stringFromId; +static IdCache idFromString; + +static int theId(const char *str, int n = 0) +{ + QBS_ASSERT(str && *str, return 0); + StringHolder sh(str, n); + int res = idFromString.value(sh, 0); + if (res == 0) { + res = firstUnusedId++; + sh.str = qstrdup(sh.str); + idFromString[sh] = res; + stringFromId[res] = sh; + } + return res; +} + +static int theId(const QByteArray &ba) +{ + return theId(ba.constData(), ba.size()); +} + +/*! + \fn qbs::Internal::Id(int uid) + + \brief Constructs an id given a UID. + + The UID is an integer value that is unique within the running + process. + + It is the callers responsibility to ensure the uniqueness of + the passed integer. The recommended approach is to use + \c{registerId()} with an value taken from the plugin's + private range. + + \sa registerId() + +*/ + +/*! + Constructs an id given its associated name. The internal + representation will be unspecified, but consistent within a + process. + +*/ +Id::Id(const char *name) + : m_id(theId(name, 0)) +{} + +/*! + \overload + +*/ +Id::Id(const QByteArray &name) + : m_id(theId(name)) +{} + +///*! +// \overload +// \deprecated +//*/ +//Id::Id(const QString &name) +// : m_id(theId(name.toUtf8())) +//{} + +/*! + Returns an internal representation of the id. +*/ + +QByteArray Id::name() const +{ + return stringFromId.value(m_id).str; +} + +/*! + Returns a string representation of the id suitable + for UI display. + + This should not be used to create a persistent version + of the Id, use \c{toSetting()} instead. + + \sa fromString(), toSetting() +*/ + +QString Id::toString() const +{ + return QString::fromUtf8(stringFromId.value(m_id).str); +} + +/*! + Creates an id from a string representation. + + This should not be used to handle a persistent version + of the Id, use \c{fromSetting()} instead. + + \deprecated + + \sa toString(), fromSetting() +*/ + +Id Id::fromString(const QString &name) +{ + return Id(theId(name.toUtf8())); +} + +/*! + Creates an id from a string representation. + + This should not be used to handle a persistent version + of the Id, use \c{fromSetting()} instead. + + \deprecated + + \sa toString(), fromSetting() +*/ + +Id Id::fromName(const QByteArray &name) +{ + return Id(theId(name)); +} + +/*! + Returns a persistent value representing the id which is + suitable to be stored in QSettings. + + \sa fromSetting() +*/ + +QVariant Id::toSetting() const +{ + return QVariant(QString::fromUtf8(stringFromId.value(m_id).str)); +} + +/*! + Reconstructs an id from a persistent value. + + \sa toSetting() +*/ + +Id Id::fromSetting(const QVariant &variant) +{ + const QByteArray ba = variant.toString().toUtf8(); + if (ba.isEmpty()) + return Id(); + return Id(theId(ba)); +} + +/*! + Constructs a derived id. + + This can be used to construct groups of ids logically + belonging together. The associated internal name + will be generated by appending \c{suffix}. +*/ + +Id Id::withSuffix(int suffix) const +{ + const QByteArray ba = name() + QByteArray::number(suffix); + return Id(ba.constData()); +} + +/*! + \overload +*/ + +Id Id::withSuffix(const char *suffix) const +{ + const QByteArray ba = name() + suffix; + return Id(ba.constData()); +} + +/*! + Constructs a derived id. + + This can be used to construct groups of ids logically + belonging together. The associated internal name + will be generated by prepending \c{prefix}. +*/ + +Id Id::withPrefix(const char *prefix) const +{ + const QByteArray ba = prefix + name(); + return Id(ba.constData()); +} + + +/*! + Associates a id with its uid and its string + representation. + + The uid should be taken from the plugin's private range. + + \sa fromSetting() +*/ + +void Id::registerId(int uid, const char *name) +{ + StringHolder sh(name, 0); + idFromString[sh] = uid; + stringFromId[uid] = sh; +} + +bool Id::operator==(const char *name) const +{ + const char *string = stringFromId.value(m_id).str; + if (string && name) + return strcmp(string, name) == 0; + else + return false; +} + +bool Id::alphabeticallyBefore(Id other) const +{ + return toString().compare(other.toString(), Qt::CaseInsensitive) < 0; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/id.h b/src/lib/corelib/tools/id.h new file mode 100644 index 000000000..097a2d89b --- /dev/null +++ b/src/lib/corelib/tools/id.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_TOOLS_ID_H +#define QBS_TOOLS_ID_H + +#include <QMetaType> +#include <QString> +#include <QVariant> + +namespace qbs { +namespace Internal { + +class Id +{ +public: + enum { IdsPerPlugin = 10000, ReservedPlugins = 1000 }; + + Id() : m_id(0) {} + Id(int uid) : m_id(uid) {} + Id(const char *name); +// explicit Id(const QString &name); + explicit Id(const QByteArray &name); + + Id withSuffix(int suffix) const; + Id withSuffix(const char *name) const; + Id withPrefix(const char *name) const; + + QByteArray name() const; + QString toString() const; // Avoid. + QVariant toSetting() const; // Good to use. + bool isValid() const { return m_id; } + bool operator==(Id id) const { return m_id == id.m_id; } + bool operator==(const char *name) const; + bool operator!=(Id id) const { return m_id != id.m_id; } + bool operator!=(const char *name) const { return !operator==(name); } + bool operator<(Id id) const { return m_id < id.m_id; } + bool operator>(Id id) const { return m_id > id.m_id; } + bool alphabeticallyBefore(Id other) const; + int uniqueIdentifier() const { return m_id; } + static Id fromUniqueIdentifier(int uid) { return Id(uid); } + static Id fromString(const QString &str); // FIXME: avoid. + static Id fromName(const QByteArray &ba); // FIXME: avoid. + static Id fromSetting(const QVariant &variant); // Good to use. + static void registerId(int uid, const char *name); + +private: + // Intentionally unimplemented + Id(const QLatin1String &); + int m_id; +}; + +inline uint qHash(const Id &id) { return id.uniqueIdentifier(); } + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::Id) +Q_DECLARE_METATYPE(QList<qbs::Internal::Id>) + +#endif // QBS_TOOLS_ID_H diff --git a/src/lib/corelib/tools/installoptions.cpp b/src/lib/corelib/tools/installoptions.cpp new file mode 100644 index 000000000..66e82fd23 --- /dev/null +++ b/src/lib/corelib/tools/installoptions.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** 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 "installoptions.h" + +#include <QDir> +#include <QSharedData> + +namespace qbs { +namespace Internal { + +class InstallOptionsPrivate : public QSharedData +{ +public: + InstallOptionsPrivate() + : useSysroot(false), removeExisting(false), dryRun(false), + keepGoing(false), logElapsedTime(false) + {} + + QString installRoot; + bool useSysroot; + bool removeExisting; + bool dryRun; + bool keepGoing; + bool logElapsedTime; +}; + +} // namespace Internal + +/*! + * \class InstallOptions + * \brief The \c InstallOptions class comprises parameters that influence the behavior of + * install operations. + */ + +InstallOptions::InstallOptions() : d(new Internal::InstallOptionsPrivate) +{ +} + +InstallOptions::InstallOptions(const InstallOptions &other) : d(other.d) +{ +} + +InstallOptions &InstallOptions::operator=(const InstallOptions &other) +{ + d = other.d; + return *this; +} + +InstallOptions::~InstallOptions() +{ +} + +/*! + * \brief The default install root, relative to the build directory. + */ +QString InstallOptions::defaultInstallRoot() +{ + return QLatin1String("install-root"); +} + +/*! + * Returns the base directory for the installation. + * The \c qbs.installPrefix path is relative to this root. If the string is empty, either the value of + * qbs.sysroot or "<build dir>/install-root" will be used, depending on what \c installIntoSysroot() + * returns. + * The default is empty. + */ +QString InstallOptions::installRoot() const +{ + return d->installRoot; +} + +/*! + * \brief Sets the base directory for the installation. + * \note The argument must either be an empty string or an absolute path to a directory + * (which might not yet exist, in which case it will be created). + */ +void InstallOptions::setInstallRoot(const QString &installRoot) +{ + d->installRoot = installRoot; + if (!QDir(installRoot).isRoot()) { + while (d->installRoot.endsWith(QLatin1Char('/'))) + d->installRoot.chop(1); + } +} + +/*! + * Returns whether to use the sysroot as the default install root. + * The default is false. + */ +bool InstallOptions::installIntoSysroot() const +{ + return d->useSysroot; +} + +void InstallOptions::setInstallIntoSysroot(bool useSysroot) +{ + d->useSysroot = useSysroot; +} + +/*! + * \brief Returns true iff an existing installation will be removed prior to installing. + * The default is false. + */ +bool InstallOptions::removeExistingInstallation() const +{ + return d->removeExisting; +} + +/*! + * Controls whether to remove an existing installation before installing. + * \note qbs may do some safety checks and refuse to remove certain directories such as + * a user's home directory. You should still be careful with this option, since it + * deletes recursively. + */ +void InstallOptions::setRemoveExistingInstallation(bool removeExisting) +{ + d->removeExisting = removeExisting; +} + +/*! + * \brief Returns true iff qbs will not actually copy any files, but just show what would happen. + * The default is false. + */ +bool InstallOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether installation will actually take place. + * If the argument is true, then qbs will emit information about which files would be copied + * instead of actually doing it. + */ +void InstallOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * Returns true iff installation will continue if an error occurs. + * The default is false. + */ +bool InstallOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether to abort on errors. + * If the argument is true, then if a file cannot be copied e.g. due to a permission problem, + * a warning will be printed and the installation will continue. If the argument is false, + * then the installation will abort immediately in case of an error. + */ +void InstallOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is false. + */ +bool InstallOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the installation time will be measured and logged. + */ +void InstallOptions::setLogElapsedTime(bool logElapsedTime) +{ + d->logElapsedTime = logElapsedTime; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/installoptions.h b/src/lib/corelib/tools/installoptions.h new file mode 100644 index 000000000..2b6194fb0 --- /dev/null +++ b/src/lib/corelib/tools/installoptions.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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_INSTALLOPTIONS_H +#define QBS_INSTALLOPTIONS_H + +#include "qbs_export.h" + +#include <QSharedDataPointer> +#include <QString> + +namespace qbs { +namespace Internal { class InstallOptionsPrivate; } + +class QBS_EXPORT InstallOptions +{ +public: + InstallOptions(); + InstallOptions(const InstallOptions &other); + InstallOptions &operator=(const InstallOptions &other); + ~InstallOptions(); + + static QString defaultInstallRoot(); + QString installRoot() const; + void setInstallRoot(const QString &installRoot); + + bool installIntoSysroot() const; + void setInstallIntoSysroot(bool useSysroot); + + bool removeExistingInstallation() const; + void setRemoveExistingInstallation(bool removeExisting); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool logElapsedTime() const; + void setLogElapsedTime(bool logElapsedTime); + +private: + QSharedDataPointer<Internal::InstallOptionsPrivate> d; +}; + +} // namespace qbs + +#endif // QBS_INSTALLOPTIONS_H diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp new file mode 100644 index 000000000..aa519cab2 --- /dev/null +++ b/src/lib/corelib/tools/persistence.cpp @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** 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 "persistence.h" + +#include "fileinfo.h" +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QScopedPointer> + +namespace qbs { +namespace Internal { + +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-60"; + +PersistentPool::PersistentPool(const Logger &logger) : m_logger(logger) +{ + m_stream.setVersion(QDataStream::Qt_4_8); +} + +PersistentPool::~PersistentPool() +{ + closeStream(); +} + +void PersistentPool::load(const QString &filePath) +{ + QScopedPointer<QFile> file(new QFile(filePath)); + if (!file->exists()) + throw ErrorInfo(Tr::tr("No build graph exists yet for this configuration.")); + if (!file->open(QFile::ReadOnly)) { + throw ErrorInfo(Tr::tr("Could not open open build graph file '%1': %2") + .arg(filePath, file->errorString())); + } + + m_stream.setDevice(file.data()); + QByteArray magic; + m_stream >> magic; + if (magic != QBS_PERSISTENCE_MAGIC) { + file->close(); + file->remove(); + m_stream.setDevice(0); + throw ErrorInfo(Tr::tr("Cannot use stored build graph at '%1': Incompatible file format. " + "Expected magic token '%2', got '%3'.") + .arg(filePath, QString::fromLatin1(QBS_PERSISTENCE_MAGIC), + QString::fromLatin1(magic))); + } + + m_stream >> m_headData.projectConfig; + file.take(); + m_loadedRaw.clear(); + m_loaded.clear(); + m_storageIndices.clear(); + m_stringStorage.clear(); + m_inverseStringStorage.clear(); +} + +void PersistentPool::setupWriteStream(const QString &filePath) +{ + QString dirPath = FileInfo::path(filePath); + if (!FileInfo::exists(dirPath) && !QDir().mkpath(dirPath)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: Cannot create directory '%1'.") + .arg(dirPath)); + } + + if (QFile::exists(filePath) && !QFile::remove(filePath)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: Cannot remove old file '%1'") + .arg(filePath)); + } + QBS_CHECK(!QFile::exists(filePath)); + QScopedPointer<QFile> file(new QFile(filePath)); + if (!file->open(QFile::WriteOnly)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: " + "Cannot open file '%1' for writing: %2").arg(filePath, file->errorString())); + } + + m_stream.setDevice(file.take()); + m_stream << QByteArray(QBS_PERSISTENCE_MAGIC) << m_headData.projectConfig; + m_lastStoredObjectId = 0; + m_lastStoredStringId = 0; +} + +void PersistentPool::closeStream() +{ + delete m_stream.device(); + m_stream.setDevice(0); +} + +void PersistentPool::store(const PersistentObject *object) +{ + if (!object) { + m_stream << -1; + return; + } + PersistentObjectId id = m_storageIndices.value(object, -1); + if (id < 0) { + id = m_lastStoredObjectId++; + m_storageIndices.insert(object, id); + m_stream << id; + object->store(*this); + } else { + m_stream << id; + } +} + +void PersistentPool::clear() +{ + m_loaded.clear(); + m_storageIndices.clear(); + m_stringStorage.clear(); + m_inverseStringStorage.clear(); +} + +QDataStream &PersistentPool::stream() +{ + return m_stream; +} + +void PersistentPool::storeString(const QString &t) +{ + int id = m_inverseStringStorage.value(t, -1); + if (id < 0) { + id = m_lastStoredStringId++; + m_inverseStringStorage.insert(t, id); + m_stream << id << t; + } else { + m_stream << id; + } +} + +QString PersistentPool::loadString(int id) +{ + QBS_CHECK(id >= 0); + + if (id >= m_stringStorage.count()) { + QString s; + m_stream >> s; + m_stringStorage.resize(id + 1); + m_stringStorage[id] = s; + return s; + } + + return m_stringStorage.at(id); +} + +QString PersistentPool::idLoadString() +{ + int id; + m_stream >> id; + return loadString(id); +} + +void PersistentPool::storeStringSet(const QSet<QString> &t) +{ + m_stream << t.count(); + foreach (const QString &s, t) + storeString(s); +} + +QSet<QString> PersistentPool::idLoadStringSet() +{ + int i; + m_stream >> i; + QSet<QString> result; + for (; --i >= 0;) + result += idLoadString(); + return result; +} + +void PersistentPool::storeStringList(const QStringList &t) +{ + m_stream << t.count(); + foreach (const QString &s, t) + storeString(s); +} + +QStringList PersistentPool::idLoadStringList() +{ + int i; + m_stream >> i; + QStringList result; + for (; --i >= 0;) + result += idLoadString(); + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/persistence.h b/src/lib/corelib/tools/persistence.h new file mode 100644 index 000000000..1afea6d80 --- /dev/null +++ b/src/lib/corelib/tools/persistence.h @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** 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_PERSISTENCE +#define QBS_PERSISTENCE + +#include "persistentobject.h" +#include <logging/logger.h> + +#include <QDataStream> +#include <QSharedPointer> +#include <QString> +#include <QVariantMap> +#include <QVector> + +namespace qbs { +namespace Internal { + +class PersistentPool +{ +public: + PersistentPool(const Logger &logger); + ~PersistentPool(); + + class HeadData + { + public: + QVariantMap projectConfig; + }; + + void load(const QString &filePath); + void setupWriteStream(const QString &filePath); + void closeStream(); + void clear(); + QDataStream &stream(); + + template <typename T> T *idLoad(); + template <typename T> void loadContainer(T &container); + template <class T> QSharedPointer<T> idLoadS(); + template <typename T> void loadContainerS(T &container); + + void store(const QSharedPointer<const PersistentObject> &ptr) { store(ptr.data()); } + void store(const PersistentObject *object); + template <typename T> void storeContainer(T &container); + + void storeString(const QString &t); + QString loadString(int id); + QString idLoadString(); + + void storeStringSet(const QSet<QString> &t); + QSet<QString> loadStringSet(const QList<int> &id); + QSet<QString> idLoadStringSet(); + + void storeStringList(const QStringList &t); + QStringList loadStringList(const QList<int> &ids); + QStringList idLoadStringList(); + + const HeadData &headData() const { return m_headData; } + void setHeadData(const HeadData &hd) { m_headData = hd; } + +private: + typedef int PersistentObjectId; + + template<typename T> struct RemovePointer { typedef T Type; }; + template<typename T> struct RemovePointer<T*> { typedef T Type; }; + template <class T> struct RemoveConst { typedef T Type; }; + template <class T> struct RemoveConst<const T> { typedef T Type; }; + + template <class T> T *loadRaw(PersistentObjectId id); + template <class T> QSharedPointer<T> load(PersistentObjectId id); + + QDataStream m_stream; + HeadData m_headData; + QVector<PersistentObject *> m_loadedRaw; + QVector<QSharedPointer<PersistentObject> > m_loaded; + QHash<const PersistentObject *, int> m_storageIndices; + PersistentObjectId m_lastStoredObjectId; + + QVector<QString> m_stringStorage; + QHash<QString, int> m_inverseStringStorage; + PersistentObjectId m_lastStoredStringId; + Logger m_logger; +}; + +template <typename T> inline T *PersistentPool::idLoad() +{ + PersistentObjectId id; + stream() >> id; + return loadRaw<T>(id); +} + +template <typename T> inline void PersistentPool::loadContainer(T &container) +{ + int count; + stream() >> count; + container.clear(); + container.reserve(count); + for (int i = count; --i >= 0;) + container += idLoad<typename RemovePointer<typename T::value_type>::Type>(); +} + +template <class T> inline QSharedPointer<T> PersistentPool::idLoadS() +{ + PersistentObjectId id; + m_stream >> id; + return load<T>(id); +} + +template <typename T> inline void PersistentPool::loadContainerS(T &container) +{ + int count; + stream() >> count; + container.clear(); + container.reserve(count); + for (int i = count; --i >= 0;) + container += idLoadS<typename RemoveConst<typename T::value_type::value_type>::Type>(); +} + +template <typename T> inline void PersistentPool::storeContainer(T &container) +{ + stream() << container.count(); + typename T::const_iterator it = container.constBegin(); + const typename T::const_iterator itEnd = container.constEnd(); + for (; it != itEnd; ++it) + store(*it); +} + +template <class T> inline T *PersistentPool::loadRaw(PersistentObjectId id) +{ + if (id < 0) + return 0; + + if (id < m_loadedRaw.count()) { + PersistentObject *obj = m_loadedRaw.value(id); + return dynamic_cast<T*>(obj); + } + + int i = m_loadedRaw.count(); + m_loadedRaw.resize(id + 1); + for (; i < m_loadedRaw.count(); ++i) + m_loadedRaw[i] = 0; + + T * const t = new T; + PersistentObject * const po = t; + m_loadedRaw[id] = po; + po->load(*this); + return t; +} + +template <class T> inline QSharedPointer<T> PersistentPool::load(PersistentObjectId id) +{ + if (id < 0) + return QSharedPointer<T>(); + + if (id < m_loaded.count()) { + QSharedPointer<PersistentObject> obj = m_loaded.value(id); + return obj.dynamicCast<T>(); + } + + m_loaded.resize(id + 1); + const QSharedPointer<T> t = T::create(); + m_loaded[id] = t; + PersistentObject * const po = t.data(); + po->load(*this); + return t; +} + +} // namespace Internal +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/persistentobject.h b/src/lib/corelib/tools/persistentobject.h new file mode 100644 index 000000000..0d9b588c0 --- /dev/null +++ b/src/lib/corelib/tools/persistentobject.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_PERSISTENTOBJECT_H +#define QBS_PERSISTENTOBJECT_H + +namespace qbs { +namespace Internal { + +class PersistentPool; + +class PersistentObject +{ +public: + virtual ~PersistentObject() {} + virtual void load(PersistentPool &) = 0; + virtual void store(PersistentPool &) const = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PERSISTENTOBJECT_H diff --git a/src/lib/corelib/tools/preferences.cpp b/src/lib/corelib/tools/preferences.cpp new file mode 100644 index 000000000..bdff7c67b --- /dev/null +++ b/src/lib/corelib/tools/preferences.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 "preferences.h" + +#include "buildoptions.h" +#include "hostosinfo.h" +#include "profile.h" +#include "settings.h" + +namespace qbs { + +/*! + * \class Preferences + * \brief The \c Preferences class gives access to all general qbs preferences. + * If a non-empty \c profileName is given, the profile's preferences take precedence over global + * ones. Otherwise, the global preferences are used. + */ +Preferences::Preferences(Settings *settings, const QString &profileName) + : m_settings(settings), m_profile(profileName) +{ +} + + +/*! + * \brief Returns true <=> colored output should be used for printing messages. + * This is only relevant for command-line frontends. + */ +bool Preferences::useColoredOutput() const +{ + return getPreference(QLatin1String("useColoredOutput"), true).toBool(); +} + +/*! + * \brief Returns the number of parallel jobs to use for building. + * Uses a sensible default value if there is no such setting. + */ +int Preferences::jobs() const +{ + return getPreference(QLatin1String("jobs"), BuildOptions::defaultMaxJobCount()).toInt(); +} + +/*! + * \brief Returns the shell to use for the "qbs shell" command. + * This is only relevant for command-line frontends. + */ +QString Preferences::shell() const +{ + return getPreference(QLatin1String("shell")).toString(); +} + +/*! + * \brief Returns the default build directory used by Qbs if none is specified. + */ +QString Preferences::defaultBuildDirectory() const +{ + return getPreference(QLatin1String("defaultBuildDirectory")).toString(); +} + +/*! + * \brief Returns the list of paths where qbs looks for module definitions and such. + * If there is no such setting, \c qbsRootPath will be used to look up a fallback location. + */ +QStringList Preferences::searchPaths(const QString &qbsRootPath) const +{ + const QStringList searchPaths = pathList(QLatin1String("qbsSearchPaths"), + qbsRootPath + QLatin1String("/share/qbs")); + + // TODO: Remove in 1.2. + const QStringList deprecatedSearchPaths = getPreference(QLatin1String("qbsPath")).toString() + .split(Internal::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + if (!deprecatedSearchPaths.isEmpty()) { + qDebug("Warning: preferences.qbsPath is deprecated, " + "use preferences.qbsSearchPaths instead."); + } + return deprecatedSearchPaths + searchPaths; +} + +/*! + * \brief Returns the list of paths where qbs looks for plugins. + * If there is no such setting, \c qbsRootPath will be used to look up a fallback location. + */ +QStringList Preferences::pluginPaths(const QString &qbsRootPath) const +{ + return pathList(QLatin1String("pluginsPath"), qbsRootPath + QLatin1String("/lib/qbs/plugins")); +} + +QVariant Preferences::getPreference(const QString &key, const QVariant &defaultValue) const +{ + const QString fullKey = QLatin1String("preferences.") + key; + if (!m_profile.isEmpty()) { + const QVariant value = Profile(m_profile, m_settings).value(fullKey); + if (value.isValid()) + return value; + } + + return m_settings->value(fullKey, defaultValue); +} + +QStringList Preferences::pathList(const QString &key, const QString &defaultValue) const +{ + QStringList paths = getPreference(key).toString().split( + Internal::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + paths << defaultValue; + return paths; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/preferences.h b/src/lib/corelib/tools/preferences.h new file mode 100644 index 000000000..e5e6c9bb6 --- /dev/null +++ b/src/lib/corelib/tools/preferences.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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_PREFERENCES_H +#define QBS_PREFERENCES_H + +#include "qbs_export.h" + +#include <QStringList> +#include <QVariant> + +namespace qbs { +class Settings; + +class QBS_EXPORT Preferences +{ +public: + explicit Preferences(Settings *settings, const QString &profileName = QString()); + + bool useColoredOutput() const; + int jobs() const; + QString shell() const; + QString defaultBuildDirectory() const; + QStringList searchPaths(const QString &qbsRootPath = QString()) const; + QStringList pluginPaths(const QString &qbsRootPath = QString()) const; + +private: + QVariant getPreference(const QString &key, const QVariant &defaultValue = QVariant()) const; + QStringList pathList(const QString &key, const QString &defaultValue) const; + + Settings *m_settings; + QString m_profile; +}; + +} // namespace qbs + + +#endif // Header guard diff --git a/src/lib/corelib/tools/processresult.cpp b/src/lib/corelib/tools/processresult.cpp new file mode 100644 index 000000000..885d7ffee --- /dev/null +++ b/src/lib/corelib/tools/processresult.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 "processresult.h" +#include "processresult_p.h" + +/*! + * \class SetupProjectParameters + * \brief The \c ProcessResult class describes a finished qbs process command. + */ + +namespace qbs { + +ProcessResult::ProcessResult() : d(new Internal::ProcessResultPrivate) +{ +} + +ProcessResult::ProcessResult(const ProcessResult &other) : d(other.d) +{ +} + +ProcessResult &ProcessResult::operator=(const ProcessResult &other) +{ + d = other.d; + return *this; +} + +ProcessResult::~ProcessResult() +{ +} + +/*! + * \brief Returns true iff the command finished successfully. + */ +bool ProcessResult::success() const +{ + return d->success; +} + +/*! + * \brief Returns the file path of the executable that was run. + */ +QString ProcessResult::executableFilePath() const +{ + return d->executableFilePath; +} + +/*! + * \brief Returns the command-line arguments with which the command was invoked. + */ +QStringList ProcessResult::arguments() const +{ + return d->arguments; +} + +/*! + * \brief Returns the working directory of the invoked command. + */ +QString ProcessResult::workingDirectory() const +{ + return d->workingDirectory; +} + +/*! + * \brief Returns the exit status of the command. + */ +QProcess::ExitStatus ProcessResult::exitStatus() const +{ + return d->exitStatus; +} + +/*! + * \brief Returns the exit code of the command. + */ +int ProcessResult::exitCode() const +{ + return d->exitCode; +} + +/*! + * \brief Returns the data the command wrote to the standard output channel. + */ +QStringList ProcessResult::stdOut() const +{ + return d->stdOut; +} + +/*! + * \brief Returns the data the command wrote to the standard error channel. + */ +QStringList ProcessResult::stdErr() const +{ + return d->stdErr; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/processresult.h b/src/lib/corelib/tools/processresult.h new file mode 100644 index 000000000..b8de9ddd2 --- /dev/null +++ b/src/lib/corelib/tools/processresult.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_PROCESSRESULT_H +#define QBS_PROCESSRESULT_H + +#include "qbs_export.h" + +#include <QExplicitlySharedDataPointer> +#include <QMetaType> +#include <QProcess> +#include <QString> +#include <QStringList> + +namespace qbs { +namespace Internal { +class ProcessCommandExecutor; +class ProcessResultPrivate; +} + +class QBS_EXPORT ProcessResult +{ + friend class qbs::Internal::ProcessCommandExecutor; +public: + ProcessResult(); + ProcessResult(const ProcessResult &other); + ProcessResult &operator=(const ProcessResult &other); + ~ProcessResult(); + + bool success() const; + QString executableFilePath() const; + QStringList arguments() const; + QString workingDirectory() const; + QProcess::ExitStatus exitStatus() const; + int exitCode() const; + QStringList stdOut() const; + QStringList stdErr() const; + +private: + QExplicitlySharedDataPointer<Internal::ProcessResultPrivate> d; +}; + +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::ProcessResult) + +#endif // QBS_PROCESSRESULT_H diff --git a/src/lib/corelib/tools/processresult_p.h b/src/lib/corelib/tools/processresult_p.h new file mode 100644 index 000000000..db2475b44 --- /dev/null +++ b/src/lib/corelib/tools/processresult_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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_PROCESSRESULT_P_H +#define QBS_PROCESSRESULT_P_H + +#include <QSharedData> +#include <QStringList> +#include <QProcess> + +namespace qbs { +namespace Internal { +class ProcessResultPrivate : public QSharedData +{ +public: + bool success; + + QString executableFilePath; + QStringList arguments; + QString workingDirectory; + + QProcess::ExitStatus exitStatus; + int exitCode; + QStringList stdOut; + QStringList stdErr; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/profile.cpp b/src/lib/corelib/tools/profile.cpp new file mode 100644 index 000000000..cb3186ae4 --- /dev/null +++ b/src/lib/corelib/tools/profile.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** 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 "profile.h" +#include "qbsassert.h" +#include "settings.h" + +#include <logging/translator.h> +#include <tools/error.h> + +namespace qbs { + +/*! + * \class Profile + * \brief The \c Profile class gives access to the settings of a given profile. + */ + + /*! + * \enum Profile::KeySelection + * This enum type specifies whether to enumerate keys recursively. + * \value KeySelectionRecursive Indicates that key enumeration should happen recursively, i.e. + * it should go up the base profile chain. + * \value KeySelectionNonRecursive Indicates that only keys directly attached to a profile + * should be listed. + */ + +/*! + * \brief Creates an object giving access to the settings for profile \c name. + */ +Profile::Profile(const QString &name, Settings *settings) : m_name(name), m_settings(settings) +{ + QBS_ASSERT(name == cleanName(name), return); +} + +bool Profile::exists() const +{ + return !m_settings->allKeysWithPrefix(profileKey()).isEmpty(); +} + +/*! + * \brief Returns the value for property \c key in this profile. + */ +QVariant Profile::value(const QString &key, const QVariant &defaultValue) const +{ + return possiblyInheritedValue(key, defaultValue, QStringList()); +} + +/*! + * \brief Gives value \c value to the property \c key in this profile. + */ +void Profile::setValue(const QString &key, const QVariant &value) +{ + m_settings->setValue(fullyQualifiedKey(key), value); + + if (key == baseProfileKey()) { + QBS_ASSERT(value.toString() == cleanName(value.toString()), return); + } +} + +/*! + * \brief Removes a key and the associated value from this profile. + */ +void Profile::remove(const QString &key) +{ + m_settings->remove(fullyQualifiedKey(key)); +} + +/*! + * \brief Returns the name of this profile. + */ +QString Profile::name() const +{ + return m_name; +} + +/*! + * \brief Returns all property keys in this profile. + * If and only if selection is Profile::KeySelectionRecursive, this will also list keys defined + * in base profiles. + */ +QStringList Profile::allKeys(KeySelection selection) const +{ + return allKeysInternal(selection, QStringList()); +} + +/*! + * \brief Returns the name of this profile's base profile. + * The returned value is empty if the profile does not have a base profile. + */ +QString Profile::baseProfile() const +{ + return localValue(baseProfileKey()).toString(); +} + +/*! + * \brief Sets a new base profile for this profile. + */ +void Profile::setBaseProfile(const QString &baseProfile) +{ + setValue(baseProfileKey(), baseProfile); +} + +/*! + * \brief Removes this profile's base profile setting. + */ +void Profile::removeBaseProfile() +{ + remove(baseProfileKey()); +} + +/*! + * \brief Removes this profile from the settings. + */ +void Profile::removeProfile() +{ + m_settings->remove(profileKey()); +} + +/*! + * \brief Returns a string suitiable as a profile name. + * Removes all dots and replaces them with hyphens. + */ +QString Profile::cleanName(const QString &name) +{ + QString newName = name; + return newName.replace(QLatin1Char('.'), QLatin1Char('-')); +} + +QString Profile::profileKey() const +{ + return QLatin1String("profiles.") + m_name; +} + +QString Profile::baseProfileKey() +{ + return QLatin1String("baseProfile"); +} + +void Profile::checkBaseProfileExistence(const Profile &baseProfile) const +{ + if (!baseProfile.exists()) + throw ErrorInfo(Internal::Tr::tr("Profile \"%1\" has a non-existent base profile \"%2\".").arg( + name(), baseProfile.name())); +} + +QVariant Profile::localValue(const QString &key) const +{ + return m_settings->value(fullyQualifiedKey(key)); +} + +QString Profile::fullyQualifiedKey(const QString &key) const +{ + return profileKey() + QLatin1Char('.') + key; +} + +QVariant Profile::possiblyInheritedValue(const QString &key, const QVariant &defaultValue, + QStringList profileChain) const +{ + extendAndCheckProfileChain(profileChain); + const QVariant v = localValue(key); + if (v.isValid()) + return v; + const QString baseProfileName = baseProfile(); + if (baseProfileName.isEmpty()) + return defaultValue; + Profile parentProfile(baseProfileName, m_settings); + checkBaseProfileExistence(parentProfile); + return parentProfile.possiblyInheritedValue(key, defaultValue, profileChain); +} + +QStringList Profile::allKeysInternal(Profile::KeySelection selection, + QStringList profileChain) const +{ + extendAndCheckProfileChain(profileChain); + QStringList keys = m_settings->allKeysWithPrefix(profileKey()); + if (selection == KeySelectionNonRecursive) + return keys; + const QString baseProfileName = baseProfile(); + if (baseProfileName.isEmpty()) + return keys; + Profile parentProfile(baseProfileName, m_settings); + checkBaseProfileExistence(parentProfile); + keys += parentProfile.allKeysInternal(KeySelectionRecursive, profileChain); + keys.removeDuplicates(); + keys.removeOne(baseProfileKey()); + keys.sort(); + return keys; +} + +void Profile::extendAndCheckProfileChain(QStringList &chain) const +{ + chain << m_name; + if (Q_UNLIKELY(chain.count(m_name) > 1)) { + throw ErrorInfo(Internal::Tr::tr("Circular profile inheritance. Cycle is '%1'.") + .arg(chain.join(QLatin1String(" -> ")))); + } +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/profile.h b/src/lib/corelib/tools/profile.h new file mode 100644 index 000000000..c3b0bbfe8 --- /dev/null +++ b/src/lib/corelib/tools/profile.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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_PROFILE_H +#define QBS_PROFILE_H + +#include "qbs_export.h" + +#include <QString> +#include <QStringList> +#include <QVariant> + +namespace qbs { +class Settings; + +class QBS_EXPORT Profile +{ +public: + explicit Profile(const QString &name, Settings *settings); + + bool exists() const; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + void setValue(const QString &key, const QVariant &value); + void remove(const QString &key); + + QString name() const; + + QString baseProfile() const; + void setBaseProfile(const QString &baseProfile); + void removeBaseProfile(); + + void removeProfile(); + + enum KeySelection { KeySelectionRecursive, KeySelectionNonRecursive }; + QStringList allKeys(KeySelection selection) const; + + static QString cleanName(const QString &name); + +private: + static QString baseProfileKey(); + void checkBaseProfileExistence(const Profile &baseProfile) const; + QString profileKey() const; + QVariant localValue(const QString &key) const; + QString fullyQualifiedKey(const QString &key) const; + QVariant possiblyInheritedValue(const QString &key, const QVariant &defaultValue, + QStringList profileChain) const; + QStringList allKeysInternal(KeySelection selection, QStringList profileChain) const; + void extendAndCheckProfileChain(QStringList &chain) const; + + QString m_name; + Settings *m_settings; +}; + +} // namespace qbs + +#endif // Header guard diff --git a/src/lib/corelib/tools/progressobserver.cpp b/src/lib/corelib/tools/progressobserver.cpp new file mode 100644 index 000000000..f18aefcfb --- /dev/null +++ b/src/lib/corelib/tools/progressobserver.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "progressobserver.h" + +namespace qbs { +namespace Internal { + +/*! + * \class ProgressObserver + * The \c ProgressObserver class is used in long running qbs operations. It serves two purposes: + * Firstly, it allows operations to indicate progress to a client. Secondly, a client can + * signal to an operation that is should exit prematurely. + * Clients of the qbs library are supposed to subclass this class and implement the virtual + * functions in a way that lets users know about the current operation and its progress. + */ + +/*! + * \fn virtual void initialize(const QString &task, int maximum) = 0 + * \brief Indicates that a new operation is starting. + * Library code calls this function to indicate that it is starting a new task. + * The \a task parameter is a textual description of that task suitable for presentation to a user. + * The \a maximum parameter is an estimate of the maximum effort the operation is going to take. + * This is helpful if the client wants to set up some sort of progress bar showing the + * percentage of the work already done. + */ + +/*! + * \fn virtual void setProgressValue(int value) = 0 + * \brief Sets the new progress value. + * Library code calls this function to indicate that the current operation has progressed. + * It will try hard to ensure that \a value will not exceed \c maximum(). + * \sa ProgressObserver::maximum(). + */ + +/*! + * \fn virtual int progressValue() = 0 + * \brief The current progress value. + * Will typically reflect the \a value from the last call to \c setProgressValue() and should not + * exceed \c maximum(). + * \sa setProgressvalue() + * \sa maximum() + */ + +void ProgressObserver::incrementProgressValue(int increment) +{ + setProgressValue(progressValue() + increment); +} + +/*! + * \fn virtual bool canceled() const = 0 + * \brief Indicates whether the current operation should be canceled. + * Library code will periodically call this function and abort the current operation + * if it returns true. + */ + +/*! + * \fn virtual int maximum() const = 0 + * \brief The expected maximum progress value. + * This will typically be the value of \c maximum passed to \c initialize(). + * \sa ProgressObserver::initialize() + */ + +void ProgressObserver::setFinished() +{ + setProgressValue(maximum()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/progressobserver.h b/src/lib/corelib/tools/progressobserver.h new file mode 100644 index 000000000..66d552a02 --- /dev/null +++ b/src/lib/corelib/tools/progressobserver.h @@ -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. +** +****************************************************************************/ +#ifndef QBS_PROGRESSOBSERVER_H +#define QBS_PROGRESSOBSERVER_H + +#include <QtGlobal> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class ProgressObserver +{ +public: + virtual ~ProgressObserver() { } + + virtual void initialize(const QString &task, int maximum) = 0; + virtual void setProgressValue(int value) = 0; + virtual int progressValue() = 0; + virtual bool canceled() const = 0; + virtual void setMaximum(int maximum) = 0; + virtual int maximum() const = 0; + + void incrementProgressValue(int increment = 1); + + // Call this to ensure that the progress bar always goes to 100%. + void setFinished(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROGRESSOBSERVER_H diff --git a/src/lib/corelib/tools/propertyfinder.cpp b/src/lib/corelib/tools/propertyfinder.cpp new file mode 100644 index 000000000..96c081f01 --- /dev/null +++ b/src/lib/corelib/tools/propertyfinder.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 "propertyfinder.h" + +#include "qbsassert.h" + +namespace qbs { +namespace Internal { + +QVariantList PropertyFinder::propertyValues(const QVariantMap &properties, + const QString &moduleName, const QString &key, MergeType mergeType) +{ + m_moduleName = moduleName; + m_key = key; + m_values.clear(); + findModuleValues(properties, true); + if (mergeType == DoMergeLists) + mergeLists(&m_values); + return m_values; +} + +QVariant PropertyFinder::propertyValue(const QVariantMap &properties, const QString &moduleName, + const QString &key) +{ + m_moduleName = moduleName; + m_key = key; + m_values.clear(); + findModuleValues(properties, false); + + QBS_ASSERT(m_values.count() <= 1, return QVariant()); + return m_values.isEmpty() ? QVariant() : m_values.first(); +} + +void PropertyFinder::findModuleValues(const QVariantMap &properties, bool searchRecursively) +{ + QVariantMap moduleProperties = properties.value(QLatin1String("modules")).toMap(); + + // Direct hits come first. + const QVariantMap::Iterator modIt = moduleProperties.find(m_moduleName); + if (modIt != moduleProperties.end()) { + const QVariantMap moduleMap = modIt->toMap(); + const QVariant property = moduleMap.value(m_key); + addToList(property); + moduleProperties.erase(modIt); + } + + if (!searchRecursively) + return; + + // These are the non-matching modules. + for (QVariantMap::ConstIterator it = moduleProperties.constBegin(); + it != moduleProperties.constEnd(); ++it) { + findModuleValues(it->toMap(), true); + } +} + +void PropertyFinder::addToList(const QVariant &value) +{ + // Note: This means that manually setting a property to "null" will not lead to a "hit". + if (!value.isNull() && !m_values.contains(value)) + m_values << value; +} + +void PropertyFinder::mergeLists(QVariantList *values) +{ + QVariantList::iterator it = values->begin(); + while (it != values->end()) { + if (it->canConvert<QVariantList>()) { + QVariantList sublist = it->toList(); + mergeLists(&sublist); + it = values->erase(it); + for (int k = sublist.count(); --k >= 0;) + it = values->insert(it, sublist.at(k)); + } else { + ++it; + } + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/propertyfinder.h b/src/lib/corelib/tools/propertyfinder.h new file mode 100644 index 000000000..0db0ba39f --- /dev/null +++ b/src/lib/corelib/tools/propertyfinder.h @@ -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. +** +****************************************************************************/ +#ifndef QBS_PROPERTY_FINDER_H +#define QBS_PROPERTY_FINDER_H + +#include <QVariantList> +#include <QVariantMap> + +namespace qbs { +namespace Internal { + +class PropertyFinder +{ +public: + enum MergeType { DoMergeLists, DoNotMergeLists }; + QVariantList propertyValues(const QVariantMap &properties, const QString &moduleName, + const QString &key, MergeType mergeType = DoMergeLists); + + // Note that this can still be a list if the property type itself is one. + QVariant propertyValue(const QVariantMap &properties, const QString &moduleName, + const QString &key); + +private: + void findModuleValues(const QVariantMap &properties, bool searchRecursively); + void addToList(const QVariant &value); + static void mergeLists(QVariantList *values); + + QString m_moduleName; + QString m_key; + QVariantList m_values; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/qbs_export.h b/src/lib/corelib/tools/qbs_export.h new file mode 100644 index 000000000..da6779088 --- /dev/null +++ b/src/lib/corelib/tools/qbs_export.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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_EXPORT_H +#define QBS_EXPORT_H + +#include <qglobal.h> + +#ifdef QBS_STATIC_LIB +# define QBS_EXPORT +#else +# ifdef QBS_LIBRARY +# define QBS_EXPORT Q_DECL_EXPORT +# else +# define QBS_EXPORT Q_DECL_IMPORT +# endif +#endif + +#endif // Include guard. diff --git a/src/lib/corelib/tools/qbsassert.cpp b/src/lib/corelib/tools/qbsassert.cpp new file mode 100644 index 000000000..46ea58b83 --- /dev/null +++ b/src/lib/corelib/tools/qbsassert.cpp @@ -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. +** +****************************************************************************/ + +#include "qbsassert.h" +#include "error.h" + +#include <QString> + +namespace qbs { +namespace Internal { + +void writeAssertLocation(const char *condition, const char *file, int line) +{ + qDebug("SOFT ASSERT: %s in %s:%d", condition, file, line); +} + +void throwAssertLocation(const char *condition, const char *file, int line) +{ + throw ErrorInfo(QString(QLatin1String("ASSERT: %1")).arg(condition), + CodeLocation(QString::fromLocal8Bit(file), line), true); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/qbsassert.h b/src/lib/corelib/tools/qbsassert.h new file mode 100644 index 000000000..25c00ae1a --- /dev/null +++ b/src/lib/corelib/tools/qbsassert.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_QBSASSERT_H +#define QBS_QBSASSERT_H + +#include "qbs_export.h" + +namespace qbs { +namespace Internal { + +void QBS_EXPORT writeAssertLocation(const char *condition, const char *file, int line); +void QBS_EXPORT throwAssertLocation(const char *condition, const char *file, int line); + +} // namespace Internal +} // namespace qbs + +#define QBS_ASSERT(cond, action)\ + if (Q_LIKELY(cond)) {} else {\ + ::qbs::Internal::writeAssertLocation(#cond, __FILE__, __LINE__); action;\ + } do {} while (0) + +// The do {} while (0) is here to enforce the use of a semicolon after QBS_ASSERT. +// action can also be continue or break. Copied from qtcassert.h in Qt Creator. + +#define QBS_CHECK(cond)\ + do {\ + if (Q_LIKELY(cond)) {} else {\ + ::qbs::Internal::throwAssertLocation(#cond, __FILE__, __LINE__);\ + }\ + } while (0) + +#endif // QBS_QBSASSERT_H diff --git a/src/lib/corelib/tools/qttools.cpp b/src/lib/corelib/tools/qttools.cpp new file mode 100644 index 000000000..21fef9b8f --- /dev/null +++ b/src/lib/corelib/tools/qttools.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 "qttools.h" + +QT_BEGIN_NAMESPACE +uint qHash(const QStringList &list) +{ + uint s = 0; + foreach (const QString &n, list) + s ^= qHash(n) + 0x9e3779b9 + (s << 6) + (s >> 2); + return s; +} +QT_END_NAMESPACE diff --git a/src/lib/corelib/tools/qttools.h b/src/lib/corelib/tools/qttools.h new file mode 100644 index 000000000..bef545c0e --- /dev/null +++ b/src/lib/corelib/tools/qttools.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 QBSQTTOOLS_H +#define QBSQTTOOLS_H + +#include <QHash> +#include <QStringList> + +QT_BEGIN_NAMESPACE +uint qHash(const QStringList &list); +QT_END_NAMESPACE + +#endif // QBSQTTOOLS_H diff --git a/src/lib/corelib/tools/scannerpluginmanager.cpp b/src/lib/corelib/tools/scannerpluginmanager.cpp new file mode 100644 index 000000000..3c5dad702 --- /dev/null +++ b/src/lib/corelib/tools/scannerpluginmanager.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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 "scannerpluginmanager.h" + +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/hostosinfo.h> + +#include <QCoreApplication> +#include <QDirIterator> +#include <QLibrary> + +namespace qbs { +namespace Internal { + +ScannerPluginManager::~ScannerPluginManager() +{ + foreach (QLibrary * const lib, m_libs) { + lib->unload(); + delete lib; + } +} + +ScannerPluginManager *ScannerPluginManager::instance() +{ + static ScannerPluginManager scannerPlugin; + return &scannerPlugin; +} + +ScannerPluginManager::ScannerPluginManager() +{ +} + +QList<ScannerPlugin *> ScannerPluginManager::scannersForFileTag(const FileTag &fileTag) +{ + return instance()->m_scannerPlugins.value(fileTag); +} + +void ScannerPluginManager::loadPlugins(const QStringList &pluginPaths, const Logger &logger) +{ + QStringList filters; + + if (HostOsInfo::isWindowsHost()) + filters << "*.dll"; + else if (HostOsInfo::isOsxHost()) + filters << "*.dylib"; + else + filters << "*.so"; + + foreach (const QString &pluginPath, pluginPaths) { + logger.qbsTrace() << QString::fromLocal8Bit("pluginmanager: loading plugins from '%1'.") + .arg(QDir::toNativeSeparators(pluginPath)); + QDirIterator it(pluginPath, filters, QDir::Files); + while (it.hasNext()) { + const QString fileName = it.next(); + QScopedPointer<QLibrary> lib(new QLibrary(fileName)); + if (!lib->load()) { + logger.qbsWarning() << Tr::tr("pluginmanager: couldn't load plugin '%1': %2") + .arg(QDir::toNativeSeparators(fileName), lib->errorString()); + continue; + } + + getScanners_f getScanners = reinterpret_cast<getScanners_f>(lib->resolve("getScanners")); + if (!getScanners) { + logger.qbsWarning() << Tr::tr("pluginmanager: couldn't resolve " + "symbol in '%1'.").arg(QDir::toNativeSeparators(fileName)); + continue; + } + + ScannerPlugin **plugins = getScanners(); + if (plugins == 0) { + logger.qbsWarning() << Tr::tr("pluginmanager: no scanners " + "returned from '%1'.").arg(QDir::toNativeSeparators(fileName)); + continue; + } + + logger.qbsTrace() << QString::fromLocal8Bit("pluginmanager: scanner plugin '%1' " + "loaded.").arg(QDir::toNativeSeparators(fileName)); + + for (int i = 0; plugins[i] != 0; ++i) + m_scannerPlugins[FileTag(plugins[i]->fileTag)] += plugins[i]; + m_libs.append(lib.take()); + } + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/scannerpluginmanager.h b/src/lib/corelib/tools/scannerpluginmanager.h new file mode 100644 index 000000000..ce1ab2d2d --- /dev/null +++ b/src/lib/corelib/tools/scannerpluginmanager.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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_PLUGINS_H +#define QBS_PLUGINS_H + +#include <language/filetags.h> +#include <plugins/scanner/scanner.h> + +#include <QHash> +#include <QString> + +QT_BEGIN_NAMESPACE +class QLibrary; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class Logger; + +class ScannerPluginManager +{ +public: + ~ScannerPluginManager(); + static ScannerPluginManager *instance(); + static QList<ScannerPlugin *> scannersForFileTag(const FileTag &fileTag); + void loadPlugins(const QStringList &paths, const Logger &logger); + +private: + ScannerPluginManager(); + +private: + QList<QLibrary *> m_libs; + QHash<FileTag, QList<ScannerPlugin*> > m_scannerPlugins; +}; + +} // namespace Internal +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp new file mode 100644 index 000000000..c87898da5 --- /dev/null +++ b/src/lib/corelib/tools/scripttools.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 "scripttools.h" + +#include <QScriptEngine> +#include <QScriptValueIterator> + +QT_BEGIN_NAMESPACE + +QDataStream &operator<< (QDataStream &s, const QScriptProgram &script) +{ + s << script.sourceCode() + << script.fileName() + << script.firstLineNumber(); + return s; +} + +QDataStream &operator>> (QDataStream &s, QScriptProgram &script) +{ + QString fileName, sourceCode; + int lineNumber; + s >> sourceCode + >> fileName + >> lineNumber; + script = QScriptProgram(sourceCode, fileName, lineNumber); + return s; +} + +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value) +{ + if (name.length() == 1) { + cfg.insert(name.first(), value); + } else { + QVariant &subCfg = cfg[name.first()]; + QVariantMap subCfgMap = subCfg.toMap(); + setConfigProperty(subCfgMap, name.mid(1), value); + subCfg = subCfgMap; + } +} + +QVariant getConfigProperty(const QVariantMap &cfg, const QStringList &name) +{ + if (name.length() == 1) + return cfg.value(name.first()); + else + return getConfigProperty(cfg.value(name.first()).toMap(), name.mid(1)); +} + +QString toJSLiteral(const bool b) +{ + return b ? "true" : "false"; +} + +QString toJSLiteral(const QString &str) +{ + QString js = str; + js.replace(QRegExp("([\\\\\"])"), "\\\\1"); + js.prepend('"'); + js.append('"'); + return js; +} + +QString toJSLiteral(const QStringList &strs) +{ + QString js = "["; + for (int i = 0; i < strs.count(); ++i) { + if (i != 0) + js.append(", "); + js.append(toJSLiteral(strs.at(i))); + } + js.append(']'); + return js; +} + +QString toJSLiteral(const QVariant &val) +{ + if (!val.isValid()) { + return "undefined"; + } else if (val.type() == QVariant::List || val.type() == QVariant::StringList) { + QString res; + foreach (const QVariant &child, val.toList()) { + if (res.length()) res.append(", "); + res.append(toJSLiteral(child)); + } + res.prepend("["); + res.append("]"); + return res; + } else if (val.type() == QVariant::Bool) { + return val.toBool() ? "true" : "false"; + } else if (val.canConvert(QVariant::String)) { + return QLatin1Char('"') + val.toString() + QLatin1Char('"'); + } else { + return QString("Unconvertible type %1").arg(val.typeName()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h new file mode 100644 index 000000000..4230c898c --- /dev/null +++ b/src/lib/corelib/tools/scripttools.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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_SCRIPTTOOLS_H +#define QBS_SCRIPTTOOLS_H + +#include <tools/qbs_export.h> + +#include <QScriptEngine> +#include <QScriptProgram> +#include <QScriptValue> +#include <QSet> +#include <QStringList> +#include <QVariantMap> + +QT_BEGIN_NAMESPACE + +QDataStream &operator<< (QDataStream &s, const QScriptProgram &script); +QDataStream &operator>> (QDataStream &s, QScriptProgram &script); + +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +template <typename C> +QScriptValue toScriptValue(QScriptEngine *scriptEngine, const C &container) +{ + QScriptValue v = scriptEngine->newArray(container.count()); + int i = 0; + foreach (const typename C::value_type &item, container) + v.setProperty(i++, scriptEngine->toScriptValue(item)); + return v; +} + +void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value); +QVariant getConfigProperty(const QVariantMap &cfg, const QStringList &name); + +QString toJSLiteral(const bool b); +QString toJSLiteral(const QString &str); +QString toJSLiteral(const QStringList &strs); +QString toJSLiteral(const QVariant &val); + +/** + * @brief push/pop a QScriptEngine's context the RAII way. + */ +class ScriptEngineContextPusher +{ +public: + ScriptEngineContextPusher(QScriptEngine *scriptEngine) + : m_scriptEngine(scriptEngine) + { + m_scriptEngine->pushContext(); + } + + ~ScriptEngineContextPusher() + { + m_scriptEngine->popContext(); + } + +private: + QScriptEngine *m_scriptEngine; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTTOOLS_H diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp new file mode 100644 index 000000000..95d27ba24 --- /dev/null +++ b/src/lib/corelib/tools/settings.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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 "settings.h" + +#include "error.h" +#include <logging/translator.h> +#include <tools/hostosinfo.h> + +#include <QSettings> + +#include <algorithm> + +namespace qbs { +using namespace Internal; + +static QSettings::Format format() +{ + return HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat; +} + +static void migrateValue(QSettings *settings, const QString &key) +{ + const QVariant v = settings->value(key); + if (!v.isValid()) + return; + settings->setValue(QLatin1String("org/qt-project/qbs/") + key, v); + settings->remove(key); +} + +static void migrateGroup(QSettings *settings, const QString &group) +{ + QStringList fullKeys; + settings->beginGroup(group); + foreach (const QString &key, settings->allKeys()) + fullKeys += group + QLatin1Char('/') + key; + settings->endGroup(); + foreach (const QString &key, fullKeys) + migrateValue(settings, key); +} + +Settings::Settings(const QString &organization, const QString &application) + : m_settings(new QSettings(format(), QSettings::UserScope, organization, application)) +{ + if (HostOsInfo::isOsxHost()) { + // Migrate settings to internal group. + // ### remove in qbs 1.3 + if (!m_settings->childGroups().contains(QLatin1String("org/qt-project/qbs"))) { + migrateValue(m_settings, QLatin1String("defaultProfile")); + migrateGroup(m_settings, QLatin1String("profiles")); + migrateGroup(m_settings, QLatin1String("preferences")); + } + // Actual qbs settings are stored within a group, because QSettings sees extra system global + // settings on OS X we're not interested in. + m_settings->beginGroup(QLatin1String("org/qt-project/qbs")); + } +} + +Settings::~Settings() +{ + delete m_settings; +} + +QVariant Settings::value(const QString &key, const QVariant &defaultValue) const +{ + return m_settings->value(internalRepresentation(key), defaultValue); +} + +QStringList Settings::allKeys() const +{ + QStringList keys = m_settings->allKeys(); + fixupKeys(keys); + return keys; +} + +QStringList Settings::directChildren(const QString &parentGroup) +{ + m_settings->beginGroup(internalRepresentation(parentGroup)); + QStringList children = m_settings->childGroups(); + children << m_settings->childKeys(); + m_settings->endGroup(); + fixupKeys(children); + return children; +} + +QStringList Settings::allKeysWithPrefix(const QString &group) const +{ + m_settings->beginGroup(internalRepresentation(group)); + QStringList keys = m_settings->allKeys(); + m_settings->endGroup(); + fixupKeys(keys); + return keys; +} + +void Settings::setValue(const QString &key, const QVariant &value) +{ + m_settings->setValue(internalRepresentation(key), value); + checkStatus(); +} + +void Settings::remove(const QString &key) +{ + m_settings->remove(internalRepresentation(key)); + checkStatus(); +} + +void Settings::clear() +{ + m_settings->clear(); +} + +QString Settings::defaultProfile() const +{ + return value(QLatin1String("defaultProfile")).toString(); +} + +QStringList Settings::profiles() const +{ + m_settings->beginGroup(QLatin1String("profiles")); + QStringList result = m_settings->childGroups(); + m_settings->endGroup(); + return result; +} + +QString Settings::internalRepresentation(const QString &externalKey) const +{ + QString internalKey = externalKey; + return internalKey.replace(QLatin1Char('.'), QLatin1Char('/')); +} + +QString Settings::externalRepresentation(const QString &internalKey) const +{ + QString externalKey = internalKey; + return externalKey.replace(QLatin1Char('/'), QLatin1Char('.')); +} + +void Settings::fixupKeys(QStringList &keys) const +{ + keys.sort(); + keys.removeDuplicates(); + for (QStringList::Iterator it = keys.begin(); it != keys.end(); ++it) + *it = externalRepresentation(*it); +} + +void Settings::checkStatus() +{ + m_settings->sync(); + switch (m_settings->status()) { + case QSettings::NoError: + break; + case QSettings::AccessError: + throw ErrorInfo(Tr::tr("%1 is not accessible.").arg(m_settings->fileName())); + case QSettings::FormatError: + throw ErrorInfo(Tr::tr("Format error in %1.").arg(m_settings->fileName())); + } +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/settings.h b/src/lib/corelib/tools/settings.h new file mode 100644 index 000000000..2542df4fb --- /dev/null +++ b/src/lib/corelib/tools/settings.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_SETTINGS_H +#define QBS_SETTINGS_H + +#include "qbs_export.h" + +#include <QStringList> +#include <QVariant> + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace qbs { + +class QBS_EXPORT Settings +{ +public: + Settings(const QString &organization, const QString &application); + ~Settings(); + + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + QStringList allKeys() const; + QStringList directChildren(const QString &parentGroup); // Keys and groups. + QStringList allKeysWithPrefix(const QString &group) const; + void setValue(const QString &key, const QVariant &value); + void remove(const QString &key); + void clear(); + + QString defaultProfile() const; + QStringList profiles() const; + +private: + QString internalRepresentation(const QString &externalKey) const; + QString externalRepresentation(const QString &internalKey) const; + void fixupKeys(QStringList &keys) const; + void checkStatus(); + + QSettings * const m_settings; +}; + +} // namespace qbs + +#endif // QBS_SETTINGS_H diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp new file mode 100644 index 000000000..c3b00f3c4 --- /dev/null +++ b/src/lib/corelib/tools/setupprojectparameters.cpp @@ -0,0 +1,434 @@ +/**************************************************************************** +** +** 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 "setupprojectparameters.h" + +#include <logging/translator.h> +#include <tools/profile.h> +#include <tools/qbsassert.h> +#include <tools/scripttools.h> +#include <tools/settings.h> + +namespace qbs { +namespace Internal { + +/*! + * \class SetupProjectParameters + * \brief The \c SetupProjectParameters class comprises data required to set up a qbs project. + */ + +class SetupProjectParametersPrivate : public QSharedData +{ +public: + SetupProjectParametersPrivate() + : ignoreDifferentProjectFilePath(false) + , dryRun(false) + , logElapsedTime(false) + , restoreBehavior(SetupProjectParameters::RestoreAndTrackChanges) + , environment(QProcessEnvironment::systemEnvironment()) + { + } + + QString projectFilePath; + QString buildRoot; + QStringList searchPaths; + QStringList pluginPaths; + QVariantMap overriddenValues; + QVariantMap buildConfiguration; + mutable QVariantMap overriddenValuesTree; + mutable QVariantMap buildConfigurationTree; + mutable QVariantMap finalBuildConfigtree; + bool ignoreDifferentProjectFilePath; + bool dryRun; + bool logElapsedTime; + SetupProjectParameters::RestoreBehavior restoreBehavior; + QProcessEnvironment environment; +}; + +} // namespace Internal + +SetupProjectParameters::SetupProjectParameters() : d(new Internal::SetupProjectParametersPrivate) +{ +} + +SetupProjectParameters::SetupProjectParameters(const SetupProjectParameters &other) : d(other.d) +{ +} + +SetupProjectParameters::~SetupProjectParameters() +{ +} + +SetupProjectParameters &SetupProjectParameters::operator=(const SetupProjectParameters &other) +{ + d = other.d; + return *this; +} + +/*! + * \brief Returns the absolute path to the qbs project file. + * This file typically has a ".qbs" suffix. + */ +QString SetupProjectParameters::projectFilePath() const +{ + return d->projectFilePath; +} + +/*! + * \brief Sets the path to the main project file. + * \note The argument must be an absolute file path. + */ +void SetupProjectParameters::setProjectFilePath(const QString &projectFilePath) +{ + d->projectFilePath = projectFilePath; +} + +/*! + * \brief Returns the base path of where to put the build artifacts and store the build graph. + */ +QString SetupProjectParameters::buildRoot() const +{ + return d->buildRoot; +} + +/*! + * \brief Sets the base path of where to put the build artifacts and store the build graph. + * The same base path can be used for several build profiles of the same project without them + * interfering with each other. + * It might look as if this parameter would not be needed at the time of setting up the project, + * but keep in mind that the project information could already exist on disk, in which case + * loading it will be much faster than setting up the project from scratch. + * \note The argument must be an absolute path to a directory. + */ +void SetupProjectParameters::setBuildRoot(const QString &buildRoot) +{ + d->buildRoot = buildRoot; +} + +/*! + * \brief Where to look for modules and items to import. + */ +QStringList SetupProjectParameters::searchPaths() const +{ + return d->searchPaths; +} + +/*! + * \brief Sets the information about where to look for modules and items to import. + * \note The elements of the list must be absolute paths to directories. + */ +void SetupProjectParameters::setSearchPaths(const QStringList &searchPaths) +{ + d->searchPaths = searchPaths; +} + +/*! + * \brief Where to look for plugins. + */ +QStringList SetupProjectParameters::pluginPaths() const +{ + return d->pluginPaths; +} + +/*! + * \brief Sets the information about where to look for plugins. + * \note The elements of the list must be absolute paths to directories. + */ +void SetupProjectParameters::setPluginPaths(const QStringList &pluginPaths) +{ + d->pluginPaths = pluginPaths; +} + +/*! + * Returns the overridden values of the build configuration. + */ +QVariantMap SetupProjectParameters::overriddenValues() const +{ + return d->overriddenValues; +} + +/*! + * Set the overridden values of the build configuration. + */ +void SetupProjectParameters::setOverriddenValues(const QVariantMap &values) +{ + // warn if somebody tries to set a build configuration tree: + for (QVariantMap::const_iterator i = values.constBegin(); + i != values.constEnd(); ++i) { + QBS_ASSERT(i.value().type() != QVariant::Map, return); + } + d->overriddenValues = values; + d->overriddenValuesTree.clear(); + d->finalBuildConfigtree.clear(); +} + +static void provideValuesTree(const QVariantMap &values, QVariantMap *valueTree) +{ + if (!valueTree->isEmpty() || values.isEmpty()) + return; + + valueTree->clear(); + for (QVariantMap::const_iterator it = values.constBegin(); it != values.constEnd(); ++it) { + QStringList nameElements = it.key().split(QLatin1Char('.')); + if (nameElements.count() > 2) { // ### workaround for submodules being represented internally as a single module of name "module/submodule" rather than two nested modules "module" and "submodule" + const QString last = nameElements.takeLast(); + nameElements = QStringList(nameElements.join(QLatin1String("/"))); + nameElements.append(last); + } + Internal::setConfigProperty(*valueTree, nameElements, it.value()); + } +} + +QVariantMap SetupProjectParameters::overriddenValuesTree() const +{ + provideValuesTree(d->overriddenValues, &d->overriddenValuesTree); + return d->overriddenValuesTree; +} + +/*! + * \brief The collection of properties to use for resolving the project. + */ +QVariantMap SetupProjectParameters::buildConfiguration() const +{ + return d->buildConfiguration; +} + +/*! + * Sets the collection of properties to use for resolving the project. + * + * Keys are expected to be in dotted syntax (e.g. Qt.declarative.qmlDebugging) that is + * used by "qbs config". + */ +void SetupProjectParameters::setBuildConfiguration(const QVariantMap &buildConfiguration) +{ + // warn if somebody tries to set a build configuration tree: + for (QVariantMap::const_iterator i = buildConfiguration.constBegin(); + i != buildConfiguration.constEnd(); ++i) { + QBS_ASSERT(i.value().type() != QVariant::Map, return); + } + d->buildConfiguration = buildConfiguration; + d->buildConfigurationTree.clear(); + d->finalBuildConfigtree.clear(); +} + +/*! + * \brief Returns the build configuration in tree form. + * \return the tree form of the build configuration. + */ +QVariantMap SetupProjectParameters::buildConfigurationTree() const +{ + provideValuesTree(d->buildConfiguration, &d->buildConfigurationTree); + return d->buildConfigurationTree; +} + +/*! + * \brief Expands the build configuration based on the given settings. + * + * Expansion is the process by which the build configuration is completed based on the given + * settings. E.g. the information configured in a profile is filled into the build + * configuration by this step. + * + * This method returns an Error. The list of entries in this error will be empty is the + * expansion was successful. + */ +ErrorInfo SetupProjectParameters::expandBuildConfiguration(Settings *settings) +{ + ErrorInfo err; + + // Generates a full build configuration from user input, using the settings. + QVariantMap expandedConfig = d->buildConfiguration; + + const QString buildVariant = expandedConfig.value(QLatin1String("qbs.buildVariant")).toString(); + if (buildVariant.isEmpty()) + return ErrorInfo(Internal::Tr::tr("No build variant set.")); + if (buildVariant != QLatin1String("debug") && buildVariant != QLatin1String("release")) { + err.append(Internal::Tr::tr("Invalid build variant '%1'. Must be 'debug' or 'release'.") + .arg(buildVariant)); + return err; + } + + // Fill in buildCfg in this order (making sure not to overwrite a key already set by a previous stage) + // 1) Things specified on command line (already in buildCfg at this point) + // 2) Everything from the profile key + QString profileName = expandedConfig.value("qbs.profile").toString(); + if (profileName.isNull()) { + profileName = settings->defaultProfile(); + if (profileName.isNull()) { + const QString profileNames = settings->profiles().join(QLatin1String(", ")); + err.append(Internal::Tr::tr("No profile given and no default profile set.\n" + "Either set the configuration value 'defaultProfile' to a " + "valid profile name\n" + "or specify the profile with the command line parameter " + "'profile:name'.\n" + "The following profiles are available:\n%1") + .arg(profileNames)); + return err; + } + expandedConfig.insert("qbs.profile", profileName); + } + + // (2) + const Profile profile(profileName, settings); + const QStringList profileKeys = profile.allKeys(Profile::KeySelectionRecursive); + if (profileKeys.isEmpty()) { + err.append(Internal::Tr::tr("Unknown or empty profile '%1'.").arg(profileName)); + return err; + } + foreach (const QString &profileKey, profileKeys) { + if (!expandedConfig.contains(profileKey)) + expandedConfig.insert(profileKey, profile.value(profileKey)); + } + + if (d->buildConfiguration != expandedConfig) { + d->buildConfigurationTree.clear(); + d->buildConfiguration = expandedConfig; + } + return err; +} + +/*! + * \brief Returns the build configuration in tree form, with overridden values taken into account. + */ +QVariantMap SetupProjectParameters::finalBuildConfigurationTree() const +{ + if (d->finalBuildConfigtree.isEmpty()) { + QVariantMap finalMap = d->buildConfiguration; + for (QVariantMap::ConstIterator it = d->overriddenValues.constBegin(); + it != d->overriddenValues.constEnd(); ++it) { + finalMap.insert(it.key(), it.value()); + } + provideValuesTree(finalMap, &d->finalBuildConfigtree); + } + return d->finalBuildConfigtree; +} + +/*! + * \variable SetupProjectParameters::ignoreDifferentProjectFilePath + * \brief Returns true iff the saved build graph should be used even if its path to the + * project file is different from \c SetupProjectParameters::projectFilePath() + */ +bool SetupProjectParameters::ignoreDifferentProjectFilePath() const +{ + return d->ignoreDifferentProjectFilePath; +} + +/*! + * \brief Controls whether the path to the main project file may be different from the one + * stored in a possible build graph file. + * The default is false. + */ +void SetupProjectParameters::setIgnoreDifferentProjectFilePath(bool doIgnore) +{ + d->ignoreDifferentProjectFilePath = doIgnore; +} + + /*! + * \brief if true, qbs will not store the build graph of the resolved project. + */ +bool SetupProjectParameters::dryRun() const +{ + return d->dryRun; +} + + /*! + * \brief Controls whether the build graph will be stored. + * If the argument is true, qbs will not store the build graph after resolving the project. + * The default is false. + */ +void SetupProjectParameters::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + + /*! + * \brief Returns true iff the time the operation takes should be logged + */ +bool SetupProjectParameters::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * Controls whether to log the time taken up for resolving the project. + * The default is false. + */ +void SetupProjectParameters::setLogElapsedTime(bool logElapsedTime) +{ + d->logElapsedTime = logElapsedTime; +} + +/*! + * \brief Gets the environment used while resolving the project. + */ +QProcessEnvironment SetupProjectParameters::environment() const +{ + return d->environment; +} + +/*! + * \brief Sets the environment used while resolving the project. + */ +void SetupProjectParameters::setEnvironment(const QProcessEnvironment &env) +{ + d->environment = env; +} + + +/*! + * \enum SetupProjectParamaters::RestoreBehavior + * This enum type specifies how to deal with existing on-disk build information. + * \value RestoreOnly Indicates that a stored build graph is to be loaded and the information + * therein assumed to be up to date. It is then considered an error if no + * such build graph exists. + * \value ResolveOnly Indicates that no attempt should be made to restore an existing build graph. + * Instead, the project is to be resolved from scratch. + * \value RestoreAndTrackChanges Indicates that the build graph should be restored from disk + * if possible and otherwise set up from scratch. In the first case, + * (parts of) the project might still be re-resolved if certain + * parameters have changed (e.g. environment variables used in the + * project files). + */ + + +/*! + * Returns information about how restored build data will be handled. + */ +SetupProjectParameters::RestoreBehavior SetupProjectParameters::restoreBehavior() const +{ + return d->restoreBehavior; +} + +/*! + * Controls how restored build data will be handled. + */ +void SetupProjectParameters::setRestoreBehavior(SetupProjectParameters::RestoreBehavior behavior) +{ + d->restoreBehavior = behavior; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h new file mode 100644 index 000000000..d6d8bf88c --- /dev/null +++ b/src/lib/corelib/tools/setupprojectparameters.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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_SETUPPROJECTPARAMETERS_H +#define QBS_SETUPPROJECTPARAMETERS_H + +#include "qbs_export.h" + +#include <tools/error.h> + +#include <QProcessEnvironment> +#include <QSharedDataPointer> +#include <QStringList> +#include <QVariantMap> + +namespace qbs { + +class Settings; + +namespace Internal { class SetupProjectParametersPrivate; } + +class QBS_EXPORT SetupProjectParameters +{ +public: + SetupProjectParameters(); + SetupProjectParameters(const SetupProjectParameters &other); + ~SetupProjectParameters(); + + SetupProjectParameters &operator=(const SetupProjectParameters &other); + + QString projectFilePath() const; + void setProjectFilePath(const QString &projectFilePath); + + QString buildRoot() const; + void setBuildRoot(const QString &buildRoot); + + QStringList searchPaths() const; + void setSearchPaths(const QStringList &searchPaths); + + QStringList pluginPaths() const; + void setPluginPaths(const QStringList &pluginPaths); + + QVariantMap overriddenValues() const; + void setOverriddenValues(const QVariantMap &values); + QVariantMap overriddenValuesTree() const; + + QVariantMap buildConfiguration() const; + void setBuildConfiguration(const QVariantMap &buildConfiguration); + QVariantMap buildConfigurationTree() const; + ErrorInfo expandBuildConfiguration(Settings *settings); + + QVariantMap finalBuildConfigurationTree() const; + + bool ignoreDifferentProjectFilePath() const; + void setIgnoreDifferentProjectFilePath(bool doIgnore); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool logElapsedTime() const; + void setLogElapsedTime(bool logElapsedTime); + + QProcessEnvironment environment() const; + void setEnvironment(const QProcessEnvironment &env); + + enum RestoreBehavior { RestoreOnly, ResolveOnly, RestoreAndTrackChanges }; + RestoreBehavior restoreBehavior() const; + void setRestoreBehavior(RestoreBehavior behavior); + +private: + QSharedDataPointer<Internal::SetupProjectParametersPrivate> d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri new file mode 100644 index 000000000..fca3d8f4b --- /dev/null +++ b/src/lib/corelib/tools/tools.pri @@ -0,0 +1,79 @@ +INCLUDEPATH += $$PWD/../.. # for plugins + +HEADERS += \ + $$PWD/codelocation.h \ + $$PWD/error.h \ + $$PWD/fileinfo.h \ + $$PWD/filetime.h \ + $$PWD/id.h \ + $$PWD/persistence.h \ + $$PWD/scannerpluginmanager.h \ + $$PWD/scripttools.h \ + $$PWD/settings.h \ + $$PWD/preferences.h \ + $$PWD/profile.h \ + $$PWD/processresult.h \ + $$PWD/processresult_p.h \ + $$PWD/progressobserver.h \ + $$PWD/propertyfinder.h \ + $$PWD/hostosinfo.h \ + $$PWD/buildoptions.h \ + $$PWD/installoptions.h \ + $$PWD/cleanoptions.h \ + $$PWD/setupprojectparameters.h \ + $$PWD/persistentobject.h \ + $$PWD/weakpointer.h \ + $$PWD/qbs_export.h \ + $$PWD/qbsassert.h \ + $$PWD/qttools.h + +SOURCES += \ + $$PWD/codelocation.cpp \ + $$PWD/error.cpp \ + $$PWD/fileinfo.cpp \ + $$PWD/id.cpp \ + $$PWD/persistence.cpp \ + $$PWD/scannerpluginmanager.cpp \ + $$PWD/scripttools.cpp \ + $$PWD/settings.cpp \ + $$PWD/preferences.cpp \ + $$PWD/processresult.cpp \ + $$PWD/profile.cpp \ + $$PWD/progressobserver.cpp \ + $$PWD/propertyfinder.cpp \ + $$PWD/buildoptions.cpp \ + $$PWD/installoptions.cpp \ + $$PWD/cleanoptions.cpp \ + $$PWD/setupprojectparameters.cpp \ + $$PWD/qbsassert.cpp \ + $$PWD/qttools.cpp + +win32 { + SOURCES += $$PWD/filetime_win.cpp +} + +unix { + SOURCES += $$PWD/filetime_unix.cpp +} + +all_tests { + HEADERS += $$PWD/tst_tools.h + SOURCES += $$PWD/tst_tools.cpp +} + +!qbs_no_dev_install { + tools_headers.files = \ + $$PWD/cleanoptions.h \ + $$PWD/codelocation.h \ + $$PWD/error.h \ + $$PWD/settings.h \ + $$PWD/preferences.h \ + $$PWD/profile.h \ + $$PWD/processresult.h \ + $$PWD/qbs_export.h \ + $$PWD/buildoptions.h \ + $$PWD/installoptions.h \ + $$PWD/setupprojectparameters.h + tools_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/tools + INSTALLS += tools_headers +} diff --git a/src/lib/corelib/tools/tst_tools.cpp b/src/lib/corelib/tools/tst_tools.cpp new file mode 100644 index 000000000..c1af58f70 --- /dev/null +++ b/src/lib/corelib/tools/tst_tools.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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_tools.h" + +#include <tools/buildoptions.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/hostosinfo.h> +#include <tools/profile.h> +#include <tools/settings.h> +#include <tools/setupprojectparameters.h> + +#include <QFileInfo> +#include <QTemporaryFile> +#include <QTest> + +namespace qbs { +namespace Internal { + +TestTools::TestTools(Settings *settings) : m_settings(settings) +{ +} + +void TestTools::testFileInfo() +{ + QCOMPARE(FileInfo::fileName("C:/waffl/copter.exe"), QString("copter.exe")); + QCOMPARE(FileInfo::baseName("C:/waffl/copter.exe.lib"), QString("copter")); + QCOMPARE(FileInfo::completeBaseName("C:/waffl/copter.exe.lib"), QString("copter.exe")); + QCOMPARE(FileInfo::path("abc"), QString(".")); + QCOMPARE(FileInfo::path("/abc/lol"), QString("/abc")); + QVERIFY(!FileInfo::isAbsolute("bla/lol")); + QVERIFY(FileInfo::isAbsolute("/bla/lol")); + if (HostOsInfo::isWindowsHost()) + QVERIFY(FileInfo::isAbsolute("C:\\bla\\lol")); + QCOMPARE(FileInfo::resolvePath("/abc/lol", "waffl"), QString("/abc/lol/waffl")); + QCOMPARE(FileInfo::resolvePath("/abc/def/ghi/jkl/", "../foo/bar"), QString("/abc/def/ghi/foo/bar")); + QCOMPARE(FileInfo::resolvePath("/abc/def/ghi/jkl/", "../../foo/bar"), QString("/abc/def/foo/bar")); + QCOMPARE(FileInfo::resolvePath("/abc", "../../../foo/bar"), QString("/foo/bar")); + QCOMPARE(FileInfo("/does/not/exist").lastModified(), FileTime()); +} + +void TestTools::fileCaseCheck() +{ + QTemporaryFile tempFile(QLatin1String("CamelCase")); + QVERIFY(tempFile.open()); + QFileInfo tempFileInfo(tempFile.fileName()); + const QString lowerFilePath = tempFileInfo.absolutePath() + QLatin1Char('/') + + tempFileInfo.fileName().toLower(); + const QString upperFilePath = tempFileInfo.absolutePath() + QLatin1Char('/') + + tempFileInfo.fileName().toUpper(); + QVERIFY(FileInfo::isFileCaseCorrect(tempFileInfo.absoluteFilePath())); + if (QFile::exists(lowerFilePath)) + QVERIFY(!FileInfo::isFileCaseCorrect(lowerFilePath)); + if (QFile::exists(upperFilePath)) + QVERIFY(!FileInfo::isFileCaseCorrect(upperFilePath)); +} + +void TestTools::testProfiles() +{ + bool exceptionCaught; + Profile parentProfile("parent", m_settings); + Profile childProfile("child", m_settings); + try { + parentProfile.removeBaseProfile(); + parentProfile.remove("testKey"); + QCOMPARE(parentProfile.value("testKey", "none").toString(), QLatin1String("none")); + parentProfile.setValue("testKey", "testValue"); + QCOMPARE(parentProfile.value("testKey").toString(), QLatin1String("testValue")); + + childProfile.remove("testKey"); + childProfile.removeBaseProfile(); + QCOMPARE(childProfile.value("testKey", "none").toString(), QLatin1String("none")); + childProfile.setBaseProfile("parent"); + QCOMPARE(childProfile.value("testKey").toString(), QLatin1String("testValue")); + + // Change base profile and check if the inherited value also changes. + Profile fooProfile("foo", m_settings); + fooProfile.setValue("testKey", "gnampf"); + childProfile.setBaseProfile("foo"); + QCOMPARE(childProfile.value("testKey", "none").toString(), QLatin1String("gnampf")); + exceptionCaught = false; + } catch (ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(!exceptionCaught); + + try { + childProfile.setBaseProfile("SmurfAlongWithMe"); + childProfile.value("blubb"); + exceptionCaught = false; + } catch (ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(exceptionCaught); + + try { + childProfile.setBaseProfile("parent"); + parentProfile.setBaseProfile("child"); + QVERIFY(!childProfile.value("blubb").isValid()); + exceptionCaught = false; + } catch (ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(exceptionCaught); + + try { + QVERIFY(!childProfile.allKeys(Profile::KeySelectionNonRecursive).isEmpty()); + exceptionCaught = false; + } catch (ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(!exceptionCaught); + + try { + QVERIFY(!childProfile.allKeys(Profile::KeySelectionRecursive).isEmpty()); + exceptionCaught = false; + } catch (ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(exceptionCaught); +} + +void TestTools::testBuildConfigMerging() +{ + QVariantMap buildConfigMap; + buildConfigMap.insert(QLatin1String("topLevelKey"), QLatin1String("topLevelValue")); + buildConfigMap.insert(QLatin1String("qbs.toolchain"), QLatin1String("gcc")); + buildConfigMap.insert(QLatin1String("qbs.architecture"), + QLatin1String("Jean-Claude Pillemann")); + buildConfigMap.insert(QLatin1String("cpp.treatWarningsAsErrors"), true); + QVariantMap overrideMap; + overrideMap.insert(QLatin1String("qbs.toolchain"), QLatin1String("clang")); + SetupProjectParameters params; + params.setBuildConfiguration(buildConfigMap); + params.setOverriddenValues(overrideMap); + const QVariantMap finalMap = params.finalBuildConfigurationTree(); + QCOMPARE(finalMap.count(), 3); + QCOMPARE(finalMap.value(QLatin1String("topLevelKey")).toString(), + QString::fromLatin1("topLevelValue")); + const QVariantMap finalQbsMap = finalMap.value(QLatin1String("qbs")).toMap(); + QCOMPARE(finalQbsMap.count(), 2); + QCOMPARE(finalQbsMap.value(QLatin1String("toolchain")).toString(), + QString::fromLatin1("clang")); + QCOMPARE(finalQbsMap.value(QLatin1String("architecture")).toString(), + QString::fromLatin1("Jean-Claude Pillemann")); + const QVariantMap finalCppMap = finalMap.value(QLatin1String("cpp")).toMap(); + QCOMPARE(finalCppMap.count(), 1); + QCOMPARE(finalCppMap.value(QLatin1String("treatWarningsAsErrors")).toBool(), true); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/tst_tools.h b/src/lib/corelib/tools/tst_tools.h new file mode 100644 index 000000000..c7168a686 --- /dev/null +++ b/src/lib/corelib/tools/tst_tools.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 "qbs_export.h" + +#include <QObject> + +namespace qbs { +class Settings; + +namespace Internal { + +class QBS_EXPORT TestTools : public QObject +{ + Q_OBJECT + +public: + TestTools(Settings *settings); + +private slots: + void testFileInfo(); + void fileCaseCheck(); + void testProfiles(); + void testBuildConfigMerging(); + +private: + Settings * const m_settings; +}; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/weakpointer.h b/src/lib/corelib/tools/weakpointer.h new file mode 100644 index 000000000..3da1abd6d --- /dev/null +++ b/src/lib/corelib/tools/weakpointer.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_WEAKPOINTER_H +#define QBS_WEAKPOINTER_H + +#include <QWeakPointer> + +namespace qbs { +namespace Internal { + +template<typename T> class WeakPointer : public QWeakPointer<T> +{ +public: + WeakPointer() : QWeakPointer<T>() {} + WeakPointer(const QSharedPointer<T> &sharedPointer) : QWeakPointer<T>(sharedPointer) {} + template <class X> WeakPointer(const QSharedPointer<X> &sp) : QWeakPointer<T>(sp) { } + + + operator T*() const { return checkedData(); } + T *operator->() const { return checkedData(); } + T operator*() const { return *checkedData(); } + +private: + T *checkedData() const { + T * const d = QWeakPointer<T>::data(); + Q_ASSERT(d); // Calling code is not expecting this situation. + return d; + } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_WEAKPOINTER_H diff --git a/src/lib/corelib/use_corelib.pri b/src/lib/corelib/use_corelib.pri new file mode 100644 index 000000000..875c51f51 --- /dev/null +++ b/src/lib/corelib/use_corelib.pri @@ -0,0 +1,47 @@ +include(../../../qbs_version.pri) + +isEmpty(QBSLIBDIR) { + QBSLIBDIR = $$OUT_PWD/../../../lib +} + +QT += script xml + +unix { + LIBS += -L$$QBSLIBDIR -lqbscore +} + +!disable_rpath { + linux-*:QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,\$\$ORIGIN/../lib\' + macx:QMAKE_LFLAGS += -Wl,-rpath,@loader_path/../lib +} + +!CONFIG(static, static|shared) { + QBSCORELIBSUFFIX = $$QBS_VERSION_MAJ +} + +win32 { + CONFIG(debug, debug|release) { + QBSCORELIB = qbscored$$QBSCORELIBSUFFIX + } + CONFIG(release, debug|release) { + QBSCORELIB = qbscore$$QBSCORELIBSUFFIX + } + win32-msvc* { + LIBS += /LIBPATH:$$QBSLIBDIR + QBSCORELIB = $${QBSCORELIB}.lib + LIBS += Shell32.lib + } else { + LIBS += -L$${QBSLIBDIR} + QBSCORELIB = lib$${QBSCORELIB} + } + LIBS += $$QBSCORELIB +} + +INCLUDEPATH += \ + $$PWD + +CONFIG += depend_includepath + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} diff --git a/src/lib/corelib/use_installed_corelib.pri b/src/lib/corelib/use_installed_corelib.pri new file mode 100644 index 000000000..bac8d6da7 --- /dev/null +++ b/src/lib/corelib/use_installed_corelib.pri @@ -0,0 +1,38 @@ +include(qbs_version.pri) + +QT += script xml + +QBSLIBDIR=$${PWD}/../../lib +unix { + LIBS += -L$$QBSLIBDIR -lqbscore +} + +!disable_rpath:unix:QMAKE_LFLAGS += -Wl,-rpath,$${QBSLIBDIR} + +!CONFIG(static, static|shared) { + QBSCORELIBSUFFIX = $$QBS_VERSION_MAJ +} + +win32 { + CONFIG(debug, debug|release) { + QBSCORELIB = qbscored$$QBSCORELIBSUFFIX + } + CONFIG(release, debug|release) { + QBSCORELIB = qbscore$$QBSCORELIBSUFFIX + } + win32-msvc* { + LIBS += /LIBPATH:$$QBSLIBDIR + QBSCORELIB = $${QBSCORELIB}.lib + LIBS += Shell32.lib + } else { + LIBS += -L$${QBSLIBDIR} + QBSCORELIB = lib$${QBSCORELIB} + } + LIBS += $$QBSCORELIB +} + +INCLUDEPATH += $${PWD} $${PWD}/.. + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} |