1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2016 The Qt Company Ltd. 4 ** Contact: https://www.qt.io/licensing/ 5 ** 6 ** This file is part of Qbs. 7 ** 8 ** $QT_BEGIN_LICENSE:LGPL$ 9 ** Commercial License Usage 10 ** Licensees holding valid commercial Qt licenses may use this file in 11 ** accordance with the commercial license agreement provided with the 12 ** Software or, alternatively, in accordance with the terms contained in 13 ** a written agreement between you and The Qt Company. For licensing terms 14 ** and conditions see https://www.qt.io/terms-conditions. For further 15 ** information use the contact form at https://www.qt.io/contact-us. 16 ** 17 ** GNU Lesser General Public License Usage 18 ** Alternatively, this file may be used under the terms of the GNU Lesser 19 ** General Public License version 3 as published by the Free Software 20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the 21 ** packaging of this file. Please review the following information to 22 ** ensure the GNU Lesser General Public License version 3 requirements 23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24 ** 25 ** GNU General Public License Usage 26 ** Alternatively, this file may be used under the terms of the GNU 27 ** General Public License version 2.0 or (at your option) the GNU General 28 ** Public license version 3 or any later version approved by the KDE Free 29 ** Qt Foundation. The licenses are as published by the Free Software 30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31 ** included in the packaging of this file. Please review the following 32 ** information to ensure the GNU General Public License requirements will 33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34 ** https://www.gnu.org/licenses/gpl-3.0.html. 35 ** 36 ** $QT_END_LICENSE$ 37 ** 38 ****************************************************************************/ 39 40 #ifndef QBS_SCRIPTENGINE_H 41 #define QBS_SCRIPTENGINE_H 42 43 #include "forward_decls.h" 44 #include "property.h" 45 #include <buildgraph/requestedartifacts.h> 46 #include <buildgraph/requesteddependencies.h> 47 #include <logging/logger.h> 48 #include <tools/codelocation.h> 49 #include <tools/filetime.h> 50 #include <tools/porting.h> 51 #include <tools/set.h> 52 53 #include <QtCore/qdir.h> 54 #include <QtCore/qhash.h> 55 #include <QtCore/qlist.h> 56 #include <QtCore/qprocess.h> 57 #include <QtCore/qstring.h> 58 59 #include <QtScript/qscriptengine.h> 60 61 #include <memory> 62 #include <mutex> 63 #include <stack> 64 #include <tuple> 65 #include <unordered_map> 66 #include <vector> 67 68 namespace qbs { 69 namespace Internal { 70 class Artifact; 71 class JsImport; 72 class PrepareScriptObserver; 73 class ScriptImporter; 74 class ScriptPropertyObserver; 75 76 enum class EvalContext { 77 PropertyEvaluation, ProbeExecution, ModuleProvider, RuleExecution, JsCommand 78 }; 79 class DubiousContext 80 { 81 public: 82 enum Suggestion { NoSuggestion, SuggestMoving }; context(c)83 DubiousContext(EvalContext c, Suggestion s = NoSuggestion) : context(c), suggestion(s) { } 84 EvalContext context; 85 Suggestion suggestion; 86 }; 87 using DubiousContextList = std::vector<DubiousContext>; 88 89 90 /* 91 * ScriptObject that acquires resources, for example a file handle. 92 * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources. 93 */ 94 class ResourceAcquiringScriptObject 95 { 96 public: 97 virtual ~ResourceAcquiringScriptObject() = default; 98 virtual void releaseResources() = 0; 99 }; 100 101 enum class ObserveMode { Enabled, Disabled }; 102 103 class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine 104 { 105 Q_OBJECT 106 ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); 107 public: 108 static ScriptEngine *create(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); 109 ~ScriptEngine() override; 110 logger()111 Logger &logger() const { return m_logger; } 112 void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, 113 ObserveMode observeMode); 114 void clearImportsCache(); 115 setEvalContext(EvalContext c)116 void setEvalContext(EvalContext c) { m_evalContext = c; } evalContext()117 EvalContext evalContext() const { return m_evalContext; } 118 void checkContext(const QString &operation, const DubiousContextList &dubiousContexts); 119 addPropertyRequestedInScript(const Property & property)120 void addPropertyRequestedInScript(const Property &property) { 121 m_propertiesRequestedInScript += property; 122 } addDependenciesArrayRequested(const ResolvedProduct * p)123 void addDependenciesArrayRequested(const ResolvedProduct *p) 124 { 125 m_productsWithRequestedDependencies.insert(p); 126 } setArtifactsMapRequested(const ResolvedProduct * product,bool forceUpdate)127 void setArtifactsMapRequested(const ResolvedProduct *product, bool forceUpdate) 128 { 129 m_requestedArtifacts.setAllArtifactTags(product, forceUpdate); 130 } setArtifactSetRequestedForTag(const ResolvedProduct * product,const FileTag & tag)131 void setArtifactSetRequestedForTag(const ResolvedProduct *product, const FileTag &tag) 132 { 133 m_requestedArtifacts.setArtifactsForTag(product, tag); 134 } setNonExistingArtifactSetRequested(const ResolvedProduct * product,const QString & tag)135 void setNonExistingArtifactSetRequested(const ResolvedProduct *product, const QString &tag) 136 { 137 m_requestedArtifacts.setNonExistingTagRequested(product, tag); 138 } setArtifactsEnumerated(const ResolvedProduct * product)139 void setArtifactsEnumerated(const ResolvedProduct *product) 140 { 141 m_requestedArtifacts.setArtifactsEnumerated(product); 142 } 143 void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); addRequestedExport(const ResolvedProduct * product)144 void addRequestedExport(const ResolvedProduct *product) { m_requestedExports.insert(product); } clearRequestedProperties()145 void clearRequestedProperties() { 146 m_propertiesRequestedInScript.clear(); 147 m_propertiesRequestedFromArtifact.clear(); 148 m_importsRequestedInScript.clear(); 149 m_productsWithRequestedDependencies.clear(); 150 m_requestedArtifacts.clear(); 151 m_requestedExports.clear(); 152 } propertiesRequestedInScript()153 PropertySet propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } propertiesRequestedFromArtifact()154 QHash<QString, PropertySet> propertiesRequestedFromArtifact() const { 155 return m_propertiesRequestedFromArtifact; 156 } productsWithRequestedDependencies()157 Set<const ResolvedProduct *> productsWithRequestedDependencies() const 158 { 159 return m_productsWithRequestedDependencies; 160 } requestedDependencies()161 RequestedDependencies requestedDependencies() const 162 { 163 return RequestedDependencies(m_productsWithRequestedDependencies); 164 } requestedArtifacts()165 RequestedArtifacts requestedArtifacts() const { return m_requestedArtifacts; } requestedExports()166 Set<const ResolvedProduct *> requestedExports() const { return m_requestedExports; } 167 168 void addImportRequestedInScript(qint64 importValueId); 169 std::vector<QString> importedFilesUsedInScript() const; 170 setUsesIo()171 void setUsesIo() { m_usesIo = true; } clearUsesIo()172 void clearUsesIo() { m_usesIo = false; } usesIo()173 bool usesIo() const { return m_usesIo; } 174 175 void enableProfiling(bool enable); 176 setPropertyCacheEnabled(bool enable)177 void setPropertyCacheEnabled(bool enable) { m_propertyCacheEnabled = enable; } isPropertyCacheEnabled()178 bool isPropertyCacheEnabled() const { return m_propertyCacheEnabled; } 179 void addToPropertyCache(const QString &moduleName, const QString &propertyName, 180 const PropertyMapConstPtr &propertyMap, const QVariant &value); 181 QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName, 182 const PropertyMapConstPtr &propertyMap); 183 184 void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor); 185 void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value); 186 void unobserveProperties(); 187 void setDeprecatedProperty(QScriptValue &object, const QString &name, const QString &newName, 188 const QScriptValue &value); observer()189 PrepareScriptObserver *observer() const { return m_observer.get(); } 190 191 QProcessEnvironment environment() const; 192 void setEnvironment(const QProcessEnvironment &env); 193 void addCanonicalFilePathResult(const QString &filePath, const QString &resultFilePath); 194 void addFileExistsResult(const QString &filePath, bool exists); 195 void addDirectoryEntriesResult(const QString &path, QDir::Filters filters, 196 const QStringList &entries); 197 void addFileLastModifiedResult(const QString &filePath, const FileTime &fileTime); canonicalFilePathResults()198 QHash<QString, QString> canonicalFilePathResults() const { return m_canonicalFilePathResult; } fileExistsResults()199 QHash<QString, bool> fileExistsResults() const { return m_fileExistsResult; } directoryEntriesResults()200 QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults() const 201 { 202 return m_directoryEntriesResult; 203 } 204 fileLastModifiedResults()205 QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; } 206 Set<QString> imports() const; 207 static QScriptValueList argumentList(const QStringList &argumentNames, 208 const QScriptValue &context); 209 uncaughtExceptionBacktraceOrEmpty()210 QStringList uncaughtExceptionBacktraceOrEmpty() const { 211 return hasUncaughtException() ? uncaughtExceptionBacktrace() : QStringList(); 212 } hasErrorOrException(const QScriptValue & v)213 bool hasErrorOrException(const QScriptValue &v) const { 214 return v.isError() || hasUncaughtException(); 215 } lastErrorValue(const QScriptValue & v)216 QScriptValue lastErrorValue(const QScriptValue &v) const { 217 return v.isError() ? v : uncaughtException(); 218 } lastErrorString(const QScriptValue & v)219 QString lastErrorString(const QScriptValue &v) const { return lastErrorValue(v).toString(); } 220 CodeLocation lastErrorLocation(const QScriptValue &v, 221 const CodeLocation &fallbackLocation = CodeLocation()) const; 222 ErrorInfo lastError(const QScriptValue &v, 223 const CodeLocation &fallbackLocation = CodeLocation()) const; 224 225 void cancel(); 226 227 // The active flag is different from QScriptEngine::isEvaluating. 228 // It is set and cleared externally for example by the rule execution code. isActive()229 bool isActive() const { return m_active; } setActive(bool on)230 void setActive(bool on) { m_active = on; } 231 232 using QScriptEngine::newFunction; 233 234 template <typename T, typename E, 235 typename = std::enable_if_t<std::is_pointer_v<T>>, 236 typename = std::enable_if_t<std::is_pointer_v<E>>, 237 typename = std::enable_if_t<std::is_base_of_v< 238 QScriptEngine, std::remove_pointer_t<E>>> newFunction(QScriptValue (* signature)(QScriptContext *,E,T),T arg)239 > QScriptValue newFunction(QScriptValue (*signature)(QScriptContext *, E, T), T arg) { 240 return QScriptEngine::newFunction( 241 reinterpret_cast<FunctionWithArgSignature>(signature), 242 reinterpret_cast<void *>(const_cast< 243 std::add_pointer_t< 244 std::remove_const_t< 245 std::remove_pointer_t<T>>>>(arg))); 246 } 247 248 QScriptClass *modulePropertyScriptClass() const; 249 void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass); 250 productPropertyScriptClass()251 QScriptClass *productPropertyScriptClass() const { return m_productPropertyScriptClass; } setProductPropertyScriptClass(QScriptClass * productPropertyScriptClass)252 void setProductPropertyScriptClass(QScriptClass *productPropertyScriptClass) 253 { 254 m_productPropertyScriptClass = productPropertyScriptClass; 255 } 256 artifactsScriptClass()257 QScriptClass *artifactsScriptClass() const { return m_artifactsScriptClass; } setArtifactsScriptClass(QScriptClass * artifactsScriptClass)258 void setArtifactsScriptClass(QScriptClass *artifactsScriptClass) 259 { 260 m_artifactsScriptClass = artifactsScriptClass; 261 } 262 263 void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj); 264 void releaseResourcesOfScriptObjects(); 265 productScriptValuePrototype(const ResolvedProduct * product)266 QScriptValue &productScriptValuePrototype(const ResolvedProduct *product) 267 { 268 return m_productScriptValues[product]; 269 } 270 projectScriptValue(const ResolvedProject * project)271 QScriptValue &projectScriptValue(const ResolvedProject *project) 272 { 273 return m_projectScriptValues[project]; 274 } 275 moduleScriptValuePrototype(const ResolvedModule * module)276 QScriptValue &moduleScriptValuePrototype(const ResolvedModule *module) 277 { 278 return m_moduleScriptValues[module]; 279 } 280 281 private: 282 QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE; 283 284 void abort(); 285 286 bool gatherFileResults() const; 287 288 void installQbsBuiltins(); 289 void extendJavaScriptBuiltins(); 290 void installFunction(const QString &name, int length, QScriptValue *functionValue, 291 FunctionSignature f, QScriptValue *targetObject); 292 void installQbsFunction(const QString &name, int length, FunctionSignature f); 293 void installConsoleFunction(const QString &name, 294 QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)); 295 void installImportFunctions(); 296 void uninstallImportFunctions(); 297 void import(const JsImport &jsImport, QScriptValue &targetObject); 298 void observeImport(QScriptValue &jsImport); 299 void importFile(const QString &filePath, QScriptValue &targetObject); 300 static QScriptValue js_loadExtension(QScriptContext *context, QScriptEngine *qtengine); 301 static QScriptValue js_loadFile(QScriptContext *context, QScriptEngine *qtengine); 302 static QScriptValue js_require(QScriptContext *context, QScriptEngine *qtengine); 303 304 class PropertyCacheKey 305 { 306 public: 307 PropertyCacheKey(QString moduleName, QString propertyName, 308 PropertyMapConstPtr propertyMap); 309 private: 310 const QString m_moduleName; 311 const QString m_propertyName; 312 const PropertyMapConstPtr m_propertyMap; 313 314 friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); 315 friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed); 316 }; 317 318 friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); 319 friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed); 320 321 static std::mutex m_creationDestructionMutex; 322 ScriptImporter *m_scriptImporter; 323 QScriptClass *m_modulePropertyScriptClass; 324 QScriptClass *m_productPropertyScriptClass = nullptr; 325 QScriptClass *m_artifactsScriptClass = nullptr; 326 QHash<JsImport, QScriptValue> m_jsImportCache; 327 std::unordered_map<QString, QScriptValue> m_jsFileCache; 328 bool m_propertyCacheEnabled; 329 bool m_active; 330 QHash<PropertyCacheKey, QVariant> m_propertyCache; 331 PropertySet m_propertiesRequestedInScript; 332 QHash<QString, PropertySet> m_propertiesRequestedFromArtifact; 333 Logger &m_logger; 334 QScriptValue m_definePropertyFunction; 335 QScriptValue m_emptyFunction; 336 QProcessEnvironment m_environment; 337 QHash<QString, QString> m_canonicalFilePathResult; 338 QHash<QString, bool> m_fileExistsResult; 339 QHash<std::pair<QString, quint32>, QStringList> m_directoryEntriesResult; 340 QHash<QString, FileTime> m_fileLastModifiedResult; 341 std::stack<QString> m_currentDirPathStack; 342 std::stack<QStringList> m_extensionSearchPathsStack; 343 QScriptValue m_loadFileFunction; 344 QScriptValue m_loadExtensionFunction; 345 QScriptValue m_requireFunction; 346 QScriptValue m_qbsObject; 347 QScriptValue m_consoleObject; 348 QScriptValue m_cancelationError; 349 qint64 m_elapsedTimeImporting = -1; 350 bool m_usesIo = false; 351 EvalContext m_evalContext; 352 std::vector<ResourceAcquiringScriptObject *> m_resourceAcquiringScriptObjects; 353 const std::unique_ptr<PrepareScriptObserver> m_observer; 354 std::vector<std::tuple<QScriptValue, QString, QScriptValue>> m_observedProperties; 355 std::vector<QScriptValue> m_requireResults; 356 std::unordered_map<qint64, std::vector<QString>> m_filePathsPerImport; 357 std::vector<qint64> m_importsRequestedInScript; 358 Set<const ResolvedProduct *> m_productsWithRequestedDependencies; 359 RequestedArtifacts m_requestedArtifacts; 360 Set<const ResolvedProduct *> m_requestedExports; 361 ObserveMode m_observeMode = ObserveMode::Disabled; 362 std::unordered_map<const ResolvedProduct *, QScriptValue> m_productScriptValues; 363 std::unordered_map<const ResolvedProject *, QScriptValue> m_projectScriptValues; 364 std::unordered_map<const ResolvedModule *, QScriptValue> m_moduleScriptValues; 365 }; 366 367 class EvalContextSwitcher 368 { 369 public: EvalContextSwitcher(ScriptEngine * engine,EvalContext newContext)370 EvalContextSwitcher(ScriptEngine *engine, EvalContext newContext) 371 : m_engine(engine), m_oldContext(engine->evalContext()) 372 { 373 engine->setEvalContext(newContext); 374 } 375 ~EvalContextSwitcher()376 ~EvalContextSwitcher() { m_engine->setEvalContext(m_oldContext); } 377 378 private: 379 ScriptEngine * const m_engine; 380 const EvalContext m_oldContext; 381 }; 382 383 } // namespace Internal 384 } // namespace qbs 385 386 #endif // QBS_SCRIPTENGINE_H 387