diff options
Diffstat (limited to 'src/lib/corelib/buildgraph/rulesapplicator.cpp')
-rw-r--r-- | src/lib/corelib/buildgraph/rulesapplicator.cpp | 260 |
1 files changed, 136 insertions, 124 deletions
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp index f464734b8..94cee0c62 100644 --- a/src/lib/corelib/buildgraph/rulesapplicator.cpp +++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp @@ -1,5 +1,3 @@ -#include <utility> - /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. @@ -41,7 +39,6 @@ #include "rulesapplicator.h" #include "buildgraph.h" -#include "productbuilddata.h" #include "projectbuilddata.h" #include "qtmocscanner.h" #include "rulecommands.h" @@ -57,7 +54,6 @@ #include <language/preparescriptobserver.h> #include <language/propertymapinternal.h> #include <language/resolvedfilecontext.h> -#include <language/scriptengine.h> #include <logging/categories.h> #include <logging/translator.h> #include <tools/error.h> @@ -69,7 +65,6 @@ #include <QtCore/qcryptographichash.h> #include <QtCore/qdir.h> -#include <QtScript/qscriptvalueiterator.h> #include <memory> #include <vector> @@ -113,10 +108,10 @@ void RulesApplicator::applyRule(RuleNode *ruleNode, const ArtifactSet &inputArti m_completeInputSet = inputArtifacts; if (m_rule->name.startsWith(QLatin1String("QtCoreMocRule"))) { delete m_mocScanner; - m_mocScanner = new QtMocScanner(m_product, scope()); + m_mocScanner = new QtMocScanner(m_product, engine(), scope()); } - QScriptValue prepareScriptContext = engine()->newObject(); - prepareScriptContext.setPrototype(engine()->globalObject()); + ScopedJsValue prepareScriptContext(jsContext(), engine()->newObject()); + JS_SetPrototype(jsContext(), prepareScriptContext, engine()->globalObject()); setupScriptEngineForFile(engine(), m_rule->prepareScript.fileContext(), scope(), ObserveMode::Enabled); setupScriptEngineForProduct(engine(), m_product.get(), m_rule->module.get(), @@ -134,6 +129,7 @@ void RulesApplicator::applyRule(RuleNode *ruleNode, const ArtifactSet &inputArti } if (engine()->usesIo()) m_ruleUsesIo = true; + engine()->releaseInputArtifactScriptValues(ruleNode); } void RulesApplicator::handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts, @@ -150,7 +146,7 @@ void RulesApplicator::handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts project->buildData->removeArtifactAndExclusiveDependents(removedArtifact, logger, true, &artifactsToRemove); } - for (Artifact * const artifact : qAsConst(artifactsToRemove)) { + for (Artifact * const artifact : std::as_const(artifactsToRemove)) { QBS_CHECK(!inputArtifacts.contains(artifact)); removedArtifacts << artifact->filePath(); delete artifact; @@ -164,9 +160,9 @@ ArtifactSet RulesApplicator::collectAuxiliaryInputs(const Rule *rule, CurrentProduct | Dependencies); } -static void copyProperty(const QString &name, const QScriptValue &src, QScriptValue dst) +static void copyProperty(JSContext *ctx, const QString &name, const JSValue &src, JSValue dst) { - dst.setProperty(name, src.property(name)); + setJsProperty(ctx, dst, name, getJsProperty(ctx, src, name)); } static QStringList toStringList(const ArtifactSet &artifacts) @@ -180,7 +176,7 @@ static QStringList toStringList(const ArtifactSet &artifacts) return lst; } -void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &prepareScriptContext) +void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, JSValue prepareScriptContext) { evalContext()->checkForCancelation(); for (const Artifact *inputArtifact : inputArtifacts) @@ -202,20 +198,22 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p engine()->clearRequestedProperties(); // create the output artifacts from the set of input artifacts - m_transformer->setupInputs(prepareScriptContext); - m_transformer->setupExplicitlyDependsOn(prepareScriptContext); - copyProperty(StringConstants::inputsVar(), prepareScriptContext, scope()); - copyProperty(StringConstants::inputVar(), prepareScriptContext, scope()); - copyProperty(StringConstants::explicitlyDependsOnVar(), prepareScriptContext, scope()); - copyProperty(StringConstants::productVar(), prepareScriptContext, scope()); - copyProperty(StringConstants::projectVar(), prepareScriptContext, scope()); + m_transformer->setupInputs(engine(), prepareScriptContext); + m_transformer->setupExplicitlyDependsOn(engine(), prepareScriptContext); + copyProperty(jsContext(), StringConstants::inputsVar(), prepareScriptContext, scope()); + copyProperty(jsContext(), StringConstants::inputVar(), prepareScriptContext, scope()); + copyProperty(jsContext(), StringConstants::explicitlyDependsOnVar(), + prepareScriptContext, scope()); + copyProperty(jsContext(), StringConstants::productVar(), prepareScriptContext, scope()); + copyProperty(jsContext(), StringConstants::projectVar(), prepareScriptContext, scope()); if (m_rule->isDynamic()) { - outputArtifacts = runOutputArtifactsScript(inputArtifacts, - ScriptEngine::argumentList(Rule::argumentNamesForOutputArtifacts(), scope())); + const ScopedJsValueList argList + = engine()->argumentList(Rule::argumentNamesForOutputArtifacts(), scope()); + outputArtifacts = runOutputArtifactsScript(inputArtifacts, argList); } else { Set<QString> outputFilePaths; for (const auto &ruleArtifact : m_rule->artifacts) { - const OutputArtifactInfo outputInfo = createOutputArtifactFromRuleArtifact( + const OutputArtifactInfo &outputInfo = createOutputArtifactFromRuleArtifact( ruleArtifact, inputArtifacts, &outputFilePaths); if (!outputInfo.artifact) continue; @@ -249,59 +247,64 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p if (outputArtifacts.empty()) return; - for (Artifact * const outputArtifact : qAsConst(outputArtifacts)) { - for (Artifact * const dependency : qAsConst(m_transformer->explicitlyDependsOn)) + for (Artifact * const outputArtifact : std::as_const(outputArtifacts)) { + for (Artifact * const dependency : std::as_const(m_transformer->explicitlyDependsOn)) connect(outputArtifact, dependency); } if (inputArtifacts != m_transformer->inputs) - m_transformer->setupInputs(prepareScriptContext); + m_transformer->setupInputs(engine(), prepareScriptContext); // change the transformer outputs according to the bindings in Artifact - QScriptValue scriptValue; - if (!ruleArtifactArtifactMap.empty()) - engine()->setGlobalObject(prepareScriptContext); - for (auto it = ruleArtifactArtifactMap.crbegin(), end = ruleArtifactArtifactMap.crend(); - it != end; ++it) { - const RuleArtifact *ra = it->first; - if (ra->bindings.empty()) - continue; - - // expose attributes of this artifact - const OutputArtifactInfo outputInfo = it->second; - Artifact *outputArtifact = outputInfo.artifact; - outputArtifact->properties = outputArtifact->properties->clone(); + if (!ruleArtifactArtifactMap.empty()) { + const TemporaryGlobalObjectSetter gos(engine(), prepareScriptContext); + for (auto it = ruleArtifactArtifactMap.crbegin(), end = ruleArtifactArtifactMap.crend(); + it != end; ++it) { + const RuleArtifact *ra = it->first; + if (ra->bindings.empty()) + continue; - scope().setProperty(StringConstants::fileNameProperty(), - engine()->toScriptValue(outputArtifact->filePath())); - scope().setProperty(StringConstants::fileTagsProperty(), - toScriptValue(engine(), outputArtifact->fileTags().toStringList())); - - QVariantMap artifactModulesCfg = outputArtifact->properties->value(); - for (const auto &binding : ra->bindings) { - scriptValue = engine()->evaluate(binding.code); - if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) { - QString msg = QStringLiteral("evaluating rule binding '%1': %2"); - throw ErrorInfo(msg.arg(binding.name.join(QLatin1Char('.')), - engine()->lastErrorString(scriptValue)), - engine()->lastErrorLocation(scriptValue, binding.location)); + // expose attributes of this artifact + const OutputArtifactInfo &outputInfo = it->second; + Artifact *outputArtifact = outputInfo.artifact; + outputArtifact->properties = outputArtifact->properties->clone(); + + setJsProperty(jsContext(), scope(), StringConstants::fileNameProperty(), + engine()->toScriptValue(outputArtifact->filePath())); + setJsProperty(jsContext(), scope(), StringConstants::fileTagsProperty(), + makeJsStringList(engine()->context(), + outputArtifact->fileTags().toStringList())); + + QVariantMap artifactModulesCfg = outputArtifact->properties->value(); + for (const auto &binding : ra->bindings) { + const ScopedJsValue scriptValue(jsContext(), engine()->evaluate( + JsValueOwner::Caller, binding.code, + binding.location.filePath(), + binding.location.line())); + if (JsException ex = engine()->checkAndClearException(binding.location)) { + ErrorInfo err = ex.toErrorInfo(); + err.prepend(QStringLiteral("evaluating rule binding '%1'") + .arg(binding.name.join(QLatin1Char('.')))); + throw err; + } + const QVariant value = getJsVariant(jsContext(), scriptValue); + setConfigProperty(artifactModulesCfg, binding.name, value); + outputArtifact->pureProperties.emplace_back(binding.name, value); + } + outputArtifact->properties->setValue(artifactModulesCfg); + if (!outputInfo.newlyCreated + && (outputArtifact->fileTags() != outputInfo.oldFileTags + || !qVariantMapsEqual( + outputArtifact->properties->value(), outputInfo.oldProperties))) { + invalidateArtifactAsRuleInputIfNecessary(outputArtifact); } - const QVariant value = scriptValue.toVariant(); - setConfigProperty(artifactModulesCfg, binding.name, value); - outputArtifact->pureProperties.emplace_back(binding.name, value); - } - outputArtifact->properties->setValue(artifactModulesCfg); - if (!outputInfo.newlyCreated && (outputArtifact->fileTags() != outputInfo.oldFileTags - || outputArtifact->properties->value() != outputInfo.oldProperties)) { - invalidateArtifactAsRuleInputIfNecessary(outputArtifact); } } - if (!ruleArtifactArtifactMap.empty()) - engine()->setGlobalObject(prepareScriptContext.prototype()); - m_transformer->setupOutputs(prepareScriptContext); - m_transformer->createCommands(engine(), m_rule->prepareScript, - ScriptEngine::argumentList(Rule::argumentNamesForPrepare(), prepareScriptContext)); + m_transformer->setupOutputs(engine(), prepareScriptContext); + const ScopedJsValueList argList = engine()->argumentList(Rule::argumentNamesForPrepare(), + prepareScriptContext); + m_transformer->createCommands(engine(), m_rule->prepareScript, argList); if (Q_UNLIKELY(m_transformer->commands.empty())) throw ErrorInfo(Tr::tr("There is a rule without commands: %1.") .arg(m_rule->toString()), m_rule->prepareScript.location()); @@ -311,7 +314,7 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p || m_oldTransformer->commands != m_transformer->commands || commandsNeedRerun(m_transformer.get(), m_product.get(), m_productsByName, m_projectsByName)) { - for (Artifact * const output : qAsConst(outputArtifacts)) { + for (Artifact * const output : std::as_const(outputArtifacts)) { output->clearTimestamp(); m_invalidatedArtifacts += output; } @@ -386,12 +389,14 @@ RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifactFromRul FileTags fileTags; bool alwaysUpdated; if (ruleArtifact) { - QScriptValue scriptValue = engine()->evaluate(ruleArtifact->filePath, - ruleArtifact->filePathLocation.filePath(), - ruleArtifact->filePathLocation.line()); - if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) - throw engine()->lastError(scriptValue, ruleArtifact->filePathLocation); - outputPath = scriptValue.toString(); + const ScopedJsValue scriptValue( + jsContext(), + engine()->evaluate(JsValueOwner::Caller, ruleArtifact->filePath, + ruleArtifact->filePathLocation.filePath(), + ruleArtifact->filePathLocation.line())); + if (JsException ex = engine()->checkAndClearException(ruleArtifact->filePathLocation)) + throw ex.toErrorInfo(); + outputPath = getJsString(jsContext(), scriptValue); fileTags = ruleArtifact->fileTags; alwaysUpdated = ruleArtifact->alwaysUpdated; } else { @@ -415,11 +420,7 @@ RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifactFromRul RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifact(const QString &filePath, const FileTags &fileTags, bool alwaysUpdated, const ArtifactSet &inputArtifacts) { - QString outputPath = filePath; - // don't let the output artifact "escape" its build dir - outputPath.replace(StringConstants::dotDot(), QStringLiteral("dotdot")); - outputPath = resolveOutPath(outputPath); - + const QString outputPath = resolveOutPath(filePath); if (m_rule->isDynamic()) { const Set<FileTag> undeclaredTags = fileTags - m_rule->collectedOutputFileTags(); if (!undeclaredTags.empty()) { @@ -514,26 +515,31 @@ public: }; QList<Artifact *> RulesApplicator::runOutputArtifactsScript(const ArtifactSet &inputArtifacts, - const QScriptValueList &args) + const JSValueList &args) { QList<Artifact *> lst; - QScriptValue fun = engine()->evaluate(m_rule->outputArtifactsScript.sourceCode(), - m_rule->outputArtifactsScript.location().filePath(), - m_rule->outputArtifactsScript.location().line()); - if (!fun.isFunction()) + const ScopedJsValue fun(jsContext(), + engine()->evaluate(JsValueOwner::Caller, + m_rule->outputArtifactsScript.sourceCode(), + m_rule->outputArtifactsScript.location().filePath(), + m_rule->outputArtifactsScript.location().line())); + if (!JS_IsFunction(jsContext(), fun)) throw ErrorInfo(QStringLiteral("Function expected."), m_rule->outputArtifactsScript.location()); - QScriptValue res = fun.call(QScriptValue(), args); - engine()->releaseResourcesOfScriptObjects(); - if (engine()->hasErrorOrException(res)) - throw engine()->lastError(res, m_rule->outputArtifactsScript.location()); - if (!res.isArray()) + JSValueList argv(args.begin(), args.end()); + const ScopedJsValue res( + jsContext(), + JS_Call(jsContext(), fun, engine()->globalObject(), int(args.size()), argv.data())); + if (JsException ex = engine()->checkAndClearException(m_rule->outputArtifactsScript.location())) + throw ex.toErrorInfo(); + if (!JS_IsArray(jsContext(), res)) throw ErrorInfo(Tr::tr("Rule.outputArtifacts must return an array of objects."), m_rule->outputArtifactsScript.location()); - const quint32 c = res.property(StringConstants::lengthProperty()).toUInt32(); + const quint32 c = getJsIntProperty(jsContext(), res, StringConstants::lengthProperty()); for (quint32 i = 0; i < c; ++i) { try { - lst.push_back(createOutputArtifactFromScriptValue(res.property(i), inputArtifacts)); + ScopedJsValue val(engine()->context(), JS_GetPropertyUint32(jsContext(), res, i)); + lst.push_back(createOutputArtifactFromScriptValue(val, inputArtifacts)); } catch (const RuleOutputArtifactsException &roae) { ErrorInfo ei = roae; ei.prepend(Tr::tr("Error in Rule.outputArtifacts[%1]").arg(i), @@ -556,6 +562,8 @@ class ArtifactBindingsExtractor QString name; QVariant value; }; + ScriptEngine *m_engine = nullptr; + JSContext *m_ctx = nullptr; std::vector<Entry> m_propertyValues; static Set<QString> getArtifactItemPropertyNames() @@ -570,35 +578,36 @@ class ArtifactBindingsExtractor return s; } - void extractPropertyValues(const QScriptValue &obj, const QString &moduleName = QString()) + void extractPropertyValues(const JSValue &obj, const QString &moduleName = QString()) { - QScriptValueIterator svit(obj); - while (svit.hasNext()) { - svit.next(); - const QString name = svit.name(); + handleJsProperties(m_ctx, obj, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) { + const QString name = getJsString(m_ctx, prop); if (moduleName.isEmpty()) { // Ignore property names that are part of the Artifact item. static const Set<QString> artifactItemPropertyNames = getArtifactItemPropertyNames(); if (artifactItemPropertyNames.contains(name)) - continue; + return; } - const QScriptValue value = svit.value(); - if (value.isObject() && !value.isArray() && !value.isError() && !value.isRegExp()) { + const JSValue value = desc.value; + if (JS_IsObject(value) && !JS_IsArray(m_ctx, value) && !JS_IsError(m_ctx, value) + && !JS_IsRegExp(m_ctx, value)) { QString newModuleName; if (!moduleName.isEmpty()) newModuleName.append(moduleName + QLatin1Char('.')); newModuleName.append(name); extractPropertyValues(value, newModuleName); } else { - m_propertyValues.emplace_back(moduleName, name, value.toVariant()); + m_propertyValues.emplace_back(moduleName, name, getJsVariant(m_ctx, value)); } - } + }); } public: - void apply(Artifact *outputArtifact, const QScriptValue &obj) + void apply(ScriptEngine *engine, Artifact *outputArtifact, const JSValue &obj) { + m_engine = engine; + m_ctx = m_engine->context(); extractPropertyValues(obj); if (m_propertyValues.empty()) return; @@ -614,24 +623,27 @@ public: } }; -Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValue &obj, +Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const JSValue &obj, const ArtifactSet &inputArtifacts) { - if (!obj.isObject()) { + if (!JS_IsObject(obj)) { throw ErrorInfo(Tr::tr("Elements of the Rule.outputArtifacts array must be " "of Object type."), m_rule->outputArtifactsScript.location()); } - const QString unresolvedFilePath - = obj.property(StringConstants::filePathProperty()).toVariant().toString(); + QString unresolvedFilePath; + const ScopedJsValue jsFilePath(jsContext(), getJsProperty(jsContext(), obj, + StringConstants::filePathProperty())); + if (JS_IsString(jsFilePath)) + unresolvedFilePath = getJsString(jsContext(), jsFilePath); if (unresolvedFilePath.isEmpty()) { throw RuleOutputArtifactsException( Tr::tr("Property filePath must be a non-empty string.")); } const QString filePath = FileInfo::resolvePath(m_product->buildDirectory(), unresolvedFilePath); const FileTags fileTags = FileTags::fromStringList( - obj.property(StringConstants::fileTagsProperty()).toVariant().toStringList()); - const QVariant alwaysUpdatedVar - = obj.property(StringConstants::alwaysUpdatedProperty()).toVariant(); + getJsStringListProperty(jsContext(), obj, StringConstants::fileTagsProperty())); + const QVariant alwaysUpdatedVar = getJsVariantProperty(jsContext(), obj, + StringConstants::alwaysUpdatedProperty()); const bool alwaysUpdated = alwaysUpdatedVar.isValid() ? alwaysUpdatedVar.toBool() : true; OutputArtifactInfo outputInfo = createOutputArtifact(filePath, fileTags, alwaysUpdated, inputArtifacts); @@ -642,16 +654,17 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValu "Alternatively, a FileTagger can be provided.") .arg(unresolvedFilePath)); } - const FileTags explicitlyDependsOn = FileTags::fromStringList( - obj.property(StringConstants::explicitlyDependsOnProperty()) - .toVariant().toStringList()); + const FileTags explicitlyDependsOn = FileTags::fromStringList(getJsStringListProperty( + jsContext(), obj, StringConstants::explicitlyDependsOnProperty())); for (const FileTag &tag : explicitlyDependsOn) { for (Artifact * const dependency : m_product->lookupArtifactsByFileTag(tag)) connect(outputInfo.artifact, dependency); } - ArtifactBindingsExtractor().apply(outputInfo.artifact, obj); - if (!outputInfo.newlyCreated && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags - || outputInfo.artifact->properties->value() != outputInfo.oldProperties)) { + ArtifactBindingsExtractor().apply(engine(), outputInfo.artifact, obj); + if (!outputInfo.newlyCreated + && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags + || !qVariantMapsEqual( + outputInfo.artifact->properties->value(), outputInfo.oldProperties))) { invalidateArtifactAsRuleInputIfNecessary(outputInfo.artifact); } return outputInfo.artifact; @@ -659,9 +672,14 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValu QString RulesApplicator::resolveOutPath(const QString &path) const { - QString buildDir = m_product->topLevelProject()->buildDirectory; - QString result = FileInfo::resolvePath(buildDir, path); - result = QDir::cleanPath(result); + const QString buildDir = m_product->topLevelProject()->buildDirectory; + QString result = QDir::cleanPath(FileInfo::resolvePath(buildDir, path)); + if (!result.startsWith(buildDir + QLatin1Char('/'))) { + throw ErrorInfo( + Tr::tr("Refusing to create artifact '%1' outside of build directory '%2'.") + .arg(QDir::toNativeSeparators(result), QDir::toNativeSeparators(buildDir)), + m_rule->prepareScript.location()); + } return result; } @@ -670,15 +688,9 @@ const RulesEvaluationContextPtr &RulesApplicator::evalContext() const return m_product->topLevelProject()->buildData->evaluationContext; } -ScriptEngine *RulesApplicator::engine() const -{ - return evalContext()->engine(); -} - -QScriptValue RulesApplicator::scope() const -{ - return evalContext()->scope(); -} +ScriptEngine *RulesApplicator::engine() const { return evalContext()->engine(); } +JSContext *RulesApplicator::jsContext() const { return engine()->context(); } +JSValue RulesApplicator::scope() const { return evalContext()->scope(); } } // namespace Internal } // namespace qbs |