aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/language/scriptengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/corelib/language/scriptengine.cpp')
-rw-r--r--src/lib/corelib/language/scriptengine.cpp314
1 files changed, 314 insertions, 0 deletions
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