diff options
Diffstat (limited to 'src/lib/corelib')
296 files changed, 22211 insertions, 16267 deletions
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt new file mode 100644 index 000000000..97b5e6fad --- /dev/null +++ b/src/lib/corelib/CMakeLists.txt @@ -0,0 +1,493 @@ +set(QBS_HEADERS qbs.h) + +set(FILE_UPDATE_SOURCES + changeset.cpp + changeset.h + projectfileupdater.cpp + projectfileupdater.h + qmljsrewriter.cpp + qmljsrewriter.h + ) +list_transform_prepend(FILE_UPDATE_SOURCES api/) + +set(API_SOURCES + internaljobs.cpp + internaljobs.h + jobs.cpp + languageinfo.cpp + project.cpp + project_p.h + projectdata.cpp + projectdata_p.h + propertymap_p.h + rulecommand.cpp + rulecommand_p.h + runenvironment.cpp + transformerdata.cpp + transformerdata_p.h + ) +list_transform_prepend(API_SOURCES api/) + +set(API_HEADERS + jobs.h + languageinfo.h + project.h + projectdata.h + rulecommand.h + runenvironment.h + transformerdata.h + ) +list_transform_prepend(API_HEADERS api/) + +set(BUILD_GRAPH_SOURCES + abstractcommandexecutor.cpp + abstractcommandexecutor.h + artifact.cpp + artifact.h + artifactcleaner.cpp + artifactcleaner.h + artifactsscriptvalue.cpp + artifactsscriptvalue.h + artifactvisitor.cpp + artifactvisitor.h + buildgraph.cpp + buildgraph.h + buildgraphnode.cpp + buildgraphnode.h + buildgraphloader.cpp + buildgraphloader.h + buildgraphvisitor.h + cycledetector.cpp + cycledetector.h + dependencyparametersscriptvalue.cpp + dependencyparametersscriptvalue.h + depscanner.cpp + depscanner.h + emptydirectoriesremover.cpp + emptydirectoriesremover.h + environmentscriptrunner.cpp + environmentscriptrunner.h + executor.cpp + executor.h + executorjob.cpp + executorjob.h + filedependency.cpp + filedependency.h + inputartifactscanner.cpp + inputartifactscanner.h + jscommandexecutor.cpp + jscommandexecutor.h + nodeset.cpp + nodeset.h + nodetreedumper.cpp + nodetreedumper.h + processcommandexecutor.cpp + processcommandexecutor.h + productbuilddata.cpp + productbuilddata.h + productinstaller.cpp + productinstaller.h + projectbuilddata.cpp + projectbuilddata.h + qtmocscanner.cpp + qtmocscanner.h + rawscanneddependency.cpp + rawscanneddependency.h + rawscanresults.cpp + rawscanresults.h + requestedartifacts.cpp + requestedartifacts.h + requesteddependencies.cpp + requesteddependencies.h + rescuableartifactdata.h + rulecommands.cpp + rulecommands.h + rulegraph.cpp + rulegraph.h + rulenode.cpp + rulenode.h + rulesapplicator.cpp + rulesapplicator.h + rulesevaluationcontext.cpp + rulesevaluationcontext.h + timestampsupdater.cpp + timestampsupdater.h + transformer.cpp + transformer.h + transformerchangetracking.cpp + transformerchangetracking.h + ) +list_transform_prepend(BUILD_GRAPH_SOURCES buildgraph/) + +set(BUILD_GRAPH_HEADERS buildgraph/forward_decls.h) + +set(GENERATORS_SOURCES + generatableprojectiterator.cpp + generatableprojectiterator.h + generator.cpp + generatordata.cpp + generatorutils.cpp + generatorutils.h + generatorversioninfo.cpp + generatorversioninfo.h + igeneratableprojectvisitor.h + ixmlnodevisitor.h + xmlproject.cpp + xmlproject.h + xmlprojectwriter.cpp + xmlprojectwriter.h + xmlproperty.cpp + xmlproperty.h + xmlpropertygroup.cpp + xmlpropertygroup.h + xmlworkspace.cpp + xmlworkspace.h + xmlworkspacewriter.cpp + xmlworkspacewriter.h + ) +list_transform_prepend(GENERATORS_SOURCES generators/) + +set(GENERATORS_HEADERS generators/generator.h generators/generatordata.h) + +set(JS_EXTENSIONS_SOURCES + environmentextension.cpp + file.cpp + fileinfoextension.cpp + host.cpp + jsextensions.cpp + jsextensions.h + moduleproperties.cpp + moduleproperties.h + pkgconfigjs.cpp + pkgconfigjs.h + process.cpp + temporarydir.cpp + textfile.cpp + binaryfile.cpp + utilitiesextension.cpp + domxml.cpp + ) +list_transform_prepend(JS_EXTENSIONS_SOURCES jsextensions/) + +if(APPLE) + set(JS_EXTENSIONS_MACOS_SOURCES + propertylist_darwin.mm + propertylistutils.h + propertylistutils.mm + ) +else() + set(JS_EXTENSIONS_MACOS_SOURCES propertylist.cpp) +endif() +list_transform_prepend(JS_EXTENSIONS_MACOS_SOURCES jsextensions/) + +set(LANGUAGE_SOURCES + artifactproperties.cpp + artifactproperties.h + asttools.cpp + asttools.h + builtindeclarations.cpp + builtindeclarations.h + deprecationinfo.h + evaluator.cpp + evaluator.h + filecontext.cpp + filecontext.h + filecontextbase.cpp + filecontextbase.h + filetags.cpp + filetags.h + identifiersearch.cpp + identifiersearch.h + item.cpp + item.h + itemdeclaration.cpp + itemdeclaration.h + itemobserver.h + itempool.cpp + itempool.h + itemtype.h + jsimports.h + language.cpp + language.h + moduleproviderinfo.h + preparescriptobserver.cpp + preparescriptobserver.h + property.cpp + property.h + propertydeclaration.cpp + propertydeclaration.h + propertymapinternal.cpp + propertymapinternal.h + qualifiedid.cpp + qualifiedid.h + resolvedfilecontext.cpp + resolvedfilecontext.h + scriptengine.cpp + scriptengine.h + scriptimporter.cpp + scriptimporter.h + scriptpropertyobserver.cpp + scriptpropertyobserver.h + value.cpp + value.h + ) +list_transform_prepend(LANGUAGE_SOURCES language/) + +set(LANGUAGE_HEADERS language/forward_decls.h) + +set(LOADER_SOURCES + astimportshandler.cpp + astimportshandler.h + astpropertiesitemhandler.cpp + astpropertiesitemhandler.h + dependenciesresolver.cpp + dependenciesresolver.h + groupshandler.cpp + groupshandler.h + itemreader.cpp + itemreader.h + itemreaderastvisitor.cpp + itemreaderastvisitor.h + itemreadervisitorstate.cpp + itemreadervisitorstate.h + loaderutils.cpp + loaderutils.h + localprofiles.cpp + localprofiles.h + moduleinstantiator.cpp + moduleinstantiator.h + moduleloader.cpp + moduleloader.h + modulepropertymerger.cpp + modulepropertymerger.h + moduleproviderloader.cpp + moduleproviderloader.h + probesresolver.cpp + probesresolver.h + productitemmultiplexer.cpp + productitemmultiplexer.h + productresolver.cpp + productresolver.h + productscollector.cpp + productscollector.h + productsresolver.cpp + productsresolver.h + projectresolver.cpp + projectresolver.h + ) +list_transform_prepend(LOADER_SOURCES loader/) + +set(LOGGING_SOURCES + categories.cpp + categories.h + ilogsink.cpp + logger.cpp + logger.h + translator.h + ) +list_transform_prepend(LOGGING_SOURCES logging/) + +set(LOGGING_HEADERS logging/ilogsink.h) + +set(PARSER_SOURCES + 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 + ) +list_transform_prepend(PARSER_SOURCES parser/) + +set(TOOLS_SOURCES + architectures.cpp + buildgraphlocker.cpp + buildgraphlocker.h + buildoptions.cpp + clangclinfo.cpp + clangclinfo.h + cleanoptions.cpp + codelocation.cpp + commandechomode.cpp + deprecationwarningmode.cpp + dynamictypecheck.h + error.cpp + executablefinder.cpp + executablefinder.h + fileinfo.cpp + fileinfo.h + filesaver.cpp + filesaver.h + filetime.cpp + filetime.h + generateoptions.cpp + hostosinfo.h + id.cpp + id.h + iosutils.h + joblimits.cpp + jsliterals.cpp + jsliterals.h + jsonhelper.h + installoptions.cpp + launcherinterface.cpp + launcherinterface.h + launcherpackets.cpp + launcherpackets.h + launchersocket.cpp + launchersocket.h + msvcinfo.cpp + msvcinfo.h + pathutils.h + pimpl.h + persistence.cpp + persistence.h + porting.h + preferences.cpp + processresult.cpp + processresult_p.h + processutils.cpp + processutils.h + profile.cpp + profiling.cpp + profiling.h + progressobserver.cpp + progressobserver.h + projectgeneratormanager.cpp + propagate_const.h + qbsassert.cpp + qbsassert.h + qbspluginmanager.cpp + qbspluginmanager.h + qbsprocess.cpp + qbsprocess.h + qttools.cpp + qttools.h + scannerpluginmanager.cpp + scannerpluginmanager.h + scripttools.cpp + scripttools.h + set.h + settings.cpp + settingscreator.cpp + settingscreator.h + settingsmodel.cpp + settingsrepresentation.cpp + setupprojectparameters.cpp + shellutils.cpp + shellutils.h + stlutils.h + stringconstants.h + stringutils.h + toolchains.cpp + version.cpp + visualstudioversioninfo.cpp + visualstudioversioninfo.h + vsenvironmentdetector.cpp + vsenvironmentdetector.h + weakpointer.h + ) +list_transform_prepend(TOOLS_SOURCES tools/) + +set(TOOLS_HEADERS + architectures.h + buildoptions.h + cleanoptions.h + codelocation.h + commandechomode.h + deprecationwarningmode.h + error.h + generateoptions.h + installoptions.h + joblimits.h + mutexdata.h + preferences.h + processresult.h + profile.h + projectgeneratormanager.h + qbs_export.h + settings.h + settingsmodel.h + settingsrepresentation.h + setupprojectparameters.h + toolchains.h + version.h + ) +list_transform_prepend(TOOLS_HEADERS tools/) + +set(EXTERNAL_DEPENDS "") +if(APPLE) + set(TOOLS_MACOS_SOURCES + applecodesignutils.cpp + applecodesignutils.h + ) + list_transform_prepend(TOOLS_MACOS_SOURCES tools/) + set(EXTERNAL_DEPENDS "-framework Foundation" "-framework Security") +endif() + +if(WIN32) + set(EXTERNAL_DEPENDS "psapi" "shell32") +endif() + +add_qbs_library(qbscore + DEFINES + "QBS_VERSION=\"${QBS_VERSION}\"" + "QBS_RELATIVE_LIBEXEC_PATH=\"${QBS_RELATIVE_LIBEXEC_PATH}\"" + "QBS_LIBRARY" + ${QBS_UNIT_TESTS_DEFINES} + DEPENDS + Qt${QT_VERSION_MAJOR}::CorePrivate + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Xml + Qt6Core5Compat + qbspkgconfig + qbsquickjs + PUBLIC_DEPENDS + Qt${QT_VERSION_MAJOR}::Core + ${EXTERNAL_DEPENDS} + INCLUDES + "${CMAKE_CURRENT_SOURCE_DIR}/../.." + SOURCES + ${QBS_HEADERS} + ${FILE_UPDATE_SOURCES} + ${API_SOURCES} + ${API_HEADERS} + ${BUILD_GRAPH_SOURCES} + ${BUILD_GRAPH_HEADERS} + ${GENERATORS_SOURCES} + ${GENERATORS_HEADERS} + ${JS_EXTENSIONS_SOURCES} + ${JS_EXTENSIONS_MACOS_SOURCES} + ${LANGUAGE_SOURCES} + ${LANGUAGE_HEADERS} + ${LOADER_SOURCES} + ${LOGGING_SOURCES} + ${LOGGING_HEADERS} + ${PARSER_SOURCES} + ${TOOLS_SOURCES} + ${TOOLS_HEADERS} + ${TOOLS_MACOS_SOURCES} + ) + +# not sure if there's a better way to do this +if(INSTALL_PUBLIC_HEADERS) + install(FILES ${QBS_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}) + install(FILES ${API_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/api) + install(FILES ${BUILD_GRAPH_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/buildgraph) + install(FILES ${GENERATORS_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/generators) + install(FILES ${LOGGING_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/logging) + install(FILES ${LANGUAGE_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/language) + install(FILES ${TOOLS_HEADERS} DESTINATION ${QBS_HEADERS_INSTALL_DIR}/tools) +endif() diff --git a/src/lib/corelib/api/api.pri b/src/lib/corelib/api/api.pri deleted file mode 100644 index ddb1171d4..000000000 --- a/src/lib/corelib/api/api.pri +++ /dev/null @@ -1,52 +0,0 @@ -include(../../../install_prefix.pri) - -HEADERS += \ - $$PWD/internaljobs.h \ - $$PWD/projectdata.h \ - $$PWD/runenvironment.h \ - $$PWD/jobs.h \ - $$PWD/languageinfo.h \ - $$PWD/project.h \ - $$PWD/project_p.h \ - $$PWD/propertymap_p.h \ - $$PWD/projectdata_p.h \ - $$PWD/rulecommand.h \ - $$PWD/rulecommand_p.h \ - $$PWD/transformerdata.h \ - $$PWD/transformerdata_p.h - -SOURCES += \ - $$PWD/internaljobs.cpp \ - $$PWD/runenvironment.cpp \ - $$PWD/projectdata.cpp \ - $$PWD/jobs.cpp \ - $$PWD/languageinfo.cpp \ - $$PWD/project.cpp \ - $$PWD/rulecommand.cpp \ - $$PWD/transformerdata.cpp - -!qbs_no_dev_install { - api_headers.files = \ - $$PWD/jobs.h \ - $$PWD/languageinfo.h \ - $$PWD/project.h \ - $$PWD/projectdata.h \ - $$PWD/rulecommand.h \ - $$PWD/runenvironment.h \ - $$PWD/transformerdata.h - api_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/api - INSTALLS += api_headers -} - -qbs_enable_project_file_updates { - HEADERS += \ - $$PWD/changeset.h \ - $$PWD/projectfileupdater.h \ - $$PWD/qmljsrewriter.h - - SOURCES += \ - $$PWD/changeset.cpp \ - $$PWD/projectfileupdater.cpp \ - $$PWD/qmljsrewriter.cpp - DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES -} diff --git a/src/lib/corelib/api/changeset.cpp b/src/lib/corelib/api/changeset.cpp index 5d375fd65..218e18fb2 100644 --- a/src/lib/corelib/api/changeset.cpp +++ b/src/lib/corelib/api/changeset.cpp @@ -39,17 +39,15 @@ #include "changeset.h" -#include <QtGui/qtextcursor.h> - namespace QbsQmlJS { ChangeSet::ChangeSet() - : m_string(nullptr), m_cursor(nullptr), m_error(false) + : m_string(nullptr), m_error(false) { } -ChangeSet::ChangeSet(const QList<EditOp> &operations) - : m_string(nullptr), m_cursor(nullptr), m_operationList(operations), m_error(false) +ChangeSet::ChangeSet(QList<EditOp> operations) + : m_string(nullptr), m_operationList(std::move(operations)), m_error(false) { } @@ -62,9 +60,8 @@ static bool overlaps(int posA, int lengthA, int posB, int lengthB) { || (posA <= posB && posA + lengthA > posB) // A contained in B || (posB < posA && posB + lengthB > posA + lengthA); - } else { - return (posB > posA && posB < posA + lengthA); } + return (posB > posA && posB < posA + lengthA); } bool ChangeSet::hasOverlap(int pos, int length) @@ -131,7 +128,6 @@ QList<ChangeSet::EditOp> ChangeSet::operationList() const void ChangeSet::clear() { m_string = nullptr; - m_cursor = nullptr; m_operationList.clear(); m_error = false; } @@ -274,10 +270,6 @@ void ChangeSet::doReplace(const EditOp &replace_helper, QList<EditOp> *replaceLi 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); } } @@ -348,21 +340,10 @@ void ChangeSet::apply(QString *s) m_string = nullptr; } -void ChangeSet::apply(QTextCursor *textCursor) -{ - m_cursor = textCursor; - apply_helper(); - m_cursor = nullptr; -} - 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 {}; } @@ -380,18 +361,12 @@ void ChangeSet::apply_helper() } // execute replaces - if (m_cursor) - m_cursor->beginEditBlock(); - while (!replaceList.empty()) { const EditOp cmd(replaceList.front()); replaceList.removeFirst(); doReplace(cmd, &replaceList); } - - if (m_cursor) - m_cursor->endEditBlock(); } -} // namespace Internal { +} // namespace QbsQmlJS diff --git a/src/lib/corelib/api/changeset.h b/src/lib/corelib/api/changeset.h index 13d3908d4..9182cb8e4 100644 --- a/src/lib/corelib/api/changeset.h +++ b/src/lib/corelib/api/changeset.h @@ -43,8 +43,6 @@ #include <QtCore/qstring.h> #include <QtCore/qlist.h> -QT_FORWARD_DECLARE_CLASS(QTextCursor) - namespace QbsQmlJS { class ChangeSet @@ -86,7 +84,7 @@ public: public: ChangeSet(); - ChangeSet(const QList<EditOp> &operations); + ChangeSet(QList<EditOp> operations); bool empty() const; @@ -109,7 +107,6 @@ public: bool hadErrors(); void apply(QString *s); - void apply(QTextCursor *textCursor); private: // length-based API. @@ -129,7 +126,6 @@ private: private: QString *m_string; - QTextCursor *m_cursor; QList<EditOp> m_operationList; bool m_error; diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp index 3b68e01b5..35766efa4 100644 --- a/src/lib/corelib/api/internaljobs.cpp +++ b/src/lib/corelib/api/internaljobs.cpp @@ -49,7 +49,8 @@ #include <buildgraph/productinstaller.h> #include <buildgraph/rulesevaluationcontext.h> #include <language/language.h> -#include <language/loader.h> +#include <language/scriptengine.h> +#include <loader/projectresolver.h> #include <logging/logger.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> @@ -59,7 +60,6 @@ #include <tools/preferences.h> #include <tools/qbsassert.h> -#include <QtCore/qeventloop.h> #include <QtCore/qtimer.h> #include <mutex> @@ -77,6 +77,8 @@ public: { std::lock_guard<std::mutex> lock(m_cancelMutex); m_canceled = true; + for (ScriptEngine * const engine : scriptEngines()) + engine->cancel(); } private: @@ -194,14 +196,14 @@ InternalJobThreadWrapper::InternalJobThreadWrapper(InternalJob *synchronousJob, InternalJobThreadWrapper::~InternalJobThreadWrapper() { if (m_running) { - QEventLoop loop; - connect(m_job, &InternalJob::finished, &loop, &QEventLoop::quit); cancel(); - loop.exec(); + while (m_running) + QCoreApplication::processEvents(); } m_thread.quit(); m_thread.wait(); delete m_job; + m_job = nullptr; } void InternalJobThreadWrapper::start() @@ -322,23 +324,21 @@ void InternalSetupProjectJob::execute() void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine) { - Loader loader(engine, logger()); - loader.setSearchPaths(m_parameters.searchPaths()); - loader.setProgressObserver(observer()); - m_newProject = loader.loadProject(m_parameters); + ProjectResolver resolver(m_parameters, engine, logger()); + resolver.setProgressObserver(observer()); + m_newProject = resolver.resolve(); QBS_CHECK(m_newProject); } void InternalSetupProjectJob::resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext) { - TimedActivityLogger resolveLogger(logger(), QStringLiteral("Resolving build project"), timed()); BuildDataResolver(logger()).resolveBuildData(m_newProject, evalContext); } BuildGraphLoadResult InternalSetupProjectJob::restoreProject(const RulesEvaluationContextPtr &evalContext) { BuildGraphLoader bgLoader(logger()); - const BuildGraphLoadResult loadResult + BuildGraphLoadResult loadResult = bgLoader.load(m_existingProject, m_parameters, evalContext); return loadResult; } @@ -351,7 +351,7 @@ BuildGraphTouchingJob::BuildGraphTouchingJob(const Logger &logger, QObject *pare BuildGraphTouchingJob::~BuildGraphTouchingJob() = default; void BuildGraphTouchingJob::setup(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, bool dryRun) + const QVector<ResolvedProductPtr> &products, bool dryRun) { m_project = project; m_products = products; @@ -370,14 +370,14 @@ InternalBuildJob::InternalBuildJob(const Logger &logger, QObject *parent) } void InternalBuildJob::build(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const BuildOptions &buildOptions) + const QVector<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(std::vector<ResolvedProductPtr>(products.cbegin(), products.cend())); + m_executor->setProducts(products); m_executor->setBuildOptions(buildOptions); m_executor->setProgressObserver(observer()); @@ -414,7 +414,8 @@ InternalCleanJob::InternalCleanJob(const Logger &logger, QObject *parent) } void InternalCleanJob::init(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const CleanOptions &options) + const QVector<ResolvedProductPtr> &products, + const CleanOptions &options) { setup(project, products, options.dryRun()); setTimed(options.logElapsedTime()); @@ -442,7 +443,7 @@ InternalInstallJob::InternalInstallJob(const Logger &logger) InternalInstallJob::~InternalInstallJob() = default; void InternalInstallJob::init(const TopLevelProjectPtr &project, - const std::vector<ResolvedProductPtr> &products, const InstallOptions &options) + const QVector<ResolvedProductPtr> &products, const InstallOptions &options) { m_project = project; m_products = products; diff --git a/src/lib/corelib/api/internaljobs.h b/src/lib/corelib/api/internaljobs.h index 58127eb05..d66835941 100644 --- a/src/lib/corelib/api/internaljobs.h +++ b/src/lib/corelib/api/internaljobs.h @@ -151,7 +151,7 @@ class BuildGraphTouchingJob : public InternalJob { Q_OBJECT public: - const QList<ResolvedProductPtr> &products() const { return m_products; } + const QVector<ResolvedProductPtr> &products() const { return m_products; } const TopLevelProjectPtr &project() const { return m_project; } signals: @@ -162,13 +162,13 @@ protected: BuildGraphTouchingJob(const Logger &logger, QObject *parent = nullptr); ~BuildGraphTouchingJob() override; - void setup(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + void setup(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, bool dryRun); void storeBuildGraph(); private: TopLevelProjectPtr m_project; - QList<ResolvedProductPtr> m_products; + QVector<ResolvedProductPtr> m_products; bool m_dryRun; }; @@ -179,7 +179,7 @@ class InternalBuildJob : public BuildGraphTouchingJob public: InternalBuildJob(const Logger &logger, QObject *parent = nullptr); - void build(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + void build(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, const BuildOptions &buildOptions); private: @@ -196,8 +196,8 @@ class InternalCleanJob : public BuildGraphTouchingJob public: InternalCleanJob(const Logger &logger, QObject *parent = nullptr); - void init(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, - const CleanOptions &options); + void init(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, + const CleanOptions &options); private: void start() override; @@ -213,14 +213,14 @@ public: InternalInstallJob(const Logger &logger); ~InternalInstallJob() override; - void init(const TopLevelProjectPtr &project, const std::vector<ResolvedProductPtr> &products, + void init(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, const InstallOptions &options); private: void start() override; TopLevelProjectPtr m_project; - std::vector<ResolvedProductPtr> m_products; + QVector<ResolvedProductPtr> m_products; InstallOptions m_options; }; diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp index 32b7accc7..10c96bfee 100644 --- a/src/lib/corelib/api/jobs.cpp +++ b/src/lib/corelib/api/jobs.cpp @@ -231,7 +231,7 @@ Project SetupProjectJob::project() const { auto const wrapper = qobject_cast<const InternalJobThreadWrapper *>(internalJob()); auto const job = qobject_cast<const InternalSetupProjectJob *>(wrapper->synchronousJob()); - return Project(job->project(), job->logger()); + return {job->project(), job->logger()}; } void SetupProjectJob::resolve(const Project &existingProject, @@ -309,7 +309,7 @@ BuildJob::BuildJob(const Logger &logger, QObject *parent) this, &BuildJob::reportProcessResult); } -void BuildJob::build(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, +void BuildJob::build(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, const BuildOptions &options) { if (!lockProject(project)) @@ -340,7 +340,7 @@ CleanJob::CleanJob(const Logger &logger, QObject *parent) { } -void CleanJob::clean(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, +void CleanJob::clean(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, const qbs::CleanOptions &options) { if (!lockProject(project)) @@ -361,15 +361,14 @@ InstallJob::InstallJob(const Logger &logger, QObject *parent) } void InstallJob::install(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const InstallOptions &options) + const QVector<ResolvedProductPtr> &products, + const InstallOptions &options) { if (!lockProject(project)) return; auto wrapper = qobject_cast<InternalJobThreadWrapper *>(internalJob()); auto installJob = qobject_cast<InternalInstallJob *>(wrapper->synchronousJob()); - installJob->init(project, - std::vector<ResolvedProductPtr>(products.cbegin(), products.cend()), - options); + installJob->init(project, products, options); wrapper->start(); } diff --git a/src/lib/corelib/api/jobs.h b/src/lib/corelib/api/jobs.h index 36c6b7a80..64929489e 100644 --- a/src/lib/corelib/api/jobs.h +++ b/src/lib/corelib/api/jobs.h @@ -136,7 +136,7 @@ private: BuildJob(const Internal::Logger &logger, QObject *parent); void build(const Internal::TopLevelProjectPtr &project, - const QList<qbs::Internal::ResolvedProductPtr> &products, + const QVector<Internal::ResolvedProductPtr> &products, const BuildOptions &options); void handleLauncherError(const ErrorInfo &error); @@ -153,7 +153,7 @@ private: CleanJob(const Internal::Logger &logger, QObject *parent); void clean(const Internal::TopLevelProjectPtr &project, - const QList<Internal::ResolvedProductPtr> &products, const CleanOptions &options); + const QVector<Internal::ResolvedProductPtr> &products, const CleanOptions &options); }; class QBS_EXPORT InstallJob : public AbstractJob @@ -164,7 +164,8 @@ private: InstallJob(const Internal::Logger &logger, QObject *parent); void install(const Internal::TopLevelProjectPtr &project, - const QList<Internal::ResolvedProductPtr> &products, const InstallOptions &options); + const QVector<Internal::ResolvedProductPtr> &products, + const InstallOptions &options); }; } // namespace qbs diff --git a/src/lib/corelib/api/languageinfo.cpp b/src/lib/corelib/api/languageinfo.cpp index cb023927a..f20a68ac3 100644 --- a/src/lib/corelib/api/languageinfo.cpp +++ b/src/lib/corelib/api/languageinfo.cpp @@ -61,29 +61,27 @@ std::string LanguageInfo::qmlTypeInfo() // Individual Components: auto typeNames = builtins.allTypeNames(); typeNames.sort(); - for (const QString &typeName : qAsConst(typeNames)) { - QByteArray utf8TypeName = typeName.toUtf8(); + for (const QString &typeName : std::as_const(typeNames)) { + const auto typeNameString = typeName.toStdString(); result.append(" Component {\n"); - result.append(QByteArray(" name: \"") + utf8TypeName + QByteArray("\"\n")); + result.append(" name: \"" + typeNameString + "\"\n"); result.append(" exports: [ \"qbs/"); - result.append(utf8TypeName); + result.append(typeNameString); result.append(" "); const auto v = builtins.languageVersion(); result.append(QStringLiteral("%1.%2") - .arg(v.majorVersion()).arg(v.minorVersion()).toUtf8()); + .arg(v.majorVersion()).arg(v.minorVersion()).toUtf8().data()); result.append("\" ]\n"); result.append(" prototype: \"QQuickItem\"\n"); Internal::ItemDeclaration itemDecl = builtins.declarationsForType(builtins.typeForName(typeName)); - auto properties = itemDecl.properties(); - std::sort(std::begin(properties), std::end(properties), [] - (const Internal::PropertyDeclaration &a, const Internal::PropertyDeclaration &b) { - return a.name() < b.name(); - }); - for (const Internal::PropertyDeclaration &property : qAsConst(properties)) { + const auto properties = Internal::sorted( + itemDecl.properties(), + [](const auto &lhs, const auto &rhs) { return lhs.name() < rhs.name(); }); + for (const Internal::PropertyDeclaration &property : properties) { result.append(" Property { name: \""); - result.append(property.name().toUtf8()); + result.append(property.name().toUtf8().data()); result.append("\"; "); switch (property.type()) { case qbs::Internal::PropertyDeclaration::UnknownType: diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index d8525a4b6..53c711b49 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -39,13 +39,10 @@ #include "project.h" #include "project_p.h" -#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES -#include "projectfileupdater.h" -#endif - #include "internaljobs.h" #include "jobs.h" #include "projectdata_p.h" +#include "projectfileupdater.h" #include "propertymap_p.h" #include "rulecommand_p.h" #include "runenvironment.h" @@ -64,7 +61,6 @@ #include <buildgraph/timestampsupdater.h> #include <buildgraph/transformer.h> #include <language/language.h> -#include <language/projectresolver.h> #include <language/propertymapinternal.h> #include <logging/logger.h> #include <logging/translator.h> @@ -82,7 +78,7 @@ #include <tools/qttools.h> #include <QtCore/qdir.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qshareddata.h> #include <mutex> @@ -131,22 +127,22 @@ ProjectData ProjectPrivate::projectData() return m_projectData; } -static void addDependencies(QList<ResolvedProductPtr> &products) +static void addDependencies(QVector<ResolvedProductPtr> &products) { for (int i = 0; i < products.size(); ++i) { const ResolvedProductPtr &product = products.at(i); - for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) { + for (const ResolvedProductPtr &dependency : std::as_const(product->dependencies)) { if (!products.contains(dependency)) products.push_back(dependency); } } } -BuildJob *ProjectPrivate::buildProducts(const QList<ResolvedProductPtr> &products, +BuildJob *ProjectPrivate::buildProducts(const QVector<ResolvedProductPtr> &products, const BuildOptions &options, bool needsDepencencyResolving, QObject *jobOwner) { - QList<ResolvedProductPtr> productsToBuild = products; + QVector<ResolvedProductPtr> productsToBuild = products; if (needsDepencencyResolving) addDependencies(productsToBuild); @@ -156,7 +152,7 @@ BuildJob *ProjectPrivate::buildProducts(const QList<ResolvedProductPtr> &product return job; } -CleanJob *ProjectPrivate::cleanProducts(const QList<ResolvedProductPtr> &products, +CleanJob *ProjectPrivate::cleanProducts(const QVector<ResolvedProductPtr> &products, const CleanOptions &options, QObject *jobOwner) { const auto job = new CleanJob(logger, jobOwner); @@ -165,10 +161,10 @@ CleanJob *ProjectPrivate::cleanProducts(const QList<ResolvedProductPtr> &product return job; } -InstallJob *ProjectPrivate::installProducts(const QList<ResolvedProductPtr> &products, +InstallJob *ProjectPrivate::installProducts(const QVector<ResolvedProductPtr> &products, const InstallOptions &options, bool needsDepencencyResolving, QObject *jobOwner) { - QList<ResolvedProductPtr> productsToInstall = products; + QVector<ResolvedProductPtr> productsToInstall = products; if (needsDepencencyResolving) addDependencies(productsToInstall); const auto job = new InstallJob(logger, jobOwner); @@ -177,9 +173,9 @@ InstallJob *ProjectPrivate::installProducts(const QList<ResolvedProductPtr> &pro return job; } -QList<ResolvedProductPtr> ProjectPrivate::internalProducts(const QList<ProductData> &products) const +QVector<ResolvedProductPtr> ProjectPrivate::internalProducts(const QList<ProductData> &products) const { - QList<ResolvedProductPtr> internalProducts; + QVector<ResolvedProductPtr> internalProducts; for (const ProductData &product : products) { if (product.isEnabled()) internalProducts.push_back(internalProduct(product)); @@ -187,20 +183,21 @@ QList<ResolvedProductPtr> ProjectPrivate::internalProducts(const QList<ProductDa return internalProducts; } -static QList<ResolvedProductPtr> enabledInternalProducts(const ResolvedProjectConstPtr &project, +static QVector<ResolvedProductPtr> enabledInternalProducts(const ResolvedProjectConstPtr &project, bool includingNonDefault) { - QList<ResolvedProductPtr> products; + QVector<ResolvedProductPtr> products; for (const ResolvedProductPtr &p : project->products) { if (p->enabled && (includingNonDefault || p->builtByDefault())) products.push_back(p); } - for (const ResolvedProjectConstPtr &subProject : qAsConst(project->subProjects)) + for (const auto &subProject : std::as_const(project->subProjects)) products << enabledInternalProducts(subProject, includingNonDefault); return products; } -QList<ResolvedProductPtr> ProjectPrivate::allEnabledInternalProducts(bool includingNonDefault) const +QVector<ResolvedProductPtr> ProjectPrivate::allEnabledInternalProducts( + bool includingNonDefault) const { return enabledInternalProducts(internalProject, includingNonDefault); } @@ -218,7 +215,7 @@ static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPt if (matches(product, resolvedProduct)) return resolvedProduct; } - for (const ResolvedProjectConstPtr &subProject : qAsConst(project->subProjects)) { + for (const auto &subProject : std::as_const(project->subProjects)) { const ResolvedProductPtr &p = internalProductForProject(subProject, product); if (p) return p; @@ -233,8 +230,7 @@ ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) c ProductData ProjectPrivate::findProductData(const ProductData &product) const { - const auto products = m_projectData.allProducts(); - for (const ProductData &p : products) { + for (const ProductData &p : m_projectData.allProducts()) { if (p.name() == product.name() && p.profile() == product.profile() && p.multiplexConfigurationId() == product.multiplexConfigurationId()) { @@ -247,8 +243,7 @@ ProductData ProjectPrivate::findProductData(const ProductData &product) const QList<ProductData> ProjectPrivate::findProductsByName(const QString &name) const { QList<ProductData> list; - const auto products = m_projectData.allProducts(); - for (const ProductData &p : products) { + for (const ProductData &p : m_projectData.allProducts()) { if (p.name() == name) list.push_back(p); } @@ -257,8 +252,7 @@ QList<ProductData> ProjectPrivate::findProductsByName(const QString &name) const GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const { - const auto groups = product.groups(); - for (const GroupData &g : groups) { + for (const GroupData &g : product.groups()) { if (g.name() == groupName) return g; } @@ -272,17 +266,13 @@ GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup group.d->name = resolvedGroup->name; group.d->prefix = resolvedGroup->prefix; group.d->location = resolvedGroup->location; - for (const SourceArtifactConstPtr &sa : resolvedGroup->files) { + for (const auto &sa : resolvedGroup->files) { ArtifactData artifact = createApiSourceArtifact(sa); setupInstallData(artifact, product); - group.d->sourceArtifacts.push_back(artifact); - } - if (resolvedGroup->wildcards) { - for (const SourceArtifactConstPtr &sa : resolvedGroup->wildcards->files) { - ArtifactData artifact = createApiSourceArtifact(sa); - setupInstallData(artifact, product); + if (sa->fromWildcard) group.d->sourceArtifactsFromWildcards.push_back(artifact); - } + else + group.d->sourceArtifacts.push_back(artifact); } std::sort(group.d->sourceArtifacts.begin(), group.d->sourceArtifacts.end()); @@ -345,7 +335,6 @@ void ProjectPrivate::setupInstallData(ArtifactData &artifact, } } -#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES void ProjectPrivate::addGroup(const ProductData &product, const QString &groupName) { if (groupName.isEmpty()) @@ -355,7 +344,7 @@ void ProjectPrivate::addGroup(const ProductData &product, const QString &groupNa QList<ProductData> products = findProductsByName(product.name()); if (products.empty()) throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); - const QList<ResolvedProductPtr> resolvedProducts = internalProducts(products); + const auto resolvedProducts = internalProducts(products); QBS_CHECK(products.size() == resolvedProducts.size()); for (const GroupPtr &resolvedGroup : resolvedProducts.front()->groups) { @@ -367,28 +356,6 @@ void ProjectPrivate::addGroup(const ProductData &product, const QString &groupNa ProjectFileGroupInserter groupInserter(products.front(), 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()); - - products = findProductsByName(products.front().name()); // These are new objects. - QBS_CHECK(products.size() == resolvedProducts.size()); - for (int i = 0; i < products.size(); ++i) { - const GroupPtr resolvedGroup = ResolvedGroup::create(); - resolvedGroup->location = groupInserter.itemPosition(); - resolvedGroup->enabled = true; - resolvedGroup->name = groupName; - resolvedGroup->properties = resolvedProducts[i]->moduleProperties; - resolvedGroup->overrideTags = false; - resolvedProducts.at(i)->groups << resolvedGroup; - QList<GroupData> &groupData = products.at(i).d->groups; - groupData << createGroupDataFromGroup(resolvedGroup, resolvedProducts.at(i)); - std::sort(groupData.begin(), groupData.end()); - } } ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const ProductData &product, @@ -403,7 +370,7 @@ ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const Product context.resolvedProducts = internalProducts(context.products); const QString groupName = group.isValid() ? group.name() : product.name(); - for (const ResolvedProductPtr &p : qAsConst(context.resolvedProducts)) { + for (const ResolvedProductPtr &p : std::as_const(context.resolvedProducts)) { for (const GroupPtr &g : p->groups) { if (g->name == groupName) { context.resolvedGroups << g; @@ -413,7 +380,7 @@ ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const Product } if (context.resolvedGroups.empty()) throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName)); - for (const ProductData &p : qAsConst(context.products)) { + for (const ProductData &p : std::as_const(context.products)) { const GroupData &g = findGroupData(p, groupName); QBS_CHECK(p.isValid()); context.groups << g; @@ -428,7 +395,7 @@ static bool matchesWildcard(const QString &filePath, const GroupConstPtr &group) { if (!group->wildcards) return false; - for (const QString &pattern : qAsConst(group->wildcards->patterns)) { + for (const QString &pattern : std::as_const(group->wildcards->patterns)) { QString fullPattern; if (QFileInfo(group->prefix).isAbsolute()) { fullPattern = group->prefix; @@ -438,7 +405,7 @@ static bool matchesWildcard(const QString &filePath, const GroupConstPtr &group) } fullPattern.append(QLatin1Char('/')).append(pattern); fullPattern = QDir::cleanPath(fullPattern); - if (QRegExp(fullPattern, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(filePath)) + if (QRegularExpression(QRegularExpression::wildcardToRegularExpression(fullPattern)).match(filePath).hasMatch()) return true; } return false; @@ -484,15 +451,6 @@ ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const P return filesContext; } -static SourceArtifactPtr createSourceArtifact(const QString &filePath, - const ResolvedProductPtr &product, const GroupPtr &group, bool wildcard) -{ - const SourceArtifactPtr artifact - = ProjectResolver::createSourceArtifact(product, filePath, group, wildcard); - ProjectResolver::applyFileTaggers(artifact, product); - return artifact; -} - void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group, const QStringList &filePaths) { @@ -501,9 +459,9 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group // We do not check for entries in other groups, because such doublettes might be legitimate // due to conditions. - for (const GroupPtr &group : qAsConst(groupContext.resolvedGroups)) { - for (const QString &filePath : qAsConst(filesContext.absoluteFilePaths)) { - for (const SourceArtifactConstPtr &sa : group->files) { + for (const GroupPtr &group : std::as_const(groupContext.resolvedGroups)) { + for (const QString &filePath : std::as_const(filesContext.absoluteFilePaths)) { + for (const auto &sa : group->files) { if (sa->absoluteFilePath == filePath) { throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") .arg(filePath, group->name)); @@ -516,57 +474,6 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group group.isValid() ? groupContext.groups.front() : GroupData(), filesContext.relativeFilePaths); adder.apply(); - - m_projectData.d.detach(); - updateInternalCodeLocations(internalProject, adder.itemPosition(), adder.lineOffset()); - updateExternalCodeLocations(m_projectData, adder.itemPosition(), adder.lineOffset()); - - QHash<QString, std::pair<SourceArtifactPtr, ResolvedProductPtr>> addedSourceArtifacts; - for (int i = 0; i < groupContext.resolvedGroups.size(); ++i) { - const ResolvedProductPtr &resolvedProduct = groupContext.resolvedProducts.at(i); - const GroupPtr &resolvedGroup = groupContext.resolvedGroups.at(i); - for (const QString &file : qAsConst(filesContext.absoluteFilePaths)) { - const SourceArtifactPtr sa = createSourceArtifact(file, resolvedProduct, resolvedGroup, - false); - addedSourceArtifacts.insert(file, std::make_pair(sa, resolvedProduct)); - } - for (const QString &file : qAsConst(filesContext.absoluteFilePathsFromWildcards)) { - QBS_CHECK(resolvedGroup->wildcards); - const SourceArtifactPtr sa = createSourceArtifact(file, resolvedProduct, resolvedGroup, - true); - addedSourceArtifacts.insert(file, std::make_pair(sa, resolvedProduct)); - } - if (resolvedProduct->enabled) { - for (const auto &pair : qAsConst(addedSourceArtifacts)) - createArtifact(resolvedProduct, pair.first); - } - } - doSanityChecks(internalProject, logger); - QList<ArtifactData> sourceArtifacts; - QList<ArtifactData> sourceArtifactsFromWildcards; - for (const QString &fp : qAsConst(filesContext.absoluteFilePaths)) { - const auto pair = addedSourceArtifacts.value(fp); - const SourceArtifactConstPtr sa = pair.first; - QBS_CHECK(sa); - ArtifactData artifactData = createApiSourceArtifact(sa); - setupInstallData(artifactData, pair.second); - sourceArtifacts << artifactData; - } - for (const QString &fp : qAsConst(filesContext.absoluteFilePathsFromWildcards)) { - const auto pair = addedSourceArtifacts.value(fp); - const SourceArtifactConstPtr sa = pair.first; - QBS_CHECK(sa); - ArtifactData artifactData = createApiSourceArtifact(sa); - setupInstallData(artifactData, pair.second); - sourceArtifactsFromWildcards << artifactData; - } - for (const GroupData &g : qAsConst(groupContext.groups)) { - g.d->sourceArtifacts << sourceArtifacts; - std::sort(g.d->sourceArtifacts.begin(), g.d->sourceArtifacts.end()); - g.d->sourceArtifactsFromWildcards << sourceArtifactsFromWildcards; - std::sort(g.d->sourceArtifactsFromWildcards.begin(), - g.d->sourceArtifactsFromWildcards.end()); - } } void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group, @@ -595,132 +502,14 @@ void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &gr group.isValid() ? groupContext.groups.front() : GroupData(), filesContext.relativeFilePaths); remover.apply(); - - for (int i = 0; i < groupContext.resolvedProducts.size(); ++i) { - removeFilesFromBuildGraph(groupContext.resolvedProducts.at(i), sourceArtifacts); - for (const SourceArtifactPtr &sa : sourceArtifacts) - removeOne(groupContext.resolvedGroups.at(i)->files, sa); - } - doSanityChecks(internalProject, logger); - - m_projectData.d.detach(); - updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); - updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); - for (const GroupData &g : qAsConst(groupContext.groups)) { - for (int i = g.d->sourceArtifacts.size() - 1; i >= 0; --i) { - if (filesContext.absoluteFilePaths.contains(g.d->sourceArtifacts.at(i).filePath())) - g.d->sourceArtifacts.removeAt(i); - } - } } void ProjectPrivate::removeGroup(const ProductData &product, const GroupData &group) { GroupUpdateContext context = getGroupContext(product, group); - ProjectFileGroupRemover remover(context.products.front(), context.groups.front()); remover.apply(); - for (int i = 0; i < context.resolvedProducts.size(); ++i) { - const ResolvedProductPtr &product = context.resolvedProducts.at(i); - const GroupPtr &group = context.resolvedGroups.at(i); - removeFilesFromBuildGraph(product, group->allFiles()); - const bool removed = removeOne(product->groups, group); - QBS_CHECK(removed); - } - doSanityChecks(internalProject, logger); - - m_projectData.d.detach(); - updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); - updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); - for (int i = 0; i < context.products.size(); ++i) { - const bool removed = context.products.at(i).d->groups.removeOne(context.groups.at(i)); - QBS_CHECK(removed); - } -} -#endif // QBS_ENABLE_PROJECT_FILE_UPDATES - -void ProjectPrivate::removeFilesFromBuildGraph(const ResolvedProductConstPtr &product, - const std::vector<SourceArtifactPtr> &files) -{ - if (!product->enabled) - return; - QBS_CHECK(internalProject->buildData); - ArtifactSet allRemovedArtifacts; - for (const SourceArtifactPtr &sa : files) { - ArtifactSet removedArtifacts; - Artifact * const artifact = lookupArtifact(product, sa->absoluteFilePath); - if (artifact) { // Can be null if the executor has not yet applied the respective rule. - internalProject->buildData->removeArtifactAndExclusiveDependents(artifact, logger, - true, &removedArtifacts); - } - allRemovedArtifacts.unite(removedArtifacts); - } - EmptyDirectoriesRemover(product->topLevelProject(), logger) - .removeEmptyParentDirectories(allRemovedArtifacts); - qDeleteAll(allRemovedArtifacts); -} - -static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation &changeLocation, - int lineOffset) -{ - if (location.filePath() == changeLocation.filePath() - && location.line() >= changeLocation.line()) { - location = CodeLocation(location.filePath(), 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); - for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) - updateInternalCodeLocations(subProject, changeLocation, lineOffset); - for (const ResolvedProductPtr &product : project->products) { - updateLocationIfNecessary(product->location, changeLocation, lineOffset); - for (const GroupPtr &group : product->groups) - updateLocationIfNecessary(group->location, changeLocation, lineOffset); - for (const RulePtr &rule : qAsConst(product->rules)) { - updateLocationIfNecessary(rule->prepareScript.location(), changeLocation, lineOffset); - for (const RuleArtifactPtr &artifact : rule->artifacts) { - for (auto &binding : artifact->bindings) { - updateLocationIfNecessary(binding.location, changeLocation, lineOffset); - } - } - } - for (const ResolvedScannerConstPtr &scanner : product->scanners) { - updateLocationIfNecessary(scanner->searchPathsScript.location(), changeLocation, - lineOffset); - updateLocationIfNecessary(scanner->scanScript.location(), changeLocation, lineOffset); - } - for (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); - const auto subProjects = project.subProjects(); - for (const ProjectData &subProject : subProjects) - updateExternalCodeLocations(subProject, changeLocation, lineOffset); - const auto products = project.products(); - for (const ProductData &product : products) { - updateLocationIfNecessary(product.d->location, changeLocation, lineOffset); - const auto groups = product.groups(); - for (const GroupData &group : groups) - updateLocationIfNecessary(group.d->location, changeLocation, lineOffset); - } } void ProjectPrivate::prepareChangeToProject() @@ -734,7 +523,7 @@ void ProjectPrivate::prepareChangeToProject() RuleCommandList ProjectPrivate::ruleCommandListForTransformer(const Transformer *transformer) { RuleCommandList list; - for (const AbstractCommandPtr &internalCommand : qAsConst(transformer->commands.commands())) { + for (const AbstractCommandPtr &internalCommand : std::as_const(transformer->commands.commands())) { RuleCommand externalCommand; externalCommand.d->description = internalCommand->description(); externalCommand.d->extendedDescription = internalCommand->extendedDescription(); @@ -775,11 +564,11 @@ RuleCommandList ProjectPrivate::ruleCommands(const ProductData &product, QBS_CHECK(resolvedProduct->buildData); const ArtifactSet &outputArtifacts = resolvedProduct->buildData->artifactsByFileTag() .value(FileTag(outputFileTag.toLocal8Bit())); - for (const Artifact * const outputArtifact : qAsConst(outputArtifacts)) { + for (const Artifact * const outputArtifact : std::as_const(outputArtifacts)) { const TransformerConstPtr transformer = outputArtifact->transformer; if (!transformer) continue; - for (const Artifact * const inputArtifact : qAsConst(transformer->inputs)) { + for (const Artifact * const inputArtifact : std::as_const(transformer->inputs)) { if (inputArtifact->filePath() == inputFilePath) return ruleCommandListForTransformer(transformer.get()); } @@ -794,8 +583,7 @@ ProjectTransformerData ProjectPrivate::transformerData() if (!m_projectData.isValid()) retrieveProjectData(m_projectData, internalProject); ProjectTransformerData projectTransformerData; - const auto allProducts = m_projectData.allProducts(); - for (const ProductData &productData : allProducts) { + for (const ProductData &productData : m_projectData.allProducts()) { if (!productData.isEnabled()) continue; const ResolvedProductConstPtr product = internalProduct(productData); @@ -837,7 +625,10 @@ static bool productIsRunnable(const ResolvedProductConstPtr &product) { const bool isBundle = product->moduleProperties->moduleProperty( QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); - return isRunnableArtifact(product->fileTags, isBundle); + const QString androidSdkPackageType = product->moduleProperties->moduleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(product->fileTags, isBundle, isAndroidApk); } static bool productIsMultiplexed(const ResolvedProductConstPtr &product) @@ -851,7 +642,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, projectData.d->name = internalProject->name; projectData.d->location = internalProject->location; projectData.d->enabled = internalProject->enabled; - for (const ResolvedProductConstPtr &resolvedProduct : internalProject->products) { + for (const auto &resolvedProduct : internalProject->products) { ProductData product; product.d->type = resolvedProduct->fileTags.toStringList(); product.d->name = resolvedProduct->name; @@ -895,8 +686,8 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, } } for (const ResolvedProductPtr &resolvedDependentProduct - : qAsConst(resolvedProduct->dependencies)) { - product.d->dependencies << resolvedDependentProduct->name; // FIXME: Shouldn't this be a unique name? + : std::as_const(resolvedProduct->dependencies)) { + product.d->dependencies << resolvedDependentProduct->fullDisplayName(); } std::sort(product.d->type.begin(), product.d->type.end()); std::sort(product.d->groups.begin(), product.d->groups.end()); @@ -904,8 +695,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, product.d->isValid = true; projectData.d->products << product; } - for (const ResolvedProjectConstPtr &internalSubProject - : qAsConst(internalProject->subProjects)) { + for (const auto &internalSubProject : std::as_const(internalProject->subProjects)) { if (!internalSubProject->enabled) continue; ProjectData subProject; @@ -1007,8 +797,14 @@ RunEnvironment Project::getRunEnvironment(const ProductData &product, const QStringList &setupRunEnvConfig, Settings *settings) const { const ResolvedProductPtr resolvedProduct = d->internalProduct(product); - return RunEnvironment(resolvedProduct, d->internalProject, installOptions, environment, - setupRunEnvConfig, settings, d->logger); + return { + resolvedProduct, + d->internalProject, + installOptions, + environment, + setupRunEnvConfig, + settings, + d->logger}; } /*! @@ -1163,7 +959,13 @@ QVariantMap Project::projectConfiguration() const std::set<QString> Project::buildSystemFiles() const { QBS_ASSERT(isValid(), return {}); - return d->internalProject->buildSystemFiles.toStdSet(); + return rangeTo<std::set<QString>>(d->internalProject->buildSystemFiles); +} + +CodeLinks Project::codeLinks() const +{ + QBS_ASSERT(isValid(), return {}); + return d->internalProject->codeLinks; } RuleCommandList Project::ruleCommands(const ProductData &product, @@ -1211,14 +1013,14 @@ Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath, const Internal::TopLevelProjectConstPtr project = BuildGraphLoader::loadProject(bgFilePath); info.bgFilePath = bgFilePath; info.overriddenProperties = project->overriddenValues; - info.profileData = project->profileConfigs; + info.profileData = project->fullProfileConfigsTree(); std::vector<std::pair<QString, QString>> props; for (const QString &prop : requestedProperties) { QStringList components = prop.split(QLatin1Char('.')); const QString propName = components.takeLast(); props.emplace_back(components.join(QLatin1Char('.')), propName); } - for (const ResolvedProductConstPtr &product : project->allProducts()) { + for (const auto &product : project->allProducts()) { if (props.empty()) break; if (product->profile() != project->profile()) @@ -1250,14 +1052,13 @@ Project::BuildGraphInfo Project::getBuildGraphInfo() const throw ErrorInfo(Tr::tr("A job is currently in progress.")); info.bgFilePath = d->internalProject->buildGraphFilePath(); info.overriddenProperties = d->internalProject->overriddenValues; - info.profileData = d->internalProject->profileConfigs; + info.profileData = d->internalProject->fullProfileConfigsTree(); } catch (const ErrorInfo &e) { info.error = e; } return info; } -#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES /*! * \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 @@ -1272,7 +1073,6 @@ ErrorInfo Project::addGroup(const ProductData &product, const QString &groupName QBS_CHECK(isValid()); d->prepareChangeToProject(); d->addGroup(product, groupName); - d->internalProject->lastStartResolveTime = FileTime::currentTime(); d->internalProject->store(d->logger); return {}; } catch (const ErrorInfo &exception) { @@ -1300,7 +1100,6 @@ ErrorInfo Project::addFiles(const ProductData &product, const GroupData &group, QBS_CHECK(isValid()); d->prepareChangeToProject(); d->addFiles(product, group, filePaths); - d->internalProject->lastStartResolveTime = FileTime::currentTime(); d->internalProject->store(d->logger); return {}; } catch (const ErrorInfo &exception) { @@ -1327,7 +1126,6 @@ ErrorInfo Project::removeFiles(const ProductData &product, const GroupData &grou QBS_CHECK(isValid()); d->prepareChangeToProject(); d->removeFiles(product, group, filePaths); - d->internalProject->lastStartResolveTime = FileTime::currentTime(); d->internalProject->store(d->logger); return {}; } catch (const ErrorInfo &exception) { @@ -1349,7 +1147,6 @@ ErrorInfo Project::removeGroup(const ProductData &product, const GroupData &grou QBS_CHECK(isValid()); d->prepareChangeToProject(); d->removeGroup(product, group); - d->internalProject->lastStartResolveTime = FileTime::currentTime(); d->internalProject->store(d->logger); return {}; } catch (const ErrorInfo &exception) { @@ -1359,6 +1156,5 @@ ErrorInfo Project::removeGroup(const ProductData &product, const GroupData &grou return errorInfo; } } -#endif // QBS_ENABLE_PROJECT_FILE_UPDATES } // namespace qbs diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h index 9000d6548..7490eb41e 100644 --- a/src/lib/corelib/api/project.h +++ b/src/lib/corelib/api/project.h @@ -43,6 +43,7 @@ #include "transformerdata.h" #include "../language/forward_decls.h" #include "../tools/error.h" +#include "../tools/porting.h" #include "../tools/qbs_export.h" #include <QtCore/qshareddata.h> @@ -83,7 +84,7 @@ class ProjectPrivate; class QBS_EXPORT Project { friend class SetupProjectJob; - friend uint qHash(const Project &p); + friend QHashValueType qHash(const Project &p); public: SetupProjectJob *setupProject(const SetupProjectParameters ¶meters, ILogSink *logSink, QObject *jobOwner); @@ -135,6 +136,7 @@ public: QVariantMap projectConfiguration() const; std::set<QString> buildSystemFiles() const; + CodeLinks codeLinks() const; RuleCommandList ruleCommands(const ProductData &product, const QString &inputFilePath, const QString &outputFileTag, ErrorInfo *error = nullptr) const; @@ -159,14 +161,12 @@ public: BuildGraphInfo getBuildGraphInfo() const; -#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES 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); -#endif // QBS_ENABLE_PROJECT_FILE_UPDATES private: Project(const Internal::TopLevelProjectPtr &internalProject, const Internal::Logger &logger); @@ -175,7 +175,7 @@ private: }; 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()); } +inline QHashValueType qHash(const Project &p) { return QT_PREPEND_NAMESPACE(qHash)(p.d.data()); } } // namespace qbs diff --git a/src/lib/corelib/api/project_p.h b/src/lib/corelib/api/project_p.h index 58f61a5bc..60c8311f3 100644 --- a/src/lib/corelib/api/project_p.h +++ b/src/lib/corelib/api/project_p.h @@ -62,22 +62,22 @@ namespace Internal { class ProjectPrivate : public QSharedData { public: - ProjectPrivate(const TopLevelProjectPtr &internalProject, const Logger &logger) - : internalProject(internalProject), logger(logger) + ProjectPrivate(TopLevelProjectPtr internalProject, Logger logger) + : internalProject(std::move(internalProject)), logger(std::move(logger)) { } ProjectData projectData(); - BuildJob *buildProducts(const QList<ResolvedProductPtr> &products, const BuildOptions &options, + BuildJob *buildProducts(const QVector<ResolvedProductPtr> &products, const BuildOptions &options, bool needsDepencencyResolving, QObject *jobOwner); - CleanJob *cleanProducts(const QList<ResolvedProductPtr> &products, const CleanOptions &options, + CleanJob *cleanProducts(const QVector<ResolvedProductPtr> &products, const CleanOptions &options, QObject *jobOwner); - InstallJob *installProducts(const QList<ResolvedProductPtr> &products, + InstallJob *installProducts(const QVector<ResolvedProductPtr> &products, const InstallOptions &options, bool needsDepencencyResolving, QObject *jobOwner); - QList<ResolvedProductPtr> internalProducts(const QList<ProductData> &products) const; - QList<ResolvedProductPtr> allEnabledInternalProducts(bool includingNonDefault) const; + QVector<ResolvedProductPtr> internalProducts(const QList<ProductData> &products) const; + QVector<ResolvedProductPtr> allEnabledInternalProducts(bool includingNonDefault) const; ResolvedProductPtr internalProduct(const ProductData &product) const; ProductData findProductData(const ProductData &product) const; QList<ProductData> findProductsByName(const QString &name) const; @@ -92,7 +92,7 @@ public: void setupInstallData(ArtifactData &artifact, const ResolvedProductConstPtr &product); struct GroupUpdateContext { - QList<ResolvedProductPtr> resolvedProducts; + QVector<ResolvedProductPtr> resolvedProducts; QList<GroupPtr> resolvedGroups; QList<ProductData> products; QList<GroupData> groups; @@ -114,12 +114,7 @@ public: 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 std::vector<SourceArtifactPtr> &files); - void updateInternalCodeLocations(const ResolvedProjectPtr &project, - const CodeLocation &changeLocation, int lineOffset); - void updateExternalCodeLocations(const ProjectData &project, - const CodeLocation &changeLocation, int lineOffset); + void prepareChangeToProject(); RuleCommandList ruleCommandListForTransformer(const Transformer *transformer); diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp index 7c64bf6ff..628fe2a5d 100644 --- a/src/lib/corelib/api/projectdata.cpp +++ b/src/lib/corelib/api/projectdata.cpp @@ -40,13 +40,15 @@ #include "projectdata_p.h" #include "propertymap_p.h" +#include <language/builtindeclarations.h> #include <language/language.h> #include <language/propertymapinternal.h> +#include <loader/loaderutils.h> #include <tools/fileinfo.h> #include <tools/jsliterals.h> #include <tools/qbsassert.h> -#include <tools/stringconstants.h> #include <tools/qttools.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qdir.h> @@ -62,9 +64,10 @@ using namespace Internal; template<typename T> static QJsonArray toJsonArray(const QList<T> &list, const QStringList &moduleProperties) { + // We can't use transformed from stlutils.h here as QJsonArray has not the reserve() method. QJsonArray jsonArray; - std::transform(list.begin(), list.end(), std::back_inserter(jsonArray), - [&moduleProperties](const T &v) { return v.toJson(moduleProperties);}); + transform(list, jsonArray, [&moduleProperties](const T &v) { + return v.toJson(moduleProperties);}); return jsonArray; } @@ -72,7 +75,7 @@ static QVariant getModuleProperty(const PropertyMap &properties, const QString & { const int lastDotIndex = fullPropertyName.lastIndexOf(QLatin1Char('.')); if (lastDotIndex == -1) - return QVariant(); + return {}; return properties.getModuleProperty(fullPropertyName.left(lastDotIndex), fullPropertyName.mid(lastDotIndex + 1)); } @@ -218,11 +221,7 @@ bool GroupData::isEnabled() const QStringList GroupData::allFilePaths() const { const QList<ArtifactData> &artifacts = allSourceArtifacts(); - QStringList paths; - paths.reserve(artifacts.size()); - std::transform(artifacts.cbegin(), artifacts.cend(), std::back_inserter(paths), - [](const ArtifactData &sa) { return sa.filePath(); }); - return paths; + return transformed<QStringList>(artifacts, [](const auto &sa) { return sa.filePath(); }); } bool operator!=(const GroupData &lhs, const GroupData &rhs) @@ -323,8 +322,10 @@ bool ArtifactData::isExecutable() const { const bool isBundle = d->properties.getModuleProperty( QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); - return isRunnableArtifact( - FileTags::fromStringList(d->fileTags), isBundle); + const QString androidSdkPackageType = d->properties.getModuleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(FileTags::fromStringList(d->fileTags), isBundle, isAndroidApk); } /*! @@ -530,7 +531,7 @@ QJsonObject ProductData::toJson(const QStringList &propertyNames) const /*! * \brief The product type, which is the list of file tags matching the product's target artifacts. */ -QStringList ProductData::type() const +const QStringList &ProductData::type() const { return d->type; } @@ -538,7 +539,7 @@ QStringList ProductData::type() const /*! * \brief The names of dependent products. */ -QStringList ProductData::dependencies() const +const QStringList &ProductData::dependencies() const { return d->dependencies; } @@ -546,7 +547,7 @@ QStringList ProductData::dependencies() const /*! * \brief The name of the product as given in the qbs source file. */ -QString ProductData::name() const +const QString &ProductData::name() const { return d->name; } @@ -558,13 +559,13 @@ QString ProductData::name() const */ QString ProductData::fullDisplayName() const { - return ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId()); + return fullProductDisplayName(name(), multiplexConfigurationId()); } /*! * \brief The base name of the product's target file as given in the qbs source file. */ -QString ProductData::targetName() const +const QString &ProductData::targetName() const { return d->targetName; } @@ -572,7 +573,7 @@ QString ProductData::targetName() const /*! * \brief The version number of the product. */ -QString ProductData::version() const +const QString &ProductData::version() const { return d->version; } @@ -587,7 +588,7 @@ QString ProductData::profile() const StringConstants::profileProperty()).toString(); } -QString ProductData::multiplexConfigurationId() const +const QString &ProductData::multiplexConfigurationId() const { return d->multiplexConfigurationId; } @@ -595,7 +596,7 @@ QString ProductData::multiplexConfigurationId() const /*! * \brief The location at which the product is defined in the source file. */ -CodeLocation ProductData::location() const +const CodeLocation &ProductData::location() const { return d->location; } @@ -603,7 +604,7 @@ CodeLocation ProductData::location() const /*! * \brief The directory under which the product's generated artifacts are located. */ -QString ProductData::buildDirectory() const +const QString &ProductData::buildDirectory() const { return d->buildDirectory; } @@ -611,7 +612,7 @@ QString ProductData::buildDirectory() const /*! * \brief All artifacts that are generated when building this product. */ -QList<ArtifactData> ProductData::generatedArtifacts() const +const QList<ArtifactData> &ProductData::generatedArtifacts() const { return d->generatedArtifacts; } @@ -620,7 +621,7 @@ QList<ArtifactData> ProductData::generatedArtifacts() const \brief This product's target artifacts. This is a subset of \c generatedArtifacts() */ -QList<ArtifactData> ProductData::targetArtifacts() const +const QList<ArtifactData> ProductData::targetArtifacts() const { QList<ArtifactData> list; std::copy_if(d->generatedArtifacts.cbegin(), d->generatedArtifacts.cend(), @@ -632,17 +633,17 @@ QList<ArtifactData> ProductData::targetArtifacts() const /*! * \brief The list of artifacts in this product that are to be installed. */ -QList<ArtifactData> ProductData::installableArtifacts() const +const QList<ArtifactData> ProductData::installableArtifacts() const { QList<ArtifactData> artifacts; - for (const GroupData &g : qAsConst(d->groups)) { + for (const GroupData &g : std::as_const(d->groups)) { const auto sourceArtifacts = g.allSourceArtifacts(); for (const ArtifactData &a : sourceArtifacts) { if (a.installData().isInstallable()) artifacts << a; } } - for (const ArtifactData &a : qAsConst(d->generatedArtifacts)) { + for (const ArtifactData &a : std::as_const(d->generatedArtifacts)) { if (a.installData().isInstallable()) artifacts << a; } @@ -681,7 +682,7 @@ QString ProductData::targetExecutable() const /*! * \brief The list of \c GroupData in this product. */ -QList<GroupData> ProductData::groups() const +const QList<GroupData> &ProductData::groups() const { return d->groups; } @@ -689,7 +690,7 @@ QList<GroupData> ProductData::groups() const /*! * \brief The product properties. */ -QVariantMap ProductData::properties() const +const QVariantMap &ProductData::properties() const { return d->properties; } @@ -697,7 +698,7 @@ QVariantMap ProductData::properties() const /*! * \brief The set of properties inherited from dependent products and modules. */ -PropertyMap ProductData::moduleProperties() const +const PropertyMap &ProductData::moduleProperties() const { return d->moduleProperties; } @@ -731,21 +732,16 @@ bool operator==(const ProductData &lhs, const ProductData &rhs) if (!lhs.isValid() && !rhs.isValid()) return true; - return lhs.isValid() == rhs.isValid() - && lhs.name() == rhs.name() - && lhs.targetName() == rhs.targetName() - && lhs.type() == rhs.type() - && lhs.version() == rhs.version() - && lhs.dependencies() == rhs.dependencies() - && lhs.profile() == rhs.profile() - && lhs.multiplexConfigurationId() == rhs.multiplexConfigurationId() - && lhs.location() == rhs.location() - && lhs.groups() == rhs.groups() - && lhs.generatedArtifacts() == rhs.generatedArtifacts() - && lhs.properties() == rhs.properties() - && lhs.moduleProperties() == rhs.moduleProperties() - && lhs.isEnabled() == rhs.isEnabled() - && lhs.isMultiplexed() == rhs.isMultiplexed(); + return lhs.isValid() == rhs.isValid() && lhs.name() == rhs.name() + && lhs.targetName() == rhs.targetName() && lhs.type() == rhs.type() + && lhs.version() == rhs.version() && lhs.dependencies() == rhs.dependencies() + && lhs.profile() == rhs.profile() + && lhs.multiplexConfigurationId() == rhs.multiplexConfigurationId() + && lhs.location() == rhs.location() && lhs.groups() == rhs.groups() + && lhs.generatedArtifacts() == rhs.generatedArtifacts() + && qVariantMapsEqual(lhs.properties(), rhs.properties()) + && lhs.moduleProperties() == rhs.moduleProperties() && lhs.isEnabled() == rhs.isEnabled() + && lhs.isMultiplexed() == rhs.isMultiplexed(); } bool operator!=(const ProductData &lhs, const ProductData &rhs) @@ -812,7 +808,7 @@ QJsonObject ProjectData::toJson(const QStringList &moduleProperties) const /*! * \brief The name of this project. */ -QString ProjectData::name() const +const QString &ProjectData::name() const { return d->name; } @@ -820,7 +816,7 @@ QString ProjectData::name() const /*! * \brief The location at which the project is defined in a qbs source file. */ -CodeLocation ProjectData::location() const +const CodeLocation &ProjectData::location() const { return d->location; } @@ -839,7 +835,7 @@ bool ProjectData::isEnabled() const * \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 +const QString &ProjectData::buildDirectory() const { return d->buildDir; } @@ -848,7 +844,7 @@ QString ProjectData::buildDirectory() const * The products in this project. * \note This also includes disabled products. */ -QList<ProductData> ProjectData::products() const +const QList<ProductData> &ProjectData::products() const { return d->products; } @@ -856,7 +852,7 @@ QList<ProductData> ProjectData::products() const /*! * The sub-projects of this project. */ -QList<ProjectData> ProjectData::subProjects() const +const QList<ProjectData> &ProjectData::subProjects() const { return d->subProjects; } @@ -864,10 +860,10 @@ QList<ProjectData> ProjectData::subProjects() const /*! * All products in this projects and its direct and indirect sub-projects. */ -QList<ProductData> ProjectData::allProducts() const +const QList<ProductData> ProjectData::allProducts() const { QList<ProductData> productList = products(); - for (const ProjectData &pd : qAsConst(d->subProjects)) + for (const ProjectData &pd : std::as_const(d->subProjects)) productList << pd.allProducts(); return productList; } @@ -875,7 +871,7 @@ QList<ProductData> ProjectData::allProducts() const /*! * The artifacts of all products in this project that are to be installed. */ -QList<ArtifactData> ProjectData::installableArtifacts() const +const QList<ArtifactData> ProjectData::installableArtifacts() const { QList<ArtifactData> artifacts; const auto products = allProducts(); @@ -953,6 +949,52 @@ QStringList PropertyMap::allProperties() const } /*! + * \brief Returns the names of all modules whose properties can be requested. + */ +QStringList PropertyMap::allModules() const +{ + QStringList modules; + for (auto it = d->m_map->value().constBegin(); it != d->m_map->value().constEnd(); ++it) { + if (it.value().canConvert<QVariantMap>()) + modules << it.key(); + } + return modules; +} + +/*! + * \brief Returns information about all properties of the given module. + */ +QList<PropertyMap::PropertyInfo> PropertyMap::allPropertiesForModule(const QString &module) const +{ + const QVariantMap moduleProps = d->m_map->value().value(module).toMap(); + QList<PropertyInfo> properties; + const auto builtinProps = transformed<QStringList>( + BuiltinDeclarations::instance().declarationsForType(ItemType::Module).properties(), + [](const PropertyDeclaration &decl) { return decl.name(); }); + for (auto it = moduleProps.begin(); it != moduleProps.end(); ++it) { + static const auto getType = [](const QVariant &v) -> QString { + switch (qVariantType(v)) { + case QMetaType::Bool: + return PropertyDeclaration::typeString(PropertyDeclaration::Boolean); + case QMetaType::Int: + return PropertyDeclaration::typeString(PropertyDeclaration::Integer); + case QMetaType::QVariantList: + return PropertyDeclaration::typeString(PropertyDeclaration::VariantList); + case QMetaType::QString: + return PropertyDeclaration::typeString(PropertyDeclaration::String); + case QMetaType::QStringList: + return PropertyDeclaration::typeString(PropertyDeclaration::StringList); + default: + return PropertyDeclaration::typeString(PropertyDeclaration::Variant); + } + }; + properties << PropertyInfo{ + it.key(), getType(it.value()), it.value(), builtinProps.contains(it.key())}; + } + return properties; +} + +/*! * \brief Returns the value of the given property of a product or group. */ QVariant PropertyMap::getProperty(const QString &name) const @@ -987,12 +1029,11 @@ QVariant PropertyMap::getModuleProperty(const QString &moduleName, static QString mapToString(const QVariantMap &map, const QString &prefix) { - QStringList keys(map.keys()); - std::sort(keys.begin(), keys.end()); QString stringRep; - for (const QString &key : qAsConst(keys)) { - const QVariant &val = map.value(key); - if (val.type() == QVariant::Map) { + for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) { + const QString &key = it.key(); + const QVariant &val = it.value(); + if (val.userType() == QMetaType::QVariantMap) { stringRep += mapToString(val.value<QVariantMap>(), prefix + key + QLatin1Char('.')); } else { stringRep += QStringLiteral("%1%2: %3\n") diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h index a285f8570..4b7bc2803 100644 --- a/src/lib/corelib/api/projectdata.h +++ b/src/lib/corelib/api/projectdata.h @@ -74,6 +74,14 @@ class QBS_EXPORT PropertyMap friend QBS_EXPORT bool operator!=(const PropertyMap &, const PropertyMap &); public: + struct PropertyInfo + { + QString name; + QString type; + QVariant value; + bool isBuiltin = false; + }; + PropertyMap(); PropertyMap(const PropertyMap &other); PropertyMap(PropertyMap &&other) Q_DECL_NOEXCEPT; @@ -83,6 +91,8 @@ public: PropertyMap &operator =(PropertyMap &&other) Q_DECL_NOEXCEPT; QStringList allProperties() const; + QStringList allModules() const; + QList<PropertyInfo> allPropertiesForModule(const QString &module) const; QVariant getProperty(const QString &name) const; QStringList getModulePropertiesAsStringList(const QString &moduleName, @@ -198,23 +208,23 @@ public: bool isValid() const; QJsonObject toJson(const QStringList &propertyNames = {}) const; - QStringList type() const; - QStringList dependencies() const; - QString name() const; + const QStringList &type() const; + const QStringList &dependencies() const; + const QString &name() const; QString fullDisplayName() const; - QString targetName() const; - QString version() const; + const QString &targetName() const; + const QString &version() const; QString profile() const; - QString multiplexConfigurationId() const; - CodeLocation location() const; - QString buildDirectory() const; - QList<ArtifactData> generatedArtifacts() const; - QList<ArtifactData> targetArtifacts() const; - QList<ArtifactData> installableArtifacts() const; + const QString &multiplexConfigurationId() const; + const CodeLocation &location() const; + const QString &buildDirectory() const; + const QList<ArtifactData> &generatedArtifacts() const; + const QList<ArtifactData> targetArtifacts() const; + const QList<ArtifactData> installableArtifacts() const; QString targetExecutable() const; - QList<GroupData> groups() const; - QVariantMap properties() const; - PropertyMap moduleProperties() const; + const QList<GroupData> &groups() const; + const QVariantMap &properties() const; + const PropertyMap &moduleProperties() const; bool isEnabled() const; bool isRunnable() const; bool isMultiplexed() const; @@ -241,14 +251,14 @@ public: bool isValid() const; QJsonObject toJson(const QStringList &moduleProperties = {}) const; - QString name() const; - CodeLocation location() const; + const QString &name() const; + const CodeLocation &location() const; bool isEnabled() const; - QString buildDirectory() const; - QList<ProductData> products() const; - QList<ProjectData> subProjects() const; - QList<ProductData> allProducts() const; - QList<ArtifactData> installableArtifacts() const; + const QString &buildDirectory() const; + const QList<ProductData> &products() const; + const QList<ProjectData> &subProjects() const; + const QList<ProductData> allProducts() const; + const QList<ArtifactData> installableArtifacts() const; private: QExplicitlySharedDataPointer<Internal::ProjectDataPrivate> d; diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h index 834aeec23..e241ea92c 100644 --- a/src/lib/corelib/api/projectdata_p.h +++ b/src/lib/corelib/api/projectdata_p.h @@ -123,11 +123,11 @@ public: QString buildDir; }; -inline bool isRunnableArtifact(const FileTags &fileTags, bool isBundle) +inline bool isRunnableArtifact(const FileTags &fileTags, bool isBundle, bool isAndroidApk) { return (fileTags.contains("application") && (!isBundle || fileTags.contains("bundle.content"))) || fileTags.contains("bundle.application-executable") - || fileTags.contains("android.apk") + || (fileTags.contains("android.package") && isAndroidApk) || fileTags.contains("msi"); } diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp index 04f8e630f..604600f8e 100644 --- a/src/lib/corelib/api/projectfileupdater.cpp +++ b/src/lib/corelib/api/projectfileupdater.cpp @@ -66,7 +66,7 @@ namespace Internal { class ItemFinder : public Visitor { public: - ItemFinder(const CodeLocation &cl) : m_cl(cl), m_item(nullptr) { } + ItemFinder(CodeLocation cl) : m_cl(std::move(cl)), m_item(nullptr) { } UiObjectDefinition *item() const { return m_item; } @@ -115,7 +115,7 @@ private: }; -ProjectFileUpdater::ProjectFileUpdater(const QString &projectFile) : m_projectFile(projectFile) +ProjectFileUpdater::ProjectFileUpdater(QString projectFile) : m_projectFile(std::move(projectFile)) { } @@ -182,7 +182,7 @@ void ProjectFileUpdater::apply() if (!parserMessages.empty()) { ErrorInfo errorInfo; errorInfo.append(Tr::tr("Failure parsing project file.")); - for (const DiagnosticMessage &msg : qAsConst(parserMessages)) + for (const DiagnosticMessage &msg : std::as_const(parserMessages)) errorInfo.append(msg.message, toCodeLocation(file.fileName(), msg.loc)); throw errorInfo; } @@ -201,14 +201,18 @@ void ProjectFileUpdater::apply() } -ProjectFileGroupInserter::ProjectFileGroupInserter(const ProductData &product, - const QString &groupName) +ProjectFileGroupInserter::ProjectFileGroupInserter(ProductData product, QString groupName) : ProjectFileUpdater(product.location().filePath()) - , m_product(product) - , m_groupName(groupName) + , m_product(std::move(product)) + , m_groupName(std::move(groupName)) { } +static int extractLine(const QString &fileContent, int pos) +{ + return QStringView{fileContent}.left(pos).count(QLatin1Char('\n')); +} + void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) { ItemFinder itemFinder(m_product.location()); @@ -222,7 +226,7 @@ void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) Rewriter rewriter(fileContent, &changeSet, QStringList()); QString groupItemString; const int productItemIndentation - = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); const int groupItemIndentation = productItemIndentation + 4; const QString groupItemIndentationString = QString(groupItemIndentation, QLatin1Char(' ')); groupItemString += groupItemIndentationString + QLatin1String("Group {\n"); @@ -239,7 +243,7 @@ void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) const ChangeSet::EditOp &insertOp = editOps.front(); setLineOffset(lineOffset); - int insertionLine = fileContent.left(insertOp.pos1).count(QLatin1Char('\n')); + int insertionLine = extractLine(fileContent, insertOp.pos1); for (int i = 0; i < insertOp.text.size() && insertOp.text.at(i) == QLatin1Char('\n'); ++i) ++insertionLine; // To account for newlines prepended by the rewriter. ++insertionLine; // To account for zero-based indexing. @@ -252,7 +256,7 @@ static QString getNodeRepresentation(const QString &fileContent, const QbsQmlJS: { const quint32 start = node->firstSourceLocation().offset; const quint32 end = node->lastSourceLocation().end(); - return fileContent.mid(start, end - start); + return fileContent.mid(start, int(end - start)); } static const ChangeSet::EditOp &getEditOp(const ChangeSet &changeSet) @@ -269,16 +273,16 @@ static int getLineOffsetForChangedBinding(const ChangeSet &changeSet, const QStr static int getBindingLine(const ChangeSet &changeSet, const QString &fileContent) { - return fileContent.left(getEditOp(changeSet).pos1 + 1).count(QLatin1Char('\n')) + 1; + return extractLine(fileContent, getEditOp(changeSet).pos1 + 1) + 1; } -ProjectFileFilesAdder::ProjectFileFilesAdder(const ProductData &product, const GroupData &group, - const QStringList &files) +ProjectFileFilesAdder::ProjectFileFilesAdder(ProductData product, GroupData group, + QStringList files) : ProjectFileUpdater(product.location().filePath()) - , m_product(product) - , m_group(group) - , m_files(files) + , m_product(std::move(product)) + , m_group(std::move(group)) + , m_files(std::move(files)) { } @@ -319,7 +323,7 @@ void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast) } const int itemIndentation - = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); const int bindingIndentation = itemIndentation + 4; const int arrayElemIndentation = bindingIndentation + 4; @@ -405,12 +409,12 @@ void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast) changeSet.apply(&fileContent); } -ProjectFileFilesRemover::ProjectFileFilesRemover(const ProductData &product, const GroupData &group, - const QStringList &files) +ProjectFileFilesRemover::ProjectFileFilesRemover(ProductData product, GroupData group, + QStringList files) : ProjectFileUpdater(product.location().filePath()) - , m_product(product) - , m_group(group) - , m_files(files) + , m_product(std::move(product)) + , m_group(std::move(group)) + , m_files(std::move(files)) { } @@ -444,7 +448,7 @@ void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast) Rewriter rewriter(fileContent, &changeSet, QStringList()); const int itemIndentation - = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); const int bindingIndentation = itemIndentation + 4; const int arrayElemIndentation = bindingIndentation + 4; @@ -472,7 +476,7 @@ void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast) } QString filesString; filesString += QLatin1String("[\n"); - for (const QString &file : qAsConst(newFilesList)) { + for (const QString &file : std::as_const(newFilesList)) { filesString += QString(arrayElemIndentation, QLatin1Char(' ')); filesString += QStringLiteral("\"%1\",\n").arg(file); } @@ -512,10 +516,10 @@ void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast) } -ProjectFileGroupRemover::ProjectFileGroupRemover(const ProductData &product, const GroupData &group) +ProjectFileGroupRemover::ProjectFileGroupRemover(ProductData product, GroupData group) : ProjectFileUpdater(product.location().filePath()) - , m_product(product) - , m_group(group) + , m_product(std::move(product)) + , m_group(std::move(group)) { } diff --git a/src/lib/corelib/api/projectfileupdater.h b/src/lib/corelib/api/projectfileupdater.h index c0d46c747..ded4e08a9 100644 --- a/src/lib/corelib/api/projectfileupdater.h +++ b/src/lib/corelib/api/projectfileupdater.h @@ -61,7 +61,7 @@ public: int lineOffset() const { return m_lineOffset; } protected: - ProjectFileUpdater(const QString &projectFile); + ProjectFileUpdater(QString projectFile); QString projectFile() const { return m_projectFile; } @@ -85,14 +85,14 @@ private: const QString m_projectFile; CodeLocation m_itemPosition; - int m_lineOffset; + int m_lineOffset = 0; }; class ProjectFileGroupInserter : public ProjectFileUpdater { public: - ProjectFileGroupInserter(const ProductData &product, const QString &groupName); + ProjectFileGroupInserter(ProductData product, QString groupName); private: void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; @@ -105,8 +105,7 @@ private: class ProjectFileFilesAdder : public ProjectFileUpdater { public: - ProjectFileFilesAdder(const ProductData &product, const GroupData &group, - const QStringList &files); + ProjectFileFilesAdder(ProductData product, GroupData group, QStringList files); private: void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; @@ -119,8 +118,8 @@ private: class ProjectFileFilesRemover : public ProjectFileUpdater { public: - ProjectFileFilesRemover(const ProductData &product, const GroupData &group, - const QStringList &files); + ProjectFileFilesRemover(ProductData product, GroupData group, + QStringList files); private: void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; @@ -133,7 +132,7 @@ private: class ProjectFileGroupRemover : public ProjectFileUpdater { public: - ProjectFileGroupRemover(const ProductData &product, const GroupData &group); + ProjectFileGroupRemover(ProductData product, GroupData group); private: void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; diff --git a/src/lib/corelib/api/qmljsrewriter.cpp b/src/lib/corelib/api/qmljsrewriter.cpp index 16817d682..d98fe62e0 100644 --- a/src/lib/corelib/api/qmljsrewriter.cpp +++ b/src/lib/corelib/api/qmljsrewriter.cpp @@ -41,10 +41,6 @@ #include <parser/qmljsast_p.h> -#include <QtGui/qtextobject.h> -#include <QtGui/qtextcursor.h> -#include <QtGui/qtextdocument.h> - namespace QbsQmlJS { using namespace AST; @@ -63,12 +59,12 @@ static QString toString(UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Cha } -Rewriter::Rewriter(const QString &originalText, +Rewriter::Rewriter(QString originalText, ChangeSet *changeSet, - const QStringList &propertyOrder) - : m_originalText(originalText) + QStringList propertyOrder) + : m_originalText(std::move(originalText)) , m_changeSet(changeSet) - , m_propertyOrder(propertyOrder) + , m_propertyOrder(std::move(propertyOrder)) { Q_ASSERT(changeSet); } @@ -136,8 +132,7 @@ Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast, break; case ObjectBinding: - newPropertyTemplate = QStringLiteral("%1: %2"); - break; + Q_FALLTHROUGH(); case ScriptBinding: newPropertyTemplate = QStringLiteral("%1: %2"); @@ -192,8 +187,7 @@ UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *memb if (lastObjectDef) return lastObjectDef; - else - return lastNonObjectDef; + return lastNonObjectDef; } UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *members, @@ -225,8 +219,7 @@ UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *member if (lastObjectDef) return lastObjectDef; - else - return lastNonObjectDef; + return lastNonObjectDef; } UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members, @@ -260,7 +253,7 @@ UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *memb idx = propertyOrder.size() - 1; for (; idx > 0; --idx) { - const QString prop = propertyOrder.at(idx - 1); + const QString &prop = propertyOrder.at(idx - 1); UiObjectMemberList *candidate = orderedMembers.value(prop, 0); if (candidate != nullptr) return candidate; @@ -305,7 +298,8 @@ void Rewriter::changeBinding(UiObjectInitializer *ast, break; // for grouped properties: - } else if (!prefix.isEmpty()) { + } + if (!prefix.isEmpty()) { if (auto def = cast<UiObjectDefinition *>(member)) { if (toString(def->qualifiedTypeNameId) == prefix) changeBinding(def->initializer, suffix, newValue, binding); @@ -359,22 +353,20 @@ bool Rewriter::isMatchingPropertyMember(const QString &propertyName, { if (auto publicMember = cast<UiPublicMember*>(member)) return publicMember->name == propertyName; - else if (auto objectBinding = cast<UiObjectBinding*>(member)) + if (auto objectBinding = cast<UiObjectBinding*>(member)) return toString(objectBinding->qualifiedId) == propertyName; - else if (auto scriptBinding = cast<UiScriptBinding*>(member)) + if (auto scriptBinding = cast<UiScriptBinding*>(member)) return toString(scriptBinding->qualifiedId) == propertyName; - else if (auto arrayBinding = cast<UiArrayBinding*>(member)) + if (auto arrayBinding = cast<UiArrayBinding*>(member)) return toString(arrayBinding->qualifiedId) == propertyName; - else - return false; + 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; + return false; } void Rewriter::insertIntoArray(UiArrayBinding *ast, const QString &newValue) @@ -469,7 +461,8 @@ bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, i paragraphFound = true; paragraphSkipped = true; break; - } else if (end == source.length()) { + } + if (end == source.length()) { break; } @@ -500,28 +493,19 @@ bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, i return paragraphFound; } -void Rewriter::includeLeadingEmptyLine(const QString &source, int &start) +void Rewriter::includeLeadingEmptyLine(QStringView source, int &start) { - QTextDocument doc(source); - if (start == 0) return; - if (doc.characterAt(start - 1) != QChar::ParagraphSeparator) + const qsizetype lineEnd = source.lastIndexOf(QChar::LineFeed, start); + if (lineEnd <= 0) 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()) + const qsizetype lineStart = source.lastIndexOf(QChar::LineFeed, lineEnd - 1) + 1; + const auto line = source.mid(lineStart, lineEnd - lineStart); + if (!line.trimmed().isEmpty()) return; - - start = prevBlock.position(); + start = lineStart; } void Rewriter::includeEmptyGroupedProperty(UiObjectDefinition *groupedProperty, UiObjectMember *memberToBeRemoved, int &start, int &end) @@ -649,7 +633,7 @@ Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &con textToInsert += content; m_changeSet->insert(insertionPoint, QLatin1String("\n") + textToInsert); - return Range(insertionPoint, insertionPoint); + return {insertionPoint, insertionPoint}; } Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content) @@ -672,7 +656,7 @@ Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content, m_changeSet->insert(insertionPoint, textToInsert); - return Range(insertionPoint, insertionPoint); + return {insertionPoint, insertionPoint}; } void Rewriter::removeObjectMember(UiObjectMember *member, UiObjectMember *parent) diff --git a/src/lib/corelib/api/qmljsrewriter.h b/src/lib/corelib/api/qmljsrewriter.h index 797b05459..7cba699e2 100644 --- a/src/lib/corelib/api/qmljsrewriter.h +++ b/src/lib/corelib/api/qmljsrewriter.h @@ -60,9 +60,9 @@ public: using Range = ChangeSet::Range; public: - Rewriter(const QString &originalText, + Rewriter(QString originalText, ChangeSet *changeSet, - const QStringList &propertyOrder); + QStringList propertyOrder); Range addBinding(AST::UiObjectInitializer *ast, const QString &propertyName, @@ -97,7 +97,7 @@ public: 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 includeLeadingEmptyLine(QStringView source, int &start); static void includeEmptyGroupedProperty(AST::UiObjectDefinition *groupedProperty, AST::UiObjectMember *memberToBeRemoved, int &start, int &end); private: diff --git a/src/lib/corelib/api/rulecommand.h b/src/lib/corelib/api/rulecommand.h index 438849604..cebcfa949 100644 --- a/src/lib/corelib/api/rulecommand.h +++ b/src/lib/corelib/api/rulecommand.h @@ -44,11 +44,10 @@ #include <QtCore/qlist.h> #include <QtCore/qshareddata.h> +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QProcessEnvironment; -class QString; -class QStringList; QT_END_NAMESPACE namespace qbs { diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp index 94a74dac2..23d0359b0 100644 --- a/src/lib/corelib/api/runenvironment.cpp +++ b/src/lib/corelib/api/runenvironment.cpp @@ -109,15 +109,12 @@ RunEnvironment::RunEnvironment(const ResolvedProductPtr &product, const TopLevelProjectConstPtr &project, const InstallOptions &installOptions, const QProcessEnvironment &environment, const QStringList &setupRunEnvConfig, Settings *settings, const Logger &logger) - : d(new RunEnvironmentPrivate(product, project, installOptions, environment, setupRunEnvConfig, - settings, logger)) + : d(std::make_unique<RunEnvironmentPrivate>(product, project, installOptions, environment, + setupRunEnvConfig, settings, logger)) { } -RunEnvironment::~RunEnvironment() -{ - delete d; -} +RunEnvironment::~RunEnvironment() = default; int RunEnvironment::runShell(ErrorInfo *error) { @@ -228,7 +225,7 @@ int RunEnvironment::doRunShell() static QString findExecutable(const QStringList &fileNames) { const QStringList path = QString::fromLocal8Bit(qgetenv("PATH")) - .split(HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts); for (const QString &fileName : fileNames) { const QString exeFileName = HostOsInfo::appendExecutableSuffix(fileName); @@ -316,13 +313,9 @@ int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arg if (!process.waitForFinished(-1)) { if (process.error() == QProcess::FailedToStart) { throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") - .arg(targetExecutable) - .arg(process.errorString())); - } else { - d->logger.qbsWarning() - << "QProcess error: " << process.errorString(); + .arg(targetExecutable, process.errorString())); } - + d->logger.qbsWarning() << "QProcess error: " << process.errorString(); return EXIT_FAILURE; } @@ -356,8 +349,7 @@ int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arg if (!process.waitForFinished(-1)) { if (process.error() == QProcess::FailedToStart) { throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") - .arg(targetExecutable) - .arg(process.errorString())); + .arg(targetExecutable, process.errorString())); } return EXIT_FAILURE; @@ -371,16 +363,16 @@ int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arg << arguments; } } else { - if (QFileInfo(targetExecutable = findExecutable(QStringList() - << QStringLiteral("iostool"))).isExecutable()) { + if (targetExecutable = findExecutable(QStringList{QStringLiteral("iostool")}); + QFileInfo(targetExecutable).isExecutable()) { targetArguments = QStringList() << QStringLiteral("-run") << QStringLiteral("-bundle") << QDir::cleanPath(bundlePath); if (!arguments.empty()) targetArguments << QStringLiteral("-extra-args") << arguments; - } else if (QFileInfo(targetExecutable = findExecutable(QStringList() - << QStringLiteral("ios-deploy"))).isExecutable()) { + } else if (targetExecutable = findExecutable(QStringList{QStringLiteral("ios-deploy")}); + QFileInfo(targetExecutable).isExecutable()) { targetArguments = QStringList() << QStringLiteral("--no-wifi") << QStringLiteral("--noninteractive") @@ -461,13 +453,9 @@ int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arg } #endif throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") - .arg(targetExecutable) - .arg(errorPrefixString + process.errorString())); - } else { - d->logger.qbsWarning() - << "QProcess error: " << process.errorString(); + .arg(targetExecutable, errorPrefixString + process.errorString())); } - + d->logger.qbsWarning() << "QProcess error: " << process.errorString(); return EXIT_FAILURE; } return process.exitCode(); diff --git a/src/lib/corelib/api/runenvironment.h b/src/lib/corelib/api/runenvironment.h index 69603bf76..4a967ba63 100644 --- a/src/lib/corelib/api/runenvironment.h +++ b/src/lib/corelib/api/runenvironment.h @@ -44,14 +44,17 @@ #include <tools/qbs_export.h> #include <QtCore/qglobal.h> +#include <QtCore/qstringlist.h> + +#include <memory> QT_BEGIN_NAMESPACE class QProcess; class QProcessEnvironment; -class QString; -class QStringList; QT_END_NAMESPACE +class TestApi; + namespace qbs { class ErrorInfo; class InstallOptions; @@ -66,7 +69,7 @@ class QBS_EXPORT RunEnvironment { friend class CommandLineFrontend; friend class Project; - friend class TestApi; + friend class ::TestApi; public: ~RunEnvironment(); @@ -94,7 +97,7 @@ private: const QProcessEnvironment getBuildEnvironment() const; class RunEnvironmentPrivate; - RunEnvironmentPrivate * const d; + const std::unique_ptr<RunEnvironmentPrivate> d; }; } // namespace qbs diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp index 16c3621b6..ee82de43d 100644 --- a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp @@ -60,13 +60,12 @@ AbstractCommandExecutor::AbstractCommandExecutor(Logger logger, QObject *parent) , m_logger(std::move(logger)) { m_watchdog.setSingleShot(true); - connect(&m_watchdog, &QTimer::timeout, - this, [this]() { - cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout.")}); + connect(&m_watchdog, &QTimer::timeout, this, [this]() { + cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout: %1") + .arg(m_command->descriptionForCancelMessage( + m_transformer->product()->fullDisplayName()))}); }); - connect(this, &AbstractCommandExecutor::finished, - &m_watchdog, &QTimer::stop); - + connect(this, &AbstractCommandExecutor::finished, &m_watchdog, &QTimer::stop); } void AbstractCommandExecutor::start(Transformer *transformer, AbstractCommand *cmd) diff --git a/src/lib/corelib/buildgraph/artifact.cpp b/src/lib/corelib/buildgraph/artifact.cpp index e82919560..8f94353e6 100644 --- a/src/lib/corelib/buildgraph/artifact.cpp +++ b/src/lib/corelib/buildgraph/artifact.cpp @@ -51,15 +51,21 @@ namespace qbs { namespace Internal { -Artifact::Artifact() +Artifact::Artifact() : + artifactType(ArtifactType::Unknown), + inputsScanned(false), + timestampRetrieved(false), + alwaysUpdated(false), + oldDataPossiblyPresent(true) { - initialize(); } Artifact::~Artifact() { for (Artifact *p : parentArtifacts()) p->childrenAddedByScanner.remove(this); + if (m_deregister) + m_deregister(this); } void Artifact::accept(BuildGraphVisitor *visitor) @@ -120,15 +126,6 @@ RuleNode *Artifact::producer() const return *ruleNodes.begin(); } -void Artifact::initialize() -{ - artifactType = Unknown; - inputsScanned = false; - timestampRetrieved = false; - alwaysUpdated = true; - oldDataPossiblyPresent = true; -} - const TypeFilter<Artifact> Artifact::parentArtifacts() const { return TypeFilter<Artifact>(parents); diff --git a/src/lib/corelib/buildgraph/artifact.h b/src/lib/corelib/buildgraph/artifact.h index ee3acea59..fce34b6ef 100644 --- a/src/lib/corelib/buildgraph/artifact.h +++ b/src/lib/corelib/buildgraph/artifact.h @@ -50,6 +50,7 @@ #include <QtCore/qstring.h> +#include <functional> #include <utility> #include <vector> @@ -114,7 +115,6 @@ public: bool alwaysUpdated : 1; bool oldDataPossiblyPresent : 1; - void initialize(); const TypeFilter<Artifact> parentArtifacts() const; const TypeFilter<Artifact> childArtifacts() const; void onChildDisconnected(BuildGraphNode *child) override; @@ -124,8 +124,12 @@ public: void load(PersistentPool &pool) override; void store(PersistentPool &pool) override; + using Deregister = std::function<void(const Artifact *)>; + void setDeregister(const Deregister &deregister) { m_deregister = deregister; } + private: FileTags m_fileTags; + Deregister m_deregister; }; template<> inline QString Set<Artifact *>::toString(Artifact * const &artifact) const diff --git a/src/lib/corelib/buildgraph/artifactcleaner.cpp b/src/lib/corelib/buildgraph/artifactcleaner.cpp index 000dfda02..6239a138b 100644 --- a/src/lib/corelib/buildgraph/artifactcleaner.cpp +++ b/src/lib/corelib/buildgraph/artifactcleaner.cpp @@ -162,7 +162,7 @@ ArtifactCleaner::ArtifactCleaner(Logger logger, ProgressObserver *observer) } void ArtifactCleaner::cleanup(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const CleanOptions &options) + const QVector<ResolvedProductPtr> &products, const CleanOptions &options) { m_hasError = false; @@ -181,7 +181,7 @@ void ArtifactCleaner::cleanup(const TopLevelProjectPtr &project, // 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(); + auto dirList = rangeTo<QStringList>(directories); for (int i = 0; i < dirList.size(); ++i) { const QString &dir = dirList.at(i); if (!dir.startsWith(project->buildDirectory)) diff --git a/src/lib/corelib/buildgraph/artifactcleaner.h b/src/lib/corelib/buildgraph/artifactcleaner.h index 5112a75d6..8d0bef275 100644 --- a/src/lib/corelib/buildgraph/artifactcleaner.h +++ b/src/lib/corelib/buildgraph/artifactcleaner.h @@ -54,7 +54,7 @@ class ArtifactCleaner { public: ArtifactCleaner(Logger logger, ProgressObserver *observer); - void cleanup(const TopLevelProjectPtr &project, const QList<ResolvedProductPtr> &products, + void cleanup(const TopLevelProjectPtr &project, const QVector<ResolvedProductPtr> &products, const CleanOptions &options); private: diff --git a/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp index 2adb77d47..6d0ea738f 100644 --- a/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp +++ b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp @@ -45,171 +45,178 @@ #include <language/language.h> #include <language/scriptengine.h> -#include <QtScript/qscriptclass.h> -#include <QtScript/qscriptcontext.h> +#include <tools/stlutils.h> +#include <tools/stringconstants.h> namespace qbs { namespace Internal { -enum BuildGraphScriptValueCommonPropertyKeys : quint32 { - CachedValueKey, - FileTagKey, - ProductPtrKey, -}; - -class ArtifactsScriptClass : public QScriptClass +template<class ProductOrModule> +static bool isRelevantArtifact(const ProductOrModule *productOrModule, const Artifact *artifact) { -public: - ArtifactsScriptClass(QScriptEngine *engine) : QScriptClass(engine) { } - -private: - QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, - QueryFlags flags, uint *id) override - { - getProduct(object); - qbsEngine()->setNonExistingArtifactSetRequested(m_product, name.toString()); - return QScriptClass::queryProperty(object, name, flags, id); - } - - QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override - { - getProduct(object); - qbsEngine()->setArtifactsEnumerated(m_product); - return QScriptClass::newIterator(object); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) { + Q_UNUSED(productOrModule) + return !artifact->isTargetOfModule(); + } else { + return artifact->targetOfModule == productOrModule->name; } - - void getProduct(const QScriptValue &object) - { - if (m_lastObjectId != object.objectId()) { - m_lastObjectId = object.objectId(); - m_product = reinterpret_cast<const ResolvedProduct *>( - object.data().property(ProductPtrKey).toVariant().value<quintptr>()); - } - } - - ScriptEngine *qbsEngine() const { return static_cast<ScriptEngine *>(engine()); } - - qint64 m_lastObjectId = 0; - const ResolvedProduct *m_product = nullptr; -}; - -static bool isRelevantArtifact(const ResolvedProduct *, const Artifact *artifact) -{ - return !artifact->isTargetOfModule(); -} -static bool isRelevantArtifact(const ResolvedModule *module, const Artifact *artifact) -{ - return artifact->targetOfModule == module->name; } -static ArtifactSetByFileTag artifactsMap(const ResolvedProduct *product) +template<class ProductOrModule> +static ArtifactSetByFileTag artifactsMap(const ProductOrModule *productOrModule) { - return product->buildData->artifactsByFileTag(); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + return productOrModule->buildData->artifactsByFileTag(); + else + return artifactsMap(productOrModule->product); } -static ArtifactSetByFileTag artifactsMap(const ResolvedModule *module) +template<class ProductOrModule> static int scriptClassIndex() { - return artifactsMap(module->product); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + return 0; + return 1; } -static QScriptValue createArtifactsObject(const ResolvedProduct *product, ScriptEngine *engine) +template<class ProductOrModule> +std::unique_lock<std::mutex> getArtifactsMapLock(ProductOrModule *productOrModule) { - QScriptClass *scriptClass = engine->artifactsScriptClass(); - if (!scriptClass) { - scriptClass = new ArtifactsScriptClass(engine); - engine->setArtifactsScriptClass(scriptClass); - } - QScriptValue artifactsObj = engine->newObject(scriptClass); - QScriptValue data = engine->newObject(); - QVariant v; - v.setValue<quintptr>(reinterpret_cast<quintptr>(product)); - data.setProperty(ProductPtrKey, engine->newVariant(v)); - artifactsObj.setData(data); - return artifactsObj; + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + return productOrModule->buildData->getArtifactsMapLock(); + else + return getArtifactsMapLock(productOrModule->product); } -static QScriptValue createArtifactsObject(const ResolvedModule *, ScriptEngine *engine) +template<class ProductOrModule> +static bool checkAndSetArtifactsMapUpToDateFlag(const ProductOrModule *productOrModule) { - return engine->newObject(); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + return productOrModule->buildData->checkAndSetJsArtifactsMapUpToDateFlag(); + else + return checkAndSetArtifactsMapUpToDateFlag(productOrModule->product); } -static bool checkAndSetArtifactsMapUpToDateFlag(const ResolvedProduct *p) +// Must be called with artifacts map lock held! +template<class ProductOrModule> +void registerArtifactsMapAccess(ScriptEngine *engine, ProductOrModule *productOrModule) { - return p->buildData->checkAndSetJsArtifactsMapUpToDateFlag(); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) { + if (!checkAndSetArtifactsMapUpToDateFlag(productOrModule)) + engine->setArtifactsMapRequested(productOrModule, true); + else + engine->setArtifactsMapRequested(productOrModule, false); + } else { + registerArtifactsMapAccess(engine, productOrModule->product); + } } -static bool checkAndSetArtifactsMapUpToDateFlag(const ResolvedModule *) { return true; } -static void registerArtifactsMapAccess(const ResolvedProduct *p, ScriptEngine *e, bool forceUpdate) -{ - e->setArtifactsMapRequested(p, forceUpdate); -} -static void registerArtifactsMapAccess(const ResolvedModule *, ScriptEngine *, bool) {} -static void registerArtifactsSetAccess(const ResolvedProduct *p, const FileTag &t, ScriptEngine *e) +template<class ProductOrModule> +static int getArtifactsPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj) { - e->setArtifactSetRequestedForTag(p, t); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto productOrModule = attachedPointer<ProductOrModule>( + obj, engine->artifactsScriptClass(scriptClassIndex<ProductOrModule>())); + const std::unique_lock lock = getArtifactsMapLock(productOrModule); + registerArtifactsMapAccess(engine, productOrModule); + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + engine->setArtifactsEnumerated(productOrModule); + const auto &map = artifactsMap(productOrModule); + const auto filter = [productOrModule](const Artifact *a) { + return isRelevantArtifact(productOrModule, a); + }; + QStringList tags; + for (auto it = map.cbegin(); it != map.cend(); ++it) { + if (any_of(it.value(), filter)) { + tags << it.key().toString(); + } + } + *plen = tags.size(); + if (!tags.isEmpty()) { + *ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab)); + JSPropertyEnum *entry = *ptab; + for (const QString &tag : std::as_const(tags)) { + entry->atom = JS_NewAtom(ctx, tag.toUtf8().constData()); + entry->is_enumerable = 1; + ++entry; + } + } else { + *ptab = nullptr; + } + return 0; } -static void registerArtifactsSetAccess(const ResolvedModule *, const FileTag &, ScriptEngine *) {} -template<class ProductOrModule> static QScriptValue js_artifactsForFileTag( - QScriptContext *ctx, ScriptEngine *engine, const ProductOrModule *productOrModule) +template<class ProductOrModule> +static int getArtifactsProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) { - const FileTag fileTag = FileTag(ctx->callee().property(FileTagKey).toString().toUtf8()); - registerArtifactsSetAccess(productOrModule, fileTag, engine); - QScriptValue result = ctx->callee().property(CachedValueKey); - if (result.isArray()) - return result; - auto artifacts = artifactsMap(productOrModule).value(fileTag); - const auto filter = [productOrModule](const Artifact *a) { + if (!desc) + return 1; + + desc->flags = JS_PROP_ENUMERABLE; + desc->value = desc->getter = desc->setter = JS_UNDEFINED; + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto productOrModule = attachedPointer<ProductOrModule>( + obj, engine->artifactsScriptClass(scriptClassIndex<ProductOrModule>())); + const std::unique_lock lock = getArtifactsMapLock(productOrModule); + registerArtifactsMapAccess(engine, productOrModule); + const QString tagString = getJsString(ctx, prop); + const FileTag fileTag(tagString.toUtf8()); + const auto &map = artifactsMap(productOrModule); + const auto it = map.constFind(fileTag); + if (it == map.constEnd()) { + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + engine->setNonExistingArtifactSetRequested(productOrModule, tagString); + return 1; + } + if constexpr (std::is_same_v<ProductOrModule, ResolvedProduct>) + engine->setArtifactSetRequestedForTag(productOrModule, fileTag); + ArtifactSet artifacts = it.value(); + removeIf(artifacts, [productOrModule](const Artifact *a) { return !isRelevantArtifact(productOrModule, a); - }; - artifacts.erase(std::remove_if(artifacts.begin(), artifacts.end(), filter), artifacts.end()); - result = engine->newArray(uint(artifacts.size())); - ctx->callee().setProperty(CachedValueKey, result); - int k = 0; - for (const Artifact * const artifact : artifacts) - result.setProperty(k++, Transformer::translateFileConfig(engine, artifact, QString())); - return result; + }); + if (!artifacts.empty()) { + desc->value = JS_NewArray(ctx); // TODO: Also cache this list? + int k = 0; + for (Artifact * const artifact : artifacts) { + JS_SetPropertyUint32(ctx, desc->value, k++, + Transformer::translateFileConfig(engine, artifact, QString())); + } + } + return 1; } -template<class ProductOrModule> static QScriptValue js_artifacts( - QScriptContext *ctx, ScriptEngine *engine, const ProductOrModule *productOrModule) +template<class ProductOrModule> static JSValue js_artifacts(JSContext *ctx, + JSValue jsProductOrModule) { - QScriptValue artifactsObj = ctx->callee().property(CachedValueKey); - if (artifactsObj.isObject() && checkAndSetArtifactsMapUpToDateFlag(productOrModule)) { - registerArtifactsMapAccess(productOrModule, engine, false); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto productOrModule = attachedPointer<ProductOrModule>(jsProductOrModule, + engine->dataWithPtrClass()); + JSValue artifactsObj = engine->artifactsMapScriptValue(productOrModule); + if (!JS_IsUndefined(artifactsObj)) return artifactsObj; + const int classIndex = scriptClassIndex<ProductOrModule>(); + JSClassID scriptClass = engine->artifactsScriptClass(classIndex); + if (scriptClass == 0) { + const QByteArray className = "ArtifactsScriptClass" + QByteArray::number(classIndex); + scriptClass = engine->registerClass(className.constData(), nullptr, nullptr, JS_UNDEFINED, + &getArtifactsPropertyNames<ProductOrModule>, + &getArtifactsProperty<ProductOrModule>); + engine->setArtifactsScriptClass(classIndex, scriptClass); } - registerArtifactsMapAccess(productOrModule, engine, true); - artifactsObj = createArtifactsObject(productOrModule, engine); - ctx->callee().setProperty(CachedValueKey, artifactsObj); - const auto &map = artifactsMap(productOrModule); - for (auto it = map.cbegin(); it != map.cend(); ++it) { - const auto filter = [productOrModule](const Artifact *a) { - return isRelevantArtifact(productOrModule, a); - }; - if (std::none_of(it.value().cbegin(), it.value().cend(), filter)) - continue; - QScriptValue fileTagFunc = engine->newFunction(&js_artifactsForFileTag<ProductOrModule>, - productOrModule); - const QString fileTag = it.key().toString(); - fileTagFunc.setProperty(FileTagKey, fileTag); - artifactsObj.setProperty(fileTag, fileTagFunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); - } + artifactsObj = JS_NewObjectClass(engine->context(), scriptClass); + attachPointerTo(artifactsObj, productOrModule); return artifactsObj; } -QScriptValue artifactsScriptValueForProduct(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedProduct *product) +JSValue artifactsScriptValueForProduct(JSContext *ctx, JSValue this_val, int, JSValue *) { - return js_artifacts(ctx, engine, product); + return js_artifacts<ResolvedProduct>(ctx, this_val); } -QScriptValue artifactsScriptValueForModule(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedModule *module) +JSValue artifactsScriptValueForModule(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - return js_artifacts(ctx, engine, module); + return js_artifacts<ResolvedModule>(ctx, this_val); } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/artifactsscriptvalue.h b/src/lib/corelib/buildgraph/artifactsscriptvalue.h index c0da8a05e..dce457758 100644 --- a/src/lib/corelib/buildgraph/artifactsscriptvalue.h +++ b/src/lib/corelib/buildgraph/artifactsscriptvalue.h @@ -41,21 +41,14 @@ #include <language/forward_decls.h> -#include <QtScript/qscriptvalue.h> - -QT_BEGIN_NAMESPACE -class QScriptContext; -QT_END_NAMESPACE +#include <quickjs.h> namespace qbs { namespace Internal { class ScriptEngine; -QScriptValue artifactsScriptValueForProduct(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedProduct *product); - -QScriptValue artifactsScriptValueForModule(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedModule *module); +JSValue artifactsScriptValueForProduct(JSContext *ctx, JSValueConst this_val, int, JSValueConst *); +JSValue artifactsScriptValueForModule(JSContext *ctx, JSValueConst this_val, int, JSValueConst *); } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactvisitor.cpp b/src/lib/corelib/buildgraph/artifactvisitor.cpp index c28f07424..885292f06 100644 --- a/src/lib/corelib/buildgraph/artifactvisitor.cpp +++ b/src/lib/corelib/buildgraph/artifactvisitor.cpp @@ -55,13 +55,13 @@ void ArtifactVisitor::visitProduct(const ResolvedProductConstPtr &product) { if (!product->buildData) return; - for (BuildGraphNode *node : qAsConst(product->buildData->allNodes())) + for (BuildGraphNode *node : std::as_const(product->buildData->allNodes())) node->accept(this); } void ArtifactVisitor::visitProject(const ResolvedProjectConstPtr &project) { - for (const ResolvedProductConstPtr &product : project->allProducts()) + for (const auto &product : project->allProducts()) visitProduct(product); } diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp index 3726c654d..10aae5991 100644 --- a/src/lib/corelib/buildgraph/buildgraph.cpp +++ b/src/lib/corelib/buildgraph/buildgraph.cpp @@ -45,7 +45,6 @@ #include "projectbuilddata.h" #include "productbuilddata.h" #include "rulenode.h" -#include "scriptclasspropertyiterator.h" #include "transformer.h" #include <jsextensions/jsextensions.h> @@ -62,14 +61,14 @@ #include <logging/translator.h> #include <tools/error.h> #include <tools/fileinfo.h> -#include <tools/scripttools.h> #include <tools/qbsassert.h> #include <tools/qttools.h> +#include <tools/scripttools.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qdir.h> #include <QtCore/qfile.h> -#include <QtScript/qscriptclass.h> #include <algorithm> #include <iterator> @@ -82,146 +81,187 @@ static QString childItemsProperty() { return QStringLiteral("childItems"); } static QString exportsProperty() { return QStringLiteral("exports"); } // TODO: Introduce productscriptvalue.{h,cpp}. -static QScriptValue getDataForProductScriptValue(QScriptEngine *engine, - const ResolvedProduct *product) + +static JSValue getDataObject(JSContext *ctx, JSValue obj) { - QScriptValue data = engine->newObject(); - QVariant v; - v.setValue<quintptr>(reinterpret_cast<quintptr>(product)); - data.setProperty(ProductPtrKey, engine->newVariant(v)); - return data; + return getJsProperty(ctx, obj, StringConstants::dataPropertyInternal()); } -class ProductPropertyScriptClass : public QScriptClass +static JSValue getValueFromData(JSContext *ctx, JSValue data, + ModulePropertiesScriptValueCommonPropertyKeys key) { -public: - ProductPropertyScriptClass(QScriptEngine *engine) : QScriptClass(engine) { } - -private: - QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, QueryFlags, - uint *) override - { - if (name == StringConstants::parametersProperty()) { - m_result = object.data().property(DependencyParametersKey); - return HandlesReadAccess; - } - if (name == StringConstants::moduleNameProperty()) { - m_result = object.data().property(ModuleNameKey); - return HandlesReadAccess; - } - if (name == StringConstants::dependenciesProperty() - || name == StringConstants::artifactsProperty() - || name == exportsProperty()) { - // The prototype is not backed by a QScriptClass. - m_result = object.prototype().property(name); - return HandlesReadAccess; - } - - getProduct(object); - QBS_ASSERT(m_product, return {}); - - const auto it = m_product->productProperties.find(name); + return JS_GetPropertyUint32(ctx, data, key); +} - // It is important that we reject unknown property names. Otherwise QtScript will forward - // *everything* to us, including built-in stuff like the hasOwnProperty function. - if (it == m_product->productProperties.cend()) - return {}; +static JSValue getValueFromObject(JSContext *ctx, JSValue obj, + ModulePropertiesScriptValueCommonPropertyKeys key) +{ + const ScopedJsValue data(ctx, getDataObject(ctx, obj)); + return getValueFromData(ctx, data, key); +} - qbsEngine()->addPropertyRequestedInScript(Property(m_product->uniqueName(), QString(), name, - it.value(), Property::PropertyInProduct)); - m_result = qbsEngine()->toScriptValue(it.value()); - return HandlesReadAccess; +void getPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + const QVariantMap &properties, const QStringList &extraPropertyNames, + JSValue extraObject) +{ + JSPropertyEnum *basePTab = nullptr; + uint32_t basePlen = 0; + JS_GetOwnPropertyNames(ctx, &basePTab, &basePlen, extraObject, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY); + *plen = uint32_t(extraPropertyNames.size() + properties.size() + basePlen); + *ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab)); + JSPropertyEnum *entry = *ptab; + for (auto it = properties.begin(); it != properties.end(); ++it, ++entry) { + entry->atom = JS_NewAtom(ctx, it.key().toUtf8().constData()); + entry->is_enumerable = 1; } - - QScriptValue property(const QScriptValue &, const QScriptString &, uint) override - { - return m_result; + for (const QString &prop : extraPropertyNames) { + entry->atom = JS_NewAtom(ctx, prop.toUtf8().constData()); + entry->is_enumerable = 1; + ++entry; + } + for (uint32_t i = 0; i < basePlen; ++i, ++entry) { + entry->atom = basePTab[i].atom; + entry->is_enumerable = 1; + } + js_free(ctx, basePTab); +} + +static int getProductPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj) +{ + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const ScopedJsValue data(ctx, getDataObject(engine->context(), obj)); + const auto product = attachedPointer<ResolvedProduct>( + obj, engine->productPropertyScriptClass()); + QBS_ASSERT(product, return -1); + + // The "moduleName" convenience property is only available for the "main product" in a rule, + // and the "parameters" property exists only for elements of the "dependencies" array for + // which dependency parameters are present. + const auto hasModuleName = [&] { + const ScopedJsValue v(ctx, getValueFromData(ctx, data, ModuleNameKey)); + return JS_IsString(v); + }; + const auto hasDependencyParams = [&] { + const ScopedJsValue v(ctx, getValueFromData(ctx, data, DependencyParametersKey)); + return JS_IsObject(v); + }; + QStringList additionalProperties; + if (hasModuleName()) + additionalProperties.push_back(StringConstants::moduleNameProperty()); + else if (hasDependencyParams()) + additionalProperties.push_back(StringConstants::parametersProperty()); + getPropertyNames(ctx, ptab, plen, product->productProperties, additionalProperties, + engine->baseProductScriptValue(product)); + return 0; +} + +static int getProductProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, + JSAtom prop) +{ + if (desc) { + desc->getter = desc->setter = desc->value = JS_UNDEFINED; + desc->flags = JS_PROP_ENUMERABLE; + } + const QString name = getJsString(ctx, prop); + if (name == StringConstants::parametersProperty()) { + if (desc) + desc->value = getValueFromObject(ctx, obj, DependencyParametersKey); + return 1; + } + if (name == StringConstants::moduleNameProperty()) { + if (desc) + desc->value = getValueFromObject(ctx, obj, ModuleNameKey); + return 1; } - QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override - { - getProduct(object); - QBS_ASSERT(m_product, return nullptr); - - // These two are in the prototype and are thus common to all product script values. - std::vector<QString> additionalProperties({StringConstants::artifactsProperty(), - StringConstants::dependenciesProperty(), - exportsProperty()}); - - // The "moduleName" convenience property is only available for the "main product" in a rule, - // and the "parameters" property exists only for elements of the "dependencies" array for - // which dependency parameters are present. - if (object.data().property(ModuleNameKey).isValid()) - additionalProperties.push_back(StringConstants::moduleNameProperty()); - else if (object.data().property(DependencyParametersKey).isValid()) - additionalProperties.push_back(StringConstants::parametersProperty()); - return new ScriptClassPropertyIterator(object, m_product->productProperties, - additionalProperties); - } - - void getProduct(const QScriptValue &object) - { - if (m_lastObjectId != object.objectId()) { - m_lastObjectId = object.objectId(); - m_product = reinterpret_cast<const ResolvedProduct *>( - object.data().property(ProductPtrKey).toVariant().value<quintptr>()); - } + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto product = attachedPointer<ResolvedProduct>( + obj, engine->productPropertyScriptClass()); + QBS_ASSERT(product, return -1); + + const JSValue baseProductValue = engine->baseProductScriptValue(product); + if (name == exportsProperty()) { + if (desc) + desc->value = getJsProperty(ctx, baseProductValue, name); + engine->addRequestedExport(product); + return 1; } - ScriptEngine *qbsEngine() const { return static_cast<ScriptEngine *>(engine()); } + const auto it = product->productProperties.constFind(name); + if (it == product->productProperties.cend()) { + ScopedJsValue v(ctx, JS_GetProperty(ctx, baseProductValue, prop)); + const int ret = JS_IsUndefined(v) ? 0 : 1; + if (desc) + desc->value = v.release(); + return ret; + } - qint64 m_lastObjectId = 0; - const ResolvedProduct *m_product = nullptr; - QScriptValue m_result; -}; + engine->addPropertyRequestedInScript(Property(product->uniqueName(), QString(), name, + it.value(), Property::PropertyInProduct)); + if (desc) + desc->value = engine->toScriptValue(it.value()); + return 1; +} -static QScriptValue setupProjectScriptValue(ScriptEngine *engine, - const ResolvedProjectConstPtr &project) +static JSValue setupProjectScriptValue(ScriptEngine *engine, const ResolvedProjectConstPtr &project) { - QScriptValue &obj = engine->projectScriptValue(project.get()); - if (obj.isValid()) - return obj; + JSValue &obj = engine->projectScriptValue(project.get()); + if (JS_IsObject(obj)) + return JS_DupValue(engine->context(), obj); obj = engine->newObject(); - obj.setProperty(StringConstants::filePathProperty(), project->location.filePath()); - obj.setProperty(StringConstants::pathProperty(), FileInfo::path(project->location.filePath())); + setJsProperty(engine->context(), obj, StringConstants::filePathProperty(), + project->location.filePath()); + setJsProperty(engine->context(), obj, StringConstants::pathProperty(), + FileInfo::path(project->location.filePath())); const QVariantMap &projectProperties = project->projectProperties(); for (QVariantMap::const_iterator it = projectProperties.begin(); it != projectProperties.end(); ++it) { - engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value())); + const ScopedJsValue val(engine->context(), engine->toScriptValue(it.value())); + engine->setObservedProperty(obj, it.key(), val); } - engine->observer()->addProjectObjectId(obj.objectId(), project->name); - return obj; + engine->observer()->addProjectObjectId(jsObjectId(obj), project->name); + return JS_DupValue(engine->context(), obj); } -static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product); +static void setupBaseProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product); class DependenciesFunction { public: - DependenciesFunction(ScriptEngine *engine) - : m_engine(engine) - { - } + DependenciesFunction(ScriptEngine *engine) : m_engine(engine) { } - void init(QScriptValue &productScriptValue, QScriptValue &exportsScriptValue, - const ResolvedProduct *product) + void init(JSValue &productScriptValue, JSValue exportsScriptValue) { - QScriptValue depfunc = m_engine->newFunction(&js_internalProductDependencies, product); - productScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); - depfunc = m_engine->newFunction(&js_exportedProductDependencies, product); - exportsScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); + const QByteArray name = StringConstants::dependenciesProperty().toUtf8(); + const ScopedJsValue depfunc( + m_engine->context(), + JS_NewCFunction(m_engine->context(), &js_internalProductDependencies, + name.constData(), 0)); + const ScopedJsAtom depAtom(m_engine->context(), name); + JS_DefineProperty(m_engine->context(), productScriptValue, depAtom, JS_UNDEFINED, depfunc, + JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + const ScopedJsValue exportedDepfunc( + m_engine->context(), + JS_NewCFunction(m_engine->context(), &js_exportedProductDependencies, + name.constData(), 0)); + JS_DefineProperty(m_engine->context(), exportsScriptValue, depAtom, JS_UNDEFINED, + exportedDepfunc, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); } private: enum class DependencyType { Internal, Exported }; - static QScriptValue js_productDependencies(QScriptContext *, ScriptEngine *engine, - const ResolvedProduct *product, DependencyType depType) + static JSValue js_productDependencies(const ResolvedProduct *product, + JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv, DependencyType depType) { - QScriptValue result = engine->newArray(); + Q_UNUSED(this_val) + Q_UNUSED(argc) + Q_UNUSED(argv) + + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + JSValue result = JS_NewArray(ctx); quint32 idx = 0; const bool exportCase = depType == DependencyType::Exported; std::vector<ResolvedProductPtr> productDeps; @@ -243,202 +283,234 @@ private: } else { productDeps = product->dependencies; } - for (const ResolvedProductPtr &dependency : qAsConst(productDeps)) { - QScriptValue obj = engine->newObject(engine->productPropertyScriptClass()); - obj.setPrototype(setupProductScriptValue(static_cast<ScriptEngine *>(engine), - dependency.get())); + for (const ResolvedProductPtr &dependency : std::as_const(productDeps)) { + setupBaseProductScriptValue(engine, dependency.get()); + JSValue obj = JS_NewObjectClass(engine->context(), + engine->productPropertyScriptClass()); + attachPointerTo(obj, dependency.get()); const QVariantMap ¶ms = (exportCase ? product->exportedModule.dependencyParameters.value(dependency) : product->dependencyParameters.value(dependency)); - QScriptValue data = getDataForProductScriptValue(engine, dependency.get()); - data.setProperty(DependencyParametersKey, dependencyParametersValue( - product->uniqueName(), dependency->name, params, engine)); - obj.setData(data); - result.setProperty(idx++, obj); + JSValue data = JS_NewObjectClass(engine->context(), engine->dataWithPtrClass()); + JS_SetPropertyUint32(ctx, data, DependencyParametersKey, dependencyParametersValue( + product->uniqueName(), dependency->name, params, engine)); + defineJsProperty(ctx, obj, StringConstants::dataPropertyInternal(), data); + JS_SetPropertyUint32(ctx, result, idx++, obj); } if (exportCase) { for (const ExportedModuleDependency &m : product->exportedModule.moduleDependencies) { - QScriptValue obj = engine->newObject(); - obj.setProperty(StringConstants::nameProperty(), m.name); - QScriptValue exportsValue = engine->newObject(); - obj.setProperty(exportsProperty(), exportsValue); - exportsValue.setProperty(StringConstants::dependenciesProperty(), - engine->newArray()); + JSValue obj = engine->newObject(); + setJsProperty(ctx, obj, StringConstants::nameProperty(), m.name); + JSValue exportsValue = engine->newObject(); + setJsProperty(ctx, obj, exportsProperty(), exportsValue); + setJsProperty(ctx, exportsValue, StringConstants::dependenciesProperty(), + JS_NewArray(ctx)); for (auto modIt = m.moduleProperties.begin(); modIt != m.moduleProperties.end(); ++modIt) { const QVariantMap entries = modIt.value().toMap(); if (entries.empty()) continue; - QScriptValue moduleObj = engine->newObject(); - ModuleProperties::setModuleScriptValue(exportsValue, moduleObj, modIt.key()); - for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) - moduleObj.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); + JSValue moduleObj = engine->newObject(); + ModuleProperties::setModuleScriptValue(engine, exportsValue, moduleObj, + modIt.key()); + for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) { + setJsProperty(ctx, moduleObj, valIt.key(), + engine->toScriptValue(valIt.value())); + } } - result.setProperty(idx++, obj); + JS_SetPropertyUint32(ctx, result, idx++, obj); } return result; } - for (const ResolvedModuleConstPtr &dependency : product->modules) { + for (const auto &dependency : product->modules) { if (dependency->isProduct) continue; - QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass()); - obj.setPrototype(engine->moduleScriptValuePrototype(dependency.get())); - - // The prototype must exist already, because we set it up for all modules - // of the product in ModuleProperties::init(). - QBS_ASSERT(obj.prototype().isValid(), ;); - + JSValue obj = JS_NewObjectClass(ctx, engine->modulePropertyScriptClass()); + attachPointerTo(obj, dependency.get()); const QVariantMap ¶ms = product->moduleParameters.value(dependency); - QScriptValue data = getDataForModuleScriptValue(engine, product, nullptr, - dependency.get()); - data.setProperty(DependencyParametersKey, dependencyParametersValue( - product->uniqueName(), dependency->name, params, engine)); - obj.setData(data); - result.setProperty(idx++, obj); + JSValue data = createDataForModuleScriptValue(engine, nullptr); + JS_SetPropertyUint32(ctx, data, DependencyParametersKey, dependencyParametersValue( + product->uniqueName(), dependency->name, params, engine)); + defineJsProperty(ctx, obj, StringConstants::dataPropertyInternal(), data); + JS_SetPropertyUint32(ctx, result, idx++, obj); } return result; } - static QScriptValue js_internalProductDependencies(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedProduct * const product) + static JSValue js_internalProductDependencies(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto product = attachedPointer<ResolvedProduct>(this_val, engine->dataWithPtrClass()); engine->addDependenciesArrayRequested(product); - return js_productDependencies(ctx, engine, product, DependencyType::Internal); + return js_productDependencies(product, ctx, this_val, argc, argv, + DependencyType::Internal); } - static QScriptValue js_exportedProductDependencies(QScriptContext *ctx, ScriptEngine *engine, - const ResolvedProduct * const product) + static JSValue js_exportedProductDependencies(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { - return js_productDependencies(ctx, engine, product, DependencyType::Exported); + const auto product = attachedPointer<ResolvedProduct>( + this_val, ScriptEngine::engineForContext(ctx)->dataWithPtrClass()); + return js_productDependencies(product, ctx, this_val, argc, argv, DependencyType::Exported); } ScriptEngine *m_engine; }; -static QScriptValue setupExportedPropertyScriptValue(const ExportedProperty &property, +static JSValue setupExportedPropertyScriptValue(const ExportedProperty &property, ScriptEngine *engine) { - QScriptValue propertyScriptValue = engine->newObject(); - propertyScriptValue.setProperty(StringConstants::nameProperty(), property.fullName); - propertyScriptValue.setProperty(StringConstants::typeProperty(), - PropertyDeclaration::typeString(property.type)); - propertyScriptValue.setProperty(StringConstants::sourceCodeProperty(), property.sourceCode); - propertyScriptValue.setProperty(QStringLiteral("isBuiltin"), property.isBuiltin); + JSValue propertyScriptValue = engine->newObject(); + setJsProperty(engine->context(), propertyScriptValue, StringConstants::nameProperty(), + property.fullName); + setJsProperty(engine->context(), propertyScriptValue, StringConstants::typeProperty(), + PropertyDeclaration::typeString(property.type)); + setJsProperty(engine->context(), propertyScriptValue, StringConstants::sourceCodeProperty(), + property.sourceCode); + JS_SetPropertyStr(engine->context(), propertyScriptValue, "isBuiltin", + JS_NewBool(engine->context(), property.isBuiltin)); return propertyScriptValue; } -static void setupExportedPropertiesScriptValue(QScriptValue &parentObject, +static void setupExportedPropertiesScriptValue(JSValue &parentObject, const std::vector<ExportedProperty> &properties, ScriptEngine *engine) { - QScriptValue propertiesScriptValue = engine->newArray(static_cast<uint>(properties.size())); - parentObject.setProperty(QStringLiteral("properties"), propertiesScriptValue); + JSValue propertiesScriptValue = JS_NewArray(engine->context()); quint32 arrayIndex = 0; for (const ExportedProperty &p : properties) { - propertiesScriptValue.setProperty(arrayIndex++, - setupExportedPropertyScriptValue(p, engine)); + JS_SetPropertyUint32(engine->context(), propertiesScriptValue, arrayIndex++, + setupExportedPropertyScriptValue(p, engine)); } + JS_SetPropertyStr(engine->context(), parentObject, "properties", propertiesScriptValue); } -static QScriptValue setupExportedItemScriptValue(const ExportedItem *item, ScriptEngine *engine) +static JSValue setupExportedItemScriptValue(const ExportedItem *item, ScriptEngine *engine) { - QScriptValue itemScriptValue = engine->newObject(); - itemScriptValue.setProperty(StringConstants::nameProperty(), item->name); + JSValue itemScriptValue = engine->newObject(); + setJsProperty(engine->context(), itemScriptValue, StringConstants::nameProperty(), item->name); setupExportedPropertiesScriptValue(itemScriptValue, item->properties, engine); - QScriptValue childrenScriptValue = engine->newArray(static_cast<uint>(item->children.size())); - itemScriptValue.setProperty(childItemsProperty(), childrenScriptValue); + JSValue childrenScriptValue = JS_NewArray(engine->context()); quint32 arrayIndex = 0; for (const auto &childItem : item->children) { - childrenScriptValue.setProperty(arrayIndex++, - setupExportedItemScriptValue(childItem.get(), engine)); + JS_SetPropertyUint32(engine->context(), childrenScriptValue, arrayIndex++, + setupExportedItemScriptValue(childItem.get(), engine)); } + setJsProperty(engine->context(), itemScriptValue, childItemsProperty(), childrenScriptValue); return itemScriptValue; } -static QScriptValue setupExportsScriptValue(const ExportedModule &module, ScriptEngine *engine) +static JSValue setupExportsScriptValue(const ResolvedProduct *product, ScriptEngine *engine) { - QScriptValue exportsScriptValue = engine->newObject(); - for (auto it = module.propertyValues.cbegin(); it != module.propertyValues.cend(); ++it) - exportsScriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); + const ExportedModule &module = product->exportedModule; + JSValue exportsScriptValue = JS_NewObjectClass(engine->context(), engine->dataWithPtrClass()); + attachPointerTo(exportsScriptValue, product); + for (auto it = module.propertyValues.cbegin(); it != module.propertyValues.cend(); ++it) { + setJsProperty(engine->context(), exportsScriptValue, it.key(), + engine->toScriptValue(it.value())); + } setupExportedPropertiesScriptValue(exportsScriptValue, module.m_properties, engine); - QScriptValue childrenScriptValue = engine->newArray(static_cast<uint>(module.children.size())); - exportsScriptValue.setProperty(childItemsProperty(), childrenScriptValue); + JSValue childrenScriptValue = JS_NewArray(engine->context()); quint32 arrayIndex = 0; for (const auto &exportedItem : module.children) { - childrenScriptValue.setProperty(arrayIndex++, - setupExportedItemScriptValue(exportedItem.get(), engine)); + JS_SetPropertyUint32(engine->context(), childrenScriptValue, arrayIndex++, + setupExportedItemScriptValue(exportedItem.get(), engine)); } - QScriptValue importsScriptValue = engine->newArray(module.importStatements.size()); - exportsScriptValue.setProperty(StringConstants::importsProperty(), importsScriptValue); + setJsProperty(engine->context(), exportsScriptValue, childItemsProperty(), childrenScriptValue); + JSValue importsScriptValue = JS_NewArray(engine->context()); arrayIndex = 0; - for (const QString &importStatement : module.importStatements) - importsScriptValue.setProperty(arrayIndex++, importStatement); + for (const QString &importStatement : module.importStatements) { + JS_SetPropertyUint32(engine->context(), importsScriptValue, arrayIndex++, + makeJsString(engine->context(), importStatement)); + } + setJsProperty(engine->context(), exportsScriptValue, StringConstants::importsProperty(), + importsScriptValue); for (auto it = module.modulePropertyValues.cbegin(); it != module.modulePropertyValues.cend(); ++it) { const QVariantMap entries = it.value().toMap(); if (entries.empty()) continue; - QScriptValue moduleObject = engine->newObject(); - ModuleProperties::setModuleScriptValue(exportsScriptValue, moduleObject, it.key()); - for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) - moduleObject.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); + JSValue moduleObject = engine->newObject(); + ModuleProperties::setModuleScriptValue(engine, exportsScriptValue, moduleObject, it.key()); + for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) { + setJsProperty(engine->context(), moduleObject, valIt.key(), + engine->toScriptValue(valIt.value())); + } } return exportsScriptValue; } -static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product) +static void setupBaseProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product) { - QScriptValue &productScriptValue = engine->productScriptValuePrototype(product); - if (productScriptValue.isValid()) - return productScriptValue; - productScriptValue = engine->newObject(); - ModuleProperties::init(productScriptValue, product); - - QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForProduct, product); - productScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); - - QScriptValue exportsScriptValue = setupExportsScriptValue(product->exportedModule, engine); - DependenciesFunction(engine).init(productScriptValue, exportsScriptValue, product); + JSValue &productScriptValue = engine->baseProductScriptValue(product); + if (JS_IsObject(productScriptValue)) + return; + const ScopedJsValue proto(engine->context(), JS_NewObject(engine->context())); + productScriptValue = JS_NewObjectProtoClass(engine->context(), proto, + engine->dataWithPtrClass()); + attachPointerTo(productScriptValue, product); + ModuleProperties::init(engine, productScriptValue, product); + + const QByteArray funcName = StringConstants::artifactsProperty().toUtf8(); + const ScopedJsValue artifactsFunc( + engine->context(), + JS_NewCFunction(engine->context(), &artifactsScriptValueForProduct, + funcName.constData(), 0)); + const ScopedJsAtom artifactsAtom(engine->context(), funcName); + JS_DefineProperty(engine->context(), productScriptValue, artifactsAtom, + JS_UNDEFINED, artifactsFunc, JS_UNDEFINED, + JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + + // FIXME: Proper un-observe rather than ref count decrease here. + ScopedJsValue exportsScriptValue(engine->context(), setupExportsScriptValue(product, engine)); + DependenciesFunction(engine).init(productScriptValue, exportsScriptValue); + + // TODO: Why are these necessary? We catch accesses to product.exports in getProductProperty(). + // But the exportsQbs() and exportsPkgConfig() tests fail without them. engine->setObservedProperty(productScriptValue, exportsProperty(), exportsScriptValue); - engine->observer()->addExportsObjectId(exportsScriptValue.objectId(), product); - return productScriptValue; + engine->observer()->addExportsObjectId(jsObjectId(exportsScriptValue), product); } void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext, - QScriptValue targetObject, const ObserveMode &observeMode) + JSValue targetObject, const ObserveMode &observeMode) { engine->import(fileContext, targetObject, observeMode); - JsExtensions::setupExtensions(fileContext->jsExtensions(), targetObject); + JsExtensions::setupExtensions(engine, fileContext->jsExtensions(), targetObject); } void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product, - const ResolvedModule *module, QScriptValue targetObject, + const ResolvedModule *module, JSValue targetObject, bool setBuildEnvironment) { - QScriptValue projectScriptValue = setupProjectScriptValue(engine, product->project.lock()); - targetObject.setProperty(StringConstants::projectVar(), projectScriptValue); - + JSValue projectScriptValue = setupProjectScriptValue(engine, product->project.lock()); + setJsProperty(engine->context(), targetObject, StringConstants::projectVar(), + projectScriptValue); if (setBuildEnvironment) { QVariant v; v.setValue<void*>(&product->buildEnvironment); engine->setProperty(StringConstants::qbsProcEnvVarInternal(), v); } - QScriptClass *scriptClass = engine->productPropertyScriptClass(); - if (!scriptClass) { - scriptClass = new ProductPropertyScriptClass(engine); - engine->setProductPropertyScriptClass(scriptClass); + JSClassID scriptClass = engine->productPropertyScriptClass(); + if (scriptClass == 0) { + engine->setProductPropertyScriptClass(engine->registerClass("ProductProperties", nullptr, + nullptr, JS_UNDEFINED, &getProductPropertyNames, &getProductProperty)); + scriptClass = engine->productPropertyScriptClass(); } - QScriptValue productScriptValue = engine->newObject(scriptClass); - productScriptValue.setPrototype(setupProductScriptValue(engine, product)); - targetObject.setProperty(StringConstants::productVar(), productScriptValue); + setupBaseProductScriptValue(engine, product); + JSValue productScriptValue = JS_NewObjectClass(engine->context(), scriptClass); + attachPointerTo(productScriptValue, product); + setJsProperty(engine->context(), targetObject, StringConstants::productVar(), + productScriptValue); - QScriptValue data = getDataForProductScriptValue(engine, product); + JSValue data = JS_NewObjectClass(engine->context(), engine->dataWithPtrClass()); // If the Rule is in a Module, set up the 'moduleName' property - if (!module->name.isEmpty()) - data.setProperty(ModuleNameKey, module->name); - productScriptValue.setData(data); + if (!module->name.isEmpty()) { + JS_SetPropertyUint32(engine->context(), data, ModuleNameKey, + makeJsString(engine->context(), module->name)); + } + defineJsProperty(engine->context(), productScriptValue, + StringConstants::dataPropertyInternal(), data); } bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &path) @@ -448,7 +520,7 @@ bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &pat return true; } - for (BuildGraphNode * const childNode : qAsConst(u->children)) { + for (BuildGraphNode * const childNode : std::as_const(u->children)) { if (findPath(childNode, v, path)) { path.prepend(u); return true; @@ -493,12 +565,9 @@ static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, NodeSet *seen) if (!seen->insert(u).second) return false; - for (BuildGraphNode * const childNode : qAsConst(u->children)) { - if (existsPath_impl(childNode, v, seen)) - return true; - } - - return false; + return Internal::any_of(u->children, [v, seen](const auto &child) { + return existsPath_impl(child, v, seen); + }); } static bool existsPath(BuildGraphNode *u, BuildGraphNode *v) @@ -641,7 +710,7 @@ void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product, const QVariantMap oldModuleProperties = artifact->properties->value(); setArtifactData(artifact, sourceArtifact); if (oldFileTags != artifact->fileTags() - || oldModuleProperties != artifact->properties->value()) { + || !qVariantMapsEqual(oldModuleProperties, artifact->properties->value())) { invalidateArtifactAsRuleInputIfNecessary(artifact); } } @@ -661,7 +730,7 @@ void provideFullFileTagsAndProperties(Artifact *artifact) artifact->properties = artifact->product->moduleProperties; FileTags allTags = artifact->pureFileTags.empty() ? artifact->product->fileTagsForFileName(artifact->fileName()) : artifact->pureFileTags; - for (const ArtifactPropertiesConstPtr &props : artifact->product->artifactProperties) { + for (const auto &props : artifact->product->artifactProperties) { if (allTags.intersects(props->fileTagsFilter())) { artifact->properties = props->propertyMap(); allTags += props->extraFileTags(); @@ -697,7 +766,7 @@ void updateGeneratedArtifacts(ResolvedProduct *product) provideFullFileTagsAndProperties(artifact); applyPerArtifactProperties(artifact); if (oldFileTags != artifact->fileTags() - || oldModuleProperties != artifact->properties->value()) { + || !qVariantMapsEqual(oldModuleProperties, artifact->properties->value())) { invalidateArtifactAsRuleInputIfNecessary(artifact); } } @@ -733,24 +802,24 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, CycleDetector cycleDetector(logger); cycleDetector.visitProduct(product); const ProductBuildData * const buildData = product->buildData.get(); - for (const ResolvedModuleConstPtr &m : product->modules) + for (const auto &m : product->modules) QBS_CHECK(m->product == product.get()); qCDebug(lcBuildGraph) << "enabled:" << product->enabled << "build data:" << buildData; if (product->enabled) QBS_CHECK(buildData); if (!product->buildData) return; - for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) { + for (BuildGraphNode * const node : std::as_const(buildData->rootNodes())) { qCDebug(lcBuildGraph).noquote() << "Checking root node" << node->toString(); QBS_CHECK(buildData->allNodes().contains(node)); } Set<QString> filePaths; - for (BuildGraphNode * const node : qAsConst(buildData->allNodes())) { + for (BuildGraphNode * const node : std::as_const(buildData->allNodes())) { qCDebug(lcBuildGraph).noquote() << "Sanity checking node" << node->toString(); QBS_CHECK(node->product == product); - for (const BuildGraphNode * const parent : qAsConst(node->parents)) + for (const BuildGraphNode * const parent : std::as_const(node->parents)) QBS_CHECK(parent->children.contains(node)); - for (BuildGraphNode * const child : qAsConst(node->children)) { + for (BuildGraphNode * const child : std::as_const(node->children)) { QBS_CHECK(child->parents.contains(node)); QBS_CHECK(!child->product.expired()); QBS_CHECK(child->product->buildData); @@ -777,7 +846,7 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, !filePaths.contains(artifact->filePath())); filePaths << artifact->filePath(); - for (Artifact * const child : qAsConst(artifact->childrenAddedByScanner)) + for (Artifact * const child : std::as_const(artifact->childrenAddedByScanner)) QBS_CHECK(artifact->children.contains(child)); const TransformerConstPtr transformer = artifact->transformer; if (artifact->artifactType == Artifact::SourceFile) @@ -796,7 +865,7 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, qCDebug(lcBuildGraph) << "The transformer has" << transformer->outputs.size() << "outputs."; ArtifactSet transformerOutputChildren; - for (const Artifact * const output : qAsConst(transformer->outputs)) { + for (const Artifact * const output : std::as_const(transformer->outputs)) { QBS_CHECK(output->transformer == transformer); transformerOutputChildren.unite(ArtifactSet::filtered(output->children)); for (const Artifact *a : filterByType<Artifact>(output->children)) { @@ -814,14 +883,14 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, } if (lcBuildGraph().isDebugEnabled()) { qCDebug(lcBuildGraph) << "The transformer output children are:"; - for (const Artifact * const a : qAsConst(transformerOutputChildren)) + for (const Artifact * const a : std::as_const(transformerOutputChildren)) qCDebug(lcBuildGraph) << "\t" << a->fileName(); qCDebug(lcBuildGraph) << "The transformer inputs are:"; - for (const Artifact * const a : qAsConst(transformer->inputs)) + for (const Artifact * const a : std::as_const(transformer->inputs)) qCDebug(lcBuildGraph) << "\t" << a->fileName(); } QBS_CHECK(transformer->inputs.size() <= transformerOutputChildren.size()); - for (Artifact * const transformerInput : qAsConst(transformer->inputs)) + for (Artifact * const transformerInput : std::as_const(transformer->inputs)) QBS_CHECK(transformerOutputChildren.contains(transformerInput)); transformer->artifactsMapRequestedInPrepareScript.doSanityChecks(); transformer->artifactsMapRequestedInCommands.doSanityChecks(); @@ -833,10 +902,10 @@ static void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger) { logger.qbsDebug() << "Sanity checking project '" << project->name << "'"; - for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) + for (const ResolvedProjectPtr &subProject : std::as_const(project->subProjects)) doSanityChecks(subProject, allProducts, productNames, logger); - for (const ResolvedProductConstPtr &product : project->products) { + for (const auto &product : project->products) { QBS_CHECK(product->project == project); QBS_CHECK(product->topLevelProject() == project->topLevelProject()); doSanityChecksForProduct(product, allProducts, logger); @@ -850,8 +919,7 @@ void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger) if (qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) return; Set<QString> productNames; - const Set<ResolvedProductPtr> allProducts - = Set<ResolvedProductPtr>::fromStdVector(project->allProducts()); + const auto allProducts = rangeTo<Set<ResolvedProductPtr>>(project->allProducts()); doSanityChecks(project, allProducts, productNames, logger); } diff --git a/src/lib/corelib/buildgraph/buildgraph.h b/src/lib/corelib/buildgraph/buildgraph.h index 2909b06bb..1eadb2e20 100644 --- a/src/lib/corelib/buildgraph/buildgraph.h +++ b/src/lib/corelib/buildgraph/buildgraph.h @@ -43,9 +43,10 @@ #include <language/forward_decls.h> #include <tools/qbs_export.h> -#include <QtCore/qstringlist.h> +#include <quickjs.h> -#include <QtScript/qscriptvalue.h> +#include <QtCore/qstringlist.h> +#include <QtCore/QVariantMap> namespace qbs { namespace Internal { @@ -88,14 +89,18 @@ void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logg void disconnect(BuildGraphNode *u, BuildGraphNode *v); void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext, - QScriptValue targetObject, const ObserveMode &observeMode); + JSValue targetObject, const ObserveMode &observeMode); void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product, - const ResolvedModule *module, QScriptValue targetObject, + const ResolvedModule *module, JSValue targetObject, bool setBuildEnvironment); QString relativeArtifactFileName(const Artifact *artifact); // Debugging helpers void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger); +void getPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + const QVariantMap &properties, const QStringList &extraPropertyNames, + JSValueConst extraObject); + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraph.pri b/src/lib/corelib/buildgraph/buildgraph.pri deleted file mode 100644 index 2ed6be4fd..000000000 --- a/src/lib/corelib/buildgraph/buildgraph.pri +++ /dev/null @@ -1,89 +0,0 @@ -include(../../../install_prefix.pri) - -SOURCES += \ - $$PWD/abstractcommandexecutor.cpp \ - $$PWD/artifact.cpp \ - $$PWD/artifactcleaner.cpp \ - $$PWD/artifactsscriptvalue.cpp \ - $$PWD/artifactvisitor.cpp \ - $$PWD/buildgraph.cpp \ - $$PWD/buildgraphloader.cpp \ - $$PWD/buildgraphnode.cpp \ - $$PWD/cycledetector.cpp \ - $$PWD/dependencyparametersscriptvalue.cpp \ - $$PWD/depscanner.cpp \ - $$PWD/emptydirectoriesremover.cpp \ - $$PWD/environmentscriptrunner.cpp \ - $$PWD/executor.cpp \ - $$PWD/executorjob.cpp \ - $$PWD/filedependency.cpp \ - $$PWD/inputartifactscanner.cpp \ - $$PWD/jscommandexecutor.cpp \ - $$PWD/nodeset.cpp \ - $$PWD/nodetreedumper.cpp \ - $$PWD/processcommandexecutor.cpp \ - $$PWD/productbuilddata.cpp \ - $$PWD/productinstaller.cpp \ - $$PWD/projectbuilddata.cpp \ - $$PWD/qtmocscanner.cpp \ - $$PWD/rawscanneddependency.cpp \ - $$PWD/rawscanresults.cpp \ - $$PWD/requestedartifacts.cpp \ - $$PWD/requesteddependencies.cpp \ - $$PWD/rulecommands.cpp \ - $$PWD/rulegraph.cpp \ - $$PWD/rulenode.cpp \ - $$PWD/rulesapplicator.cpp \ - $$PWD/rulesevaluationcontext.cpp \ - $$PWD/timestampsupdater.cpp \ - $$PWD/transformerchangetracking.cpp \ - $$PWD/transformer.cpp - -HEADERS += \ - $$PWD/abstractcommandexecutor.h \ - $$PWD/artifact.h \ - $$PWD/artifactcleaner.h \ - $$PWD/artifactsscriptvalue.h \ - $$PWD/artifactvisitor.h \ - $$PWD/buildgraph.h \ - $$PWD/buildgraphloader.h \ - $$PWD/buildgraphnode.h \ - $$PWD/buildgraphvisitor.h \ - $$PWD/cycledetector.h \ - $$PWD/dependencyparametersscriptvalue.h \ - $$PWD/depscanner.h \ - $$PWD/emptydirectoriesremover.h \ - $$PWD/environmentscriptrunner.h \ - $$PWD/executor.h \ - $$PWD/executorjob.h \ - $$PWD/filedependency.h \ - $$PWD/forward_decls.h \ - $$PWD/inputartifactscanner.h \ - $$PWD/jscommandexecutor.h \ - $$PWD/nodeset.h \ - $$PWD/nodetreedumper.h \ - $$PWD/processcommandexecutor.h \ - $$PWD/productbuilddata.h \ - $$PWD/productinstaller.h \ - $$PWD/projectbuilddata.h \ - $$PWD/qtmocscanner.h \ - $$PWD/rawscanneddependency.h \ - $$PWD/rawscanresults.h \ - $$PWD/requestedartifacts.h \ - $$PWD/requesteddependencies.h \ - $$PWD/rescuableartifactdata.h \ - $$PWD/rulecommands.h \ - $$PWD/rulegraph.h \ - $$PWD/rulenode.h \ - $$PWD/rulesapplicator.h \ - $$PWD/rulesevaluationcontext.h \ - $$PWD/scriptclasspropertyiterator.h \ - $$PWD/timestampsupdater.h \ - $$PWD/transformerchangetracking.h \ - $$PWD/transformer.h - -!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 index cad236db7..ffae52ab2 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.cpp +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -39,31 +39,32 @@ #include "buildgraphloader.h" #include "buildgraph.h" -#include "cycledetector.h" #include "emptydirectoriesremover.h" #include "productbuilddata.h" #include "projectbuilddata.h" #include "rulenode.h" #include "rulecommands.h" -#include "rulesevaluationcontext.h" #include "transformer.h" +#include <buildgraph/rulesevaluationcontext.h> #include <language/artifactproperties.h> #include <language/language.h> -#include <language/loader.h> #include <language/propertymapinternal.h> #include <language/qualifiedid.h> #include <language/resolvedfilecontext.h> +#include <loader/projectresolver.h> #include <logging/categories.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> #include <tools/fileinfo.h> +#include <tools/jsliterals.h> #include <tools/persistence.h> #include <tools/profile.h> #include <tools/profiling.h> #include <tools/qbsassert.h> #include <tools/qttools.h> #include <tools/settings.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qdir.h> @@ -93,7 +94,7 @@ static void restoreBackPointers(const ResolvedProjectPtr &project) product->project = project; if (!product->buildData) continue; - for (BuildGraphNode * const n : qAsConst(product->buildData->allNodes())) { + for (BuildGraphNode * const n : std::as_const(product->buildData->allNodes())) { if (n->type() == BuildGraphNode::ArtifactNodeType) { project->topLevelProject()->buildData ->insertIntoLookupTable(static_cast<Artifact *>(n)); @@ -101,7 +102,7 @@ static void restoreBackPointers(const ResolvedProjectPtr &project) } } - for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) { + for (const ResolvedProjectPtr &subProject : std::as_const(project->subProjects)) { subProject->parentProject = project; restoreBackPointers(subProject); } @@ -127,7 +128,7 @@ BuildGraphLoadResult BuildGraphLoader::load(const TopLevelProjectPtr &existingPr if (!m_result.loadedProject) return m_result; if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) { - for (const ErrorInfo &e : qAsConst(m_result.loadedProject->warningsEncountered)) + for (const ErrorInfo &e : std::as_const(m_result.loadedProject->warningsEncountered)) m_logger.printWarning(e); return m_result; } @@ -218,7 +219,7 @@ bool BuildGraphLoader::checkBuildGraphCompatibility(const TopLevelProjectConstPt if (m_parameters.projectFilePath().isEmpty()) m_parameters.setProjectFilePath(project->location.filePath()); else - Loader::setupProjectFilePath(m_parameters); + m_parameters.finalizeProjectFilePath(); if (QFileInfo(project->location.filePath()) == QFileInfo(m_parameters.projectFilePath())) return true; QString message = Tr::tr("Stored build graph at '%1' is for project file '%2', but " @@ -242,7 +243,7 @@ static bool checkProductForChangedDependency(std::vector<ResolvedProductPtr> &ch return false; if (contains(changedProducts, product)) return true; - for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { + for (const ResolvedProductPtr &dep : std::as_const(product->dependencies)) { if (checkProductForChangedDependency(changedProducts, seenProducts, dep)) { changedProducts << product; return true; @@ -267,14 +268,14 @@ static void makeChangedProductsListComplete(std::vector<ResolvedProductPtr> &cha static void updateProductAndRulePointers(const ResolvedProductPtr &newProduct) { std::unordered_map<RuleConstPtr, RuleConstPtr> ruleMap; - for (BuildGraphNode *node : qAsConst(newProduct->buildData->allNodes())) { + for (BuildGraphNode *node : std::as_const(newProduct->buildData->allNodes())) { node->product = newProduct; const auto findNewRule = [&ruleMap, &newProduct] (const RuleConstPtr &oldRule) -> RuleConstPtr { const auto it = ruleMap.find(oldRule); if (it != ruleMap.cend()) return it->second; - for (const RuleConstPtr &r : qAsConst(newProduct->rules)) { + for (const auto &r : std::as_const(newProduct->rules)) { if (*r == *oldRule) { ruleMap.insert(std::make_pair(oldRule, r)); return r; @@ -306,8 +307,10 @@ void BuildGraphLoader::trackProjectChanges() std::vector<ResolvedProductPtr> allRestoredProducts = restoredProject->allProducts(); std::vector<ResolvedProductPtr> changedProducts; bool reResolvingNecessary = false; - if (!checkConfigCompatibility()) + if (!checkConfigCompatibility()) { + m_logger.qbsInfo() << Tr::tr("One or more properties have changed."); reResolvingNecessary = true; + } if (hasProductFileChanged(allRestoredProducts, restoredProject->lastStartResolveTime, buildSystemFiles, changedProducts)) { reResolvingNecessary = true; @@ -328,33 +331,41 @@ void BuildGraphLoader::trackProjectChanges() } if (!reResolvingNecessary) { - for (const ErrorInfo &e : qAsConst(restoredProject->warningsEncountered)) + for (const ErrorInfo &e : std::as_const(restoredProject->warningsEncountered)) m_logger.printWarning(e); return; } + for (const QString &file : m_changedProjectFiles) { + m_logger.qbsInfo() << Tr::tr("Project file '%1' has changed.") + .arg(QDir::toNativeSeparators(file)); + } + for (const QString &file : m_removedProjectFiles) { + m_logger.qbsInfo() << Tr::tr("Project file '%1' was removed.") + .arg(QDir::toNativeSeparators(file)); + } + restoredProject->buildData->setDirty(); markTransformersForChangeTracking(allRestoredProducts); if (!m_parameters.overrideBuildGraphData()) m_parameters.setEnvironment(restoredProject->environment); - Loader ldr(m_evalContext->engine(), m_logger); - ldr.setSearchPaths(m_parameters.searchPaths()); - ldr.setProgressObserver(m_evalContext->observer()); - ldr.setOldProjectProbes(restoredProject->probes); + ProjectResolver resolver(m_parameters, m_evalContext->engine(), m_logger); + resolver.setProgressObserver(m_evalContext->observer()); + resolver.setOldProjectProbes(restoredProject->probes); if (!m_parameters.forceProbeExecution()) - ldr.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo); - ldr.setLastResolveTime(restoredProject->lastStartResolveTime); + resolver.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo); + resolver.setLastResolveTime(restoredProject->lastStartResolveTime); QHash<QString, std::vector<ProbeConstPtr>> restoredProbes; - for (const auto &restoredProduct : qAsConst(allRestoredProducts)) + for (const auto &restoredProduct : std::as_const(allRestoredProducts)) restoredProbes.insert(restoredProduct->uniqueName(), restoredProduct->probes); - ldr.setOldProductProbes(restoredProbes); + resolver.setOldProductProbes(restoredProbes); if (!m_parameters.overrideBuildGraphData()) - ldr.setStoredProfiles(restoredProject->profileConfigs); - m_result.newlyResolvedProject = ldr.loadProject(m_parameters); + resolver.setStoredProfiles(restoredProject->profileConfigs); + m_result.newlyResolvedProject = resolver.resolve(); std::vector<ResolvedProductPtr> allNewlyResolvedProducts = m_result.newlyResolvedProject->allProducts(); - for (const ResolvedProductPtr &cp : qAsConst(allNewlyResolvedProducts)) + for (const ResolvedProductPtr &cp : std::as_const(allNewlyResolvedProducts)) m_freshProductsByName.insert(cp->uniqueName(), cp); checkAllProductsForChanges(allRestoredProducts, changedProducts); @@ -363,7 +374,7 @@ void BuildGraphLoader::trackProjectChanges() ChildListHash childLists; if (!changedProducts.empty()) { oldBuildData = std::make_shared<ProjectBuildData>(restoredProject->buildData.get()); - for (const ResolvedProductConstPtr &product : qAsConst(allRestoredProducts)) { + for (const auto &product : std::as_const(allRestoredProducts)) { if (!product->buildData) continue; @@ -382,7 +393,7 @@ void BuildGraphLoader::trackProjectChanges() // mean that artifacts will have to get rebuilt; whether this is necesessary will be decided // an a per-artifact basis by the Executor on the next build. QHash<QString, AllRescuableArtifactData> rescuableArtifactData; - for (const ResolvedProductPtr &product : qAsConst(changedProducts)) { + for (const ResolvedProductPtr &product : std::as_const(changedProducts)) { const QString name = product->uniqueName(); m_changedSourcesByProduct.erase(name); m_productsWhoseArtifactsNeedUpdate.remove(name); @@ -426,7 +437,7 @@ void BuildGraphLoader::trackProjectChanges() } // Products still left in the list do not exist anymore. - for (const ResolvedProductPtr &removedProduct : qAsConst(allRestoredProducts)) { + for (const ResolvedProductPtr &removedProduct : std::as_const(allRestoredProducts)) { removeOne(changedProducts, removedProduct); onProductRemoved(removedProduct, m_result.newlyResolvedProject->buildData.get()); } @@ -452,7 +463,7 @@ void BuildGraphLoader::trackProjectChanges() updateGeneratedArtifacts(product.get()); } - for (const ResolvedProductConstPtr &changedProduct : qAsConst(changedProducts)) { + for (const auto &changedProduct : std::as_const(changedProducts)) { rescueOldBuildData(changedProduct, m_freshProductsByName.value(changedProduct->uniqueName()), childLists, rescuableArtifactData.value(changedProduct->uniqueName())); @@ -461,7 +472,7 @@ void BuildGraphLoader::trackProjectChanges() EmptyDirectoriesRemover(m_result.newlyResolvedProject.get(), m_logger) .removeEmptyParentDirectories(m_artifactsRemovedFromDisk); - for (FileResourceBase * const f : qAsConst(m_objectsToDelete)) { + for (FileResourceBase * const f : std::as_const(m_objectsToDelete)) { if (f->fileType() == FileResourceBase::FileTypeArtifact) static_cast<Artifact *>(f)->product.reset(); // To help with the sanity checks. } @@ -478,12 +489,9 @@ bool BuildGraphLoader::probeExecutionForced( if (!restoredProject->probes.empty()) return true; - for (const auto &p : qAsConst(restoredProducts)) { - if (!p->probes.empty()) - return true; - } - - return false; + return Internal::any_of(restoredProducts, [](const auto &p) { + return !p->probes.empty(); + }); } bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &restoredProject) const @@ -500,9 +508,11 @@ bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &rest newEnv.remove(ldPreloadEnvVar); if (oldEnv != newEnv) { - qCDebug(lcBuildGraph) << "Set of environment variables changed. Must re-resolve project." - << "\nold:" << restoredProject->environment.toStringList() - << "\nnew:" << m_parameters.adjustedEnvironment().toStringList(); + m_logger.qbsInfo() << Tr::tr("Environment changed."); + m_logger.qbsDebug() << Tr::tr("old: %1").arg( + restoredProject->environment.toStringList().join(QLatin1Char('\n'))); + m_logger.qbsDebug() << Tr::tr("new: %2").arg( + m_parameters.adjustedEnvironment().toStringList().join(QLatin1Char('\n'))); return true; } return false; @@ -513,8 +523,8 @@ bool BuildGraphLoader::hasCanonicalFilePathResultChanged(const TopLevelProjectCo for (auto it = restoredProject->canonicalFilePathResults.constBegin(); it != restoredProject->canonicalFilePathResults.constEnd(); ++it) { if (QFileInfo(it.key()).canonicalFilePath() != it.value()) { - qCDebug(lcBuildGraph) << "Canonical file path for file" << it.key() - << "changed, must re-resolve project."; + m_logger.qbsInfo() << Tr::tr("Canonical file path for file '%1' changed.") + .arg(QDir::toNativeSeparators(it.key())); return true; } } @@ -527,8 +537,8 @@ bool BuildGraphLoader::hasFileExistsResultChanged(const TopLevelProjectConstPtr for (QHash<QString, bool>::ConstIterator it = restoredProject->fileExistsResults.constBegin(); it != restoredProject->fileExistsResults.constEnd(); ++it) { if (FileInfo(it.key()).exists() != it.value()) { - qCDebug(lcBuildGraph) << "Existence check for file" << it.key() - << "changed, must re-resolve project."; + m_logger.qbsInfo() << Tr::tr("Existence check for file '%1' changed.") + .arg(QDir::toNativeSeparators(it.key())); return true; } } @@ -542,9 +552,8 @@ bool BuildGraphLoader::hasDirectoryEntriesResultChanged(const TopLevelProjectCon it != restoredProject->directoryEntriesResults.constEnd(); ++it) { if (QDir(it.key().first).entryList(static_cast<QDir::Filters>(it.key().second), QDir::Name) != it.value()) { - qCDebug(lcBuildGraph) << "Entry list for directory" << it.key().first - << static_cast<QDir::Filters>(it.key().second) - << "changed, must re-resolve project."; + m_logger.qbsInfo() << Tr::tr("Entry list for directory '%1' changed.") + .arg(QDir::toNativeSeparators(it.key().first)); return true; } } @@ -558,8 +567,8 @@ bool BuildGraphLoader::hasFileLastModifiedResultChanged(const TopLevelProjectCon = restoredProject->fileLastModifiedResults.constBegin(); it != restoredProject->fileLastModifiedResults.constEnd(); ++it) { if (FileInfo(it.key()).lastModified() != it.value()) { - qCDebug(lcBuildGraph) << "Timestamp for file" << it.key() - << "changed, must re-resolve project."; + m_logger.qbsInfo() << Tr::tr("Timestamp for file '%1' has changed.") + .arg(QDir::toNativeSeparators(it.key())); return true; } } @@ -577,17 +586,18 @@ bool BuildGraphLoader::hasProductFileChanged(const std::vector<ResolvedProductPt const FileInfo pfi(filePath); remainingBuildSystemFiles.remove(filePath); if (!pfi.exists()) { - qCDebug(lcBuildGraph) << "A product was removed, must re-resolve project"; + m_removedProjectFiles << filePath; hasChanged = true; } else if (referenceTime < pfi.lastModified()) { - qCDebug(lcBuildGraph) << "A product was changed, must re-resolve project"; + m_changedProjectFiles << filePath; hasChanged = true; } else if (!contains(changedProducts, product)) { bool foundMissingSourceFile = false; - for (const QString &file : qAsConst(product->missingSourceFiles)) { + for (const QString &file : std::as_const(product->missingSourceFiles)) { if (FileInfo(file).exists()) { - qCDebug(lcBuildGraph) << "Formerly missing file" << file << "in product" - << product->name << "exists now, must re-resolve project"; + m_logger.qbsInfo() + << Tr::tr("Formerly missing file '%1' in product '%2' exists now.") + .arg(QDir::toNativeSeparators(filePath), product->fullDisplayName()); foundMissingSourceFile = true; break; } @@ -601,27 +611,14 @@ bool BuildGraphLoader::hasProductFileChanged(const std::vector<ResolvedProductPt AccumulatingTimer wildcardTimer(m_parameters.logElapsedTime() ? &m_wildcardExpansionEffort : nullptr); for (const GroupPtr &group : product->groups) { - if (!group->wildcards) - continue; - const bool reExpansionRequired = std::any_of( - group->wildcards->dirTimeStamps.cbegin(), - group->wildcards->dirTimeStamps.cend(), - [](const std::pair<QString, FileTime> &pair) { - return FileInfo(pair.first).lastModified() > pair.second; - }); - if (!reExpansionRequired) - continue; - const Set<QString> files = group->wildcards->expandPatterns(group, - FileInfo::path(group->location.filePath()), - product->topLevelProject()->buildDirectory); - Set<QString> wcFiles; - for (const SourceArtifactConstPtr &sourceArtifact : group->wildcards->files) - wcFiles += sourceArtifact->absoluteFilePath; - if (files == wcFiles) - continue; - hasChanged = true; - changedProducts.push_back(product); - break; + if (group->wildcards && group->wildcards->hasChangedSinceExpansion()) { + m_logger.qbsInfo() + << Tr::tr("Must re-expand wildcards for group '%1' in product '%2'.") + .arg(group->name, product->fullDisplayName()); + hasChanged = true; + changedProducts.push_back(product); + break; + } } } } @@ -635,19 +632,18 @@ bool BuildGraphLoader::hasBuildSystemFileChanged(const Set<QString> &buildSystem for (const QString &file : buildSystemFiles) { const FileInfo fi(file); if (!fi.exists()) { - qCDebug(lcBuildGraph) << "Project file" << file - << "no longer exists, must re-resolve project."; + m_removedProjectFiles << file; return true; } const auto generatedChecker = [&file, restoredProject](const ModuleProviderInfo &mpi) { return file.startsWith(mpi.outputDirPath(restoredProject->buildDirectory)); }; - const bool fileWasCreatedByModuleProvider = any_of(restoredProject->moduleProviderInfo, - generatedChecker); + const bool fileWasCreatedByModuleProvider = + any_of(restoredProject->moduleProviderInfo.providers, generatedChecker); const FileTime referenceTime = fileWasCreatedByModuleProvider ? restoredProject->lastEndResolveTime : restoredProject->lastStartResolveTime; if (referenceTime < fi.lastModified()) { - qCDebug(lcBuildGraph) << "Project file" << file << "changed, must re-resolve project."; + m_changedProjectFiles << file; return true; } } @@ -742,9 +738,9 @@ static bool dependenciesAreEqual(const ResolvedProductConstPtr &p1, return false; Set<QString> names1; Set<QString> names2; - for (const ResolvedProductConstPtr &dep : qAsConst(p1->dependencies)) + for (const auto &dep : std::as_const(p1->dependencies)) names1 << dep->uniqueName(); - for (const ResolvedProductConstPtr &dep : qAsConst(p2->dependencies)) + for (const auto &dep : std::as_const(p2->dependencies)) names2 << dep->uniqueName(); return names1 == names2; } @@ -774,10 +770,11 @@ bool BuildGraphLoader::checkProductForInstallInfoChanges(const ResolvedProductPt << StringConstants::installDirProperty() << StringConstants::installPrefixProperty() << StringConstants::installRootProperty(); for (const QString &key : specialProperties) { - if (restoredProduct->moduleProperties->qbsPropertyValue(key) - != newlyResolvedProduct->moduleProperties->qbsPropertyValue(key)) { + if (!qVariantsEqual( + restoredProduct->moduleProperties->qbsPropertyValue(key), + newlyResolvedProduct->moduleProperties->qbsPropertyValue(key))) { qCDebug(lcBuildGraph).noquote().nospace() - << "Product property 'qbs." << key << "' changed."; + << "Product property 'qbs." << key << "' changed."; return true; } } @@ -823,7 +820,7 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, removeOne(product->project->products, product); if (product->buildData) { - for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes())) { + for (BuildGraphNode * const node : std::as_const(product->buildData->allNodes())) { if (node->type() == BuildGraphNode::ArtifactNodeType) { const auto artifact = static_cast<Artifact *>(node); projectBuildData->removeArtifact(artifact, m_logger, removeArtifactsFromDisk, @@ -831,10 +828,10 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, if (removeArtifactsFromDisk && artifact->artifactType == Artifact::Generated) m_artifactsRemovedFromDisk << artifact->filePath(); } else { - for (BuildGraphNode * const parent : qAsConst(node->parents)) + for (BuildGraphNode * const parent : std::as_const(node->parents)) parent->children.remove(node); node->parents.clear(); - for (BuildGraphNode * const child : qAsConst(node->children)) + for (BuildGraphNode * const child : std::as_const(node->children)) child->parents.remove(node); node->children.clear(); } @@ -863,15 +860,32 @@ void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPt bool BuildGraphLoader::checkConfigCompatibility() { const TopLevelProjectConstPtr restoredProject = m_result.loadedProject; - if (m_parameters.topLevelProfile().isEmpty()) + if (m_parameters.topLevelProfile().isEmpty()) { m_parameters.setTopLevelProfile(restoredProject->profile()); + m_parameters.expandBuildConfiguration(); + } if (!m_parameters.overrideBuildGraphData()) { if (!m_parameters.overriddenValues().empty() - && m_parameters.overriddenValues() != restoredProject->overriddenValues) { + && !qVariantMapsEqual( + m_parameters.overriddenValues(), restoredProject->overriddenValues)) { + const auto toUserOutput = [](const QVariantMap &propMap) { + QString o; + for (auto it = propMap.begin(); it != propMap.end(); ++it) { + if (!o.isEmpty()) + o += QLatin1Char(' '); + o.append(it.key()).append(QLatin1Char(':')).append(toJSLiteral(it.value())); + } + return o; + }; throw ErrorInfo(Tr::tr("Property values set on the command line differ from the " - "ones used for the previous build. Use the 'resolve' command if " - "you really want to rebuild with the new properties.")); - } + "ones used for the previous build.\n" + "Old property values: %1\n" + "New property values: %2\n" + "Use the 'resolve' command if " + "you really want to rebuild with the new properties.") + .arg(toUserOutput(restoredProject->overriddenValues), + toUserOutput(m_parameters.overriddenValues()))); + } m_parameters.setOverriddenValues(restoredProject->overriddenValues); if (m_parameters.topLevelProfile() != restoredProject->profile()) { throw ErrorInfo(Tr::tr("The current profile is '%1', but profile '%2' was used " @@ -886,11 +900,12 @@ bool BuildGraphLoader::checkConfigCompatibility() } if (!m_parameters.overrideBuildGraphData()) return true; - if (m_parameters.finalBuildConfigurationTree() != restoredProject->buildConfiguration()) + if (!qVariantMapsEqual( + m_parameters.finalBuildConfigurationTree(), restoredProject->buildConfiguration())) return false; Settings settings(m_parameters.settingsDirectory()); - for (QVariantMap::ConstIterator it = restoredProject->profileConfigs.constBegin(); - it != restoredProject->profileConfigs.constEnd(); ++it) { + const QVariantMap profileConfigsTree = restoredProject->fullProfileConfigsTree(); + for (auto it = profileConfigsTree.begin(); it != profileConfigsTree.end(); ++it) { const Profile profile(it.key(), &settings); const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( profile, m_parameters.configurationName()); @@ -956,14 +971,12 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore rad.lastPrepareScriptExecutionTime = oldArtifact->transformer->lastPrepareScriptExecutionTime; const ChildrenInfo &childrenInfo = childLists.value(oldArtifact); - for (Artifact * const child : qAsConst(childrenInfo.children)) { + for (Artifact * const child : std::as_const(childrenInfo.children)) { rad.children.emplace_back(child->product->name, child->product->multiplexConfigurationId, child->filePath(), childrenInfo.childrenAddedByScanner.contains(child)); - std::transform(oldArtifact->fileDependencies.cbegin(), - oldArtifact->fileDependencies.cend(), - std::back_inserter(rad.fileDependencies), - std::mem_fn(&FileDependency::filePath)); + transform(oldArtifact->fileDependencies, rad.fileDependencies, + std::mem_fn(&FileDependency::filePath)); } newlyResolvedProduct->buildData->addRescuableArtifactData(oldArtifact->filePath(), rad); } diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h index a89272958..02b00e29d 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.h +++ b/src/lib/corelib/buildgraph/buildgraphloader.h @@ -46,6 +46,7 @@ #include <language/forward_decls.h> #include <logging/logger.h> +#include <tools/set.h> #include <tools/setupprojectparameters.h> #include <QtCore/qprocess.h> @@ -122,8 +123,8 @@ private: struct ChildrenInfo { ChildrenInfo() = default; - ChildrenInfo(const ArtifactSet &c1, const ArtifactSet &c2) - : children(c1), childrenAddedByScanner(c2) {} + ChildrenInfo(ArtifactSet c1, ArtifactSet c2) + : children(std::move(c1)), childrenAddedByScanner(std::move(c2)) {} ArtifactSet children; ArtifactSet childrenAddedByScanner; }; @@ -140,6 +141,8 @@ private: Logger m_logger; QStringList m_artifactsRemovedFromDisk; std::unordered_map<QString, std::vector<SourceArtifactConstPtr>> m_changedSourcesByProduct; + Set<QString> m_changedProjectFiles; + Set<QString> m_removedProjectFiles; Set<QString> m_productsWhoseArtifactsNeedUpdate; qint64 m_wildcardExpansionEffort = 0; qint64 m_propertyComparisonEffort = 0; diff --git a/src/lib/corelib/buildgraph/buildgraphnode.cpp b/src/lib/corelib/buildgraph/buildgraphnode.cpp index 7d011d50c..cb19ee95c 100644 --- a/src/lib/corelib/buildgraph/buildgraphnode.cpp +++ b/src/lib/corelib/buildgraph/buildgraphnode.cpp @@ -56,9 +56,9 @@ BuildGraphNode::BuildGraphNode() : buildState(Untouched) BuildGraphNode::~BuildGraphNode() { - for (BuildGraphNode *p : qAsConst(parents)) + for (BuildGraphNode *p : std::as_const(parents)) p->children.remove(this); - for (BuildGraphNode *c : qAsConst(children)) + for (BuildGraphNode *c : std::as_const(children)) c->parents.remove(this); } @@ -69,7 +69,7 @@ void BuildGraphNode::onChildDisconnected(BuildGraphNode *child) void BuildGraphNode::acceptChildren(BuildGraphVisitor *visitor) { - for (BuildGraphNode *child : qAsConst(children)) + for (BuildGraphNode *child : std::as_const(children)) child->accept(visitor); } diff --git a/src/lib/corelib/buildgraph/cycledetector.cpp b/src/lib/corelib/buildgraph/cycledetector.cpp index 5daed55fd..3a1c43cd1 100644 --- a/src/lib/corelib/buildgraph/cycledetector.cpp +++ b/src/lib/corelib/buildgraph/cycledetector.cpp @@ -91,7 +91,7 @@ bool CycleDetector::visitNode(BuildGraphNode *node) m_nodesInCurrentPath += node; m_parent = node; - for (BuildGraphNode * const child : qAsConst(node->children)) + for (BuildGraphNode * const child : std::as_const(node->children)) child->accept(this); m_nodesInCurrentPath -= node; m_allNodes += node; diff --git a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp index f1bf8db13..7b333d32f 100644 --- a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp +++ b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp @@ -46,37 +46,39 @@ namespace qbs { namespace Internal { -static QScriptValue toScriptValue(ScriptEngine *engine, const QString &productName, - const QVariantMap &v, const QString &depName, - const QualifiedId &moduleName) +static JSValue toScriptValue(ScriptEngine *engine, const QString &productName, + const QVariantMap &v, const QString &depName, + const QualifiedId &moduleName) { - QScriptValue obj = engine->newObject(); + JSValue obj = engine->newObject(); bool objIdAddedToObserver = false; for (auto it = v.begin(); it != v.end(); ++it) { - if (it.value().type() == QVariant::Map) { - obj.setProperty(it.key(), toScriptValue(engine, productName, it.value().toMap(), - depName, QualifiedId(moduleName) << it.key())); + if (it.value().userType() == QMetaType::QVariantMap) { + setJsProperty(engine->context(), obj, it.key(), + toScriptValue(engine, productName, it.value().toMap(), + depName, QualifiedId(moduleName) << it.key())); } else { if (!objIdAddedToObserver) { objIdAddedToObserver = true; - engine->observer()->addParameterObjectId(obj.objectId(), productName, depName, + engine->observer()->addParameterObjectId(jsObjectId(obj), productName, depName, moduleName); } - engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value())); + const ScopedJsValue val(engine->context(), engine->toScriptValue(it.value())); + engine->setObservedProperty(obj, it.key(), val); } } return obj; } -static QScriptValue toScriptValue(ScriptEngine *scriptEngine, const QString &productName, - const QVariantMap &v, const QString &depName) +static JSValue toScriptValue(ScriptEngine *scriptEngine, const QString &productName, + const QVariantMap &v, const QString &depName) { return toScriptValue(scriptEngine, productName, v, depName, {}); } -QScriptValue dependencyParametersValue(const QString &productName, const QString &dependencyName, - const QVariantMap ¶metersMap, ScriptEngine *engine) +JSValue dependencyParametersValue(const QString &productName, const QString &dependencyName, + const QVariantMap ¶metersMap, ScriptEngine *engine) { return toScriptValue(engine, productName, parametersMap, dependencyName); } diff --git a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h index 7e4287be6..2fb4634e8 100644 --- a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h +++ b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h @@ -39,15 +39,16 @@ #ifndef QBS_DEPENDENCYPARAMETERSSCRIPTVALUE_H #define QBS_DEPENDENCYPARAMETERSSCRIPTVALUE_H +#include <quickjs.h> + #include <QtCore/qvariant.h> -#include <QtScript/qscriptvalue.h> namespace qbs { namespace Internal { class ScriptEngine; -QScriptValue dependencyParametersValue(const QString &productName, const QString &dependencyName, - const QVariantMap ¶metersMap, ScriptEngine *engine); +JSValue dependencyParametersValue(const QString &productName, const QString &dependencyName, + const QVariantMap ¶metersMap, ScriptEngine *engine); } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/depscanner.cpp b/src/lib/corelib/buildgraph/depscanner.cpp index 0bf644286..01e781a54 100644 --- a/src/lib/corelib/buildgraph/depscanner.cpp +++ b/src/lib/corelib/buildgraph/depscanner.cpp @@ -56,7 +56,7 @@ #include <QtCore/qvariant.h> -#include <QtScript/qscriptcontext.h> +#include <vector> namespace qbs { namespace Internal { @@ -110,7 +110,7 @@ QStringList PluginDependencyScanner::collectDependencies(Artifact *artifact, Fil void *scannerHandle = m_plugin->open(filepath.utf16(), fileTags, ScanForDependenciesFlag); if (!scannerHandle) return {}; - forever { + for (;;) { int flags = 0; int length = 0; const char *szOutFilePath = m_plugin->next(scannerHandle, &length, &flags); @@ -127,7 +127,7 @@ QStringList PluginDependencyScanner::collectDependencies(Artifact *artifact, Fil result += outFilePath; } m_plugin->close(scannerHandle); - return result.toList(); + return rangeTo<QStringList>(result); } bool PluginDependencyScanner::recursive() const @@ -158,10 +158,9 @@ UserDependencyScanner::UserDependencyScanner(ResolvedScannerConstPtr scanner, ScriptEngine *engine) : m_scanner(std::move(scanner)), m_engine(engine), + m_global(engine->context(), JS_NewObjectProto(engine->context(), m_engine->globalObject())), m_product(nullptr) { - m_global = m_engine->newObject(); - m_global.setPrototype(m_engine->globalObject()); setupScriptEngineForFile(m_engine, m_scanner->scanScript.fileContext(), m_global, ObserveMode::Disabled); // TODO: QBS-1092 } @@ -218,7 +217,7 @@ public: } }; -QStringList UserDependencyScanner::evaluate(const Artifact *artifact, +QStringList UserDependencyScanner::evaluate(Artifact *artifact, const FileResourceBase *fileToScan, const PrivateScriptFunction &script) { ScriptEngineActiveFlagGuard guard(m_engine); @@ -229,38 +228,37 @@ QStringList UserDependencyScanner::evaluate(const Artifact *artifact, m_scanner->module.get(), m_global, true); } - QScriptValueList args; + JSValueList args; args.reserve(fileToScan ? 4 : 3); - args.push_back(m_global.property(StringConstants::projectVar())); - args.push_back(m_global.property(StringConstants::productVar())); + args.push_back(getJsProperty(m_engine->context(), m_global, StringConstants::projectVar())); + args.push_back(getJsProperty(m_engine->context(), m_global, StringConstants::productVar())); args.push_back(Transformer::translateFileConfig(m_engine, artifact, m_scanner->module->name)); if (fileToScan) - args.push_back(fileToScan->filePath()); + args.push_back(makeJsString(m_engine->context(), fileToScan->filePath())); + const ScopedJsValueList argsMgr(m_engine->context(), args); - m_engine->setGlobalObject(m_global); - QScriptValue &function = script.scriptFunction; - if (!function.isValid() || function.engine() != m_engine) { - function = m_engine->evaluate(script.sourceCode()); - if (Q_UNLIKELY(!function.isFunction())) - throw ErrorInfo(Tr::tr("Invalid scan script."), script.location()); - } - QScriptValue result = function.call(QScriptValue(), args); - m_engine->setGlobalObject(m_global.prototype()); + const TemporaryGlobalObjectSetter gos(m_engine, m_global); + const JSValue function = script.getFunction(m_engine, Tr::tr("Invalid scan script.")); + const ScopedJsValue result( + m_engine->context(), + JS_Call(m_engine->context(), function, m_engine->globalObject(), + int(args.size()), args.data())); m_engine->clearRequestedProperties(); - if (Q_UNLIKELY(m_engine->hasErrorOrException(result))) { - QString msg = Tr::tr("evaluating scan script: ") + m_engine->lastErrorString(result); - const CodeLocation loc = m_engine->lastErrorLocation(result, script.location()); - m_engine->clearExceptions(); - throw ErrorInfo(msg, loc); + if (JsException ex = m_engine->checkAndClearException(script.location())) { + ErrorInfo err = ex.toErrorInfo(); + err.prepend(Tr::tr("Error evaluating scan script")); + throw err; } QStringList list; - if (result.isArray()) { - const int count = result.property(StringConstants::lengthProperty()).toInt32(); + if (JS_IsArray(m_engine->context(), result)) { + const int count = getJsIntProperty(m_engine->context(), result, + StringConstants::lengthProperty()); list.reserve(count); for (qint32 i = 0; i < count; ++i) { - QScriptValue item = result.property(i); - if (item.isValid() && !item.isUndefined()) - list.push_back(item.toString()); + JSValue item = JS_GetPropertyUint32(m_engine->context(), result, i); + if (!JS_IsUninitialized(item) && !JS_IsUndefined(item)) + list.push_back(getJsString(m_engine->context(), item)); + JS_FreeValue(m_engine->context(), item); } } return list; diff --git a/src/lib/corelib/buildgraph/depscanner.h b/src/lib/corelib/buildgraph/depscanner.h index 6b18004f9..51816dbd7 100644 --- a/src/lib/corelib/buildgraph/depscanner.h +++ b/src/lib/corelib/buildgraph/depscanner.h @@ -43,11 +43,10 @@ #include <language/forward_decls.h> #include <language/filetags.h> #include <language/preparescriptobserver.h> +#include <tools/scripttools.h> #include <QtCore/qstringlist.h> -#include <QtScript/qscriptvalue.h> - class ScannerPlugin; namespace qbs { @@ -115,11 +114,11 @@ private: const PropertyMapConstPtr &m2) const override; bool cacheIsPerFile() const override { return true; } - QStringList evaluate(const Artifact *artifact, const FileResourceBase *fileToScan, const PrivateScriptFunction &script); + QStringList evaluate(Artifact *artifact, const FileResourceBase *fileToScan, const PrivateScriptFunction &script); ResolvedScannerConstPtr m_scanner; ScriptEngine *m_engine; - QScriptValue m_global; + ScopedJsValue m_global; ResolvedProduct *m_product; }; diff --git a/src/lib/corelib/buildgraph/environmentscriptrunner.cpp b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp index 7d82efb41..0fbb3ab19 100644 --- a/src/lib/corelib/buildgraph/environmentscriptrunner.cpp +++ b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp @@ -45,11 +45,14 @@ #include <language/propertymapinternal.h> #include <language/resolvedfilecontext.h> #include <language/scriptengine.h> +#include <logging/translator.h> #include <tools/qbsassert.h> #include <tools/qttools.h> -#include <logging/translator.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> +#include <quickjs.h> + #include <QtCore/qhash.h> #include <QtCore/qvariant.h> @@ -123,19 +126,18 @@ void EnvironmentScriptRunner::setupEnvironment() const auto hasScript = [this](const ResolvedModuleConstPtr &m) { return !getScript(m.get()).sourceCode().isEmpty(); }; - const bool hasAnyScripts = std::any_of(m_product->modules.cbegin(), m_product->modules.cend(), - hasScript); + const bool hasAnyScripts = Internal::any_of(m_product->modules, hasScript); if (!hasAnyScripts) return; QMap<QString, const ResolvedModule *> moduleMap; - for (const ResolvedModuleConstPtr &module : m_product->modules) + for (const auto &module : m_product->modules) moduleMap.insert(module->name, module.get()); QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleParents; QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleChildren; - for (const ResolvedModuleConstPtr &module : m_product->modules) { - for (const QString &moduleName : qAsConst(module->moduleDependencies)) { + for (const auto &module : m_product->modules) { + for (const QString &moduleName : std::as_const(module->moduleDependencies)) { const ResolvedModule * const depmod = moduleMap.value(moduleName); QBS_ASSERT(depmod, return); moduleParents[depmod].push_back(module.get()); @@ -144,7 +146,7 @@ void EnvironmentScriptRunner::setupEnvironment() } QList<const ResolvedModule *> rootModules; - for (const ResolvedModuleConstPtr &module : m_product->modules) { + for (const auto &module : m_product->modules) { if (moduleParents.value(module.get()).isEmpty()) { QBS_ASSERT(module, return); rootModules.push_back(module.get()); @@ -165,38 +167,40 @@ void EnvironmentScriptRunner::setupEnvironment() continue; RulesEvaluationContext::Scope s(m_evalContext); - QScriptValue envScriptContext = engine()->newObject(); - envScriptContext.setPrototype(engine()->globalObject()); + JSContext * const ctx = engine()->context(); + ScopedJsValue envScriptContext(ctx, JS_NewObjectProto(ctx, engine()->globalObject())); setupScriptEngineForProduct(engine(), m_product, module, envScriptContext, false); const QString &productKey = StringConstants::productVar(); const QString &projectKey = StringConstants::projectVar(); - m_evalContext->scope().setProperty(productKey, envScriptContext.property(productKey)); - m_evalContext->scope().setProperty(projectKey, envScriptContext.property(projectKey)); + setJsProperty(ctx, m_evalContext->scope(), productKey, + getJsProperty(ctx, envScriptContext, productKey)); + setJsProperty(ctx, m_evalContext->scope(), projectKey, + getJsProperty(ctx, envScriptContext, projectKey)); if (m_envType == RunEnv) { - QScriptValue configArray = engine()->newArray(m_runEnvConfig.size()); + JSValue configArray = JS_NewArray(ctx); for (int i = 0; i < m_runEnvConfig.size(); ++i) - configArray.setProperty(i, QScriptValue(m_runEnvConfig.at(i))); - m_evalContext->scope().setProperty(QStringLiteral("config"), configArray); + JS_SetPropertyUint32(ctx, configArray, i, makeJsString(ctx, m_runEnvConfig.at(i))); + JS_SetPropertyStr(ctx, m_evalContext->scope(), "config", configArray); } setupScriptEngineForFile(engine(), setupScript.fileContext(), m_evalContext->scope(), ObserveMode::Disabled); // TODO: Cache evaluate result - QScriptValue fun = engine()->evaluate(setupScript.sourceCode(), - setupScript.location().filePath(), - setupScript.location().line()); - QBS_CHECK(fun.isFunction()); - const QScriptValueList svArgs = ScriptEngine::argumentList(scriptFunctionArgs, - m_evalContext->scope()); - const QScriptValue res = fun.call(QScriptValue(), svArgs); - engine()->releaseResourcesOfScriptObjects(); - if (Q_UNLIKELY(engine()->hasErrorOrException(res))) { + ScopedJsValue fun(ctx, engine()->evaluate(JsValueOwner::Caller, setupScript.sourceCode(), + setupScript.location().filePath(), + setupScript.location().line())); + QBS_CHECK(JS_IsFunction(ctx, fun)); + const ScopedJsValueList svArgs = engine()->argumentList(scriptFunctionArgs, + m_evalContext->scope()); + JSValueList argsForFun = svArgs; + JS_Call(ctx, fun, engine()->globalObject(), int(argsForFun.size()), argsForFun.data()); + if (const JsException ex = engine()->checkAndClearException(setupScript.location())) { const QString scriptName = m_envType == BuildEnv ? StringConstants::setupBuildEnvironmentProperty() : StringConstants::setupRunEnvironmentProperty(); - throw ErrorInfo(Tr::tr("Error running %1 script for product '%2': %3") - .arg(scriptName, m_product->fullDisplayName(), - engine()->lastErrorString(res)), - engine()->lastErrorLocation(res, setupScript.location())); + ErrorInfo err = ex.toErrorInfo(); + err.prepend(Tr::tr("Error running %1 script for product '%2'") + .arg(scriptName, m_product->fullDisplayName())); + throw err; } } diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp index de81ada20..2b8232a0a 100644 --- a/src/lib/corelib/buildgraph/executor.cpp +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -67,6 +67,7 @@ #include <tools/qbsassert.h> #include <tools/qttools.h> #include <tools/settings.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qdir.h> @@ -102,12 +103,8 @@ Executor::Executor(Logger logger, QObject *parent) Executor::~Executor() { - // jobs must be destroyed before deleting the shared scan result cache - for (ExecutorJob *job : qAsConst(m_availableJobs)) - delete job; - const auto processingJobs = m_processingJobs.keys(); - for (ExecutorJob *job : processingJobs) - delete job; + // jobs must be destroyed before deleting the m_inputArtifactScanContext + m_allJobs.clear(); delete m_inputArtifactScanContext; delete m_productInstaller; } @@ -138,11 +135,9 @@ void Executor::retrieveSourceFileTimestamp(Artifact *artifact) const { QBS_CHECK(artifact->artifactType == Artifact::SourceFile); - if (m_buildOptions.changedFiles().empty()) - artifact->setTimestamp(recursiveFileTime(artifact->filePath())); - else if (m_buildOptions.changedFiles().contains(artifact->filePath())) + if (m_buildOptions.changedFiles().contains(artifact->filePath())) artifact->setTimestamp(FileTime::currentTime()); - else if (!artifact->timestamp().isValid()) + else if (m_buildOptions.changedFiles().empty() || !artifact->timestamp().isValid()) artifact->setTimestamp(recursiveFileTime(artifact->filePath())); artifact->timestampRetrieved = true; @@ -153,7 +148,7 @@ void Executor::retrieveSourceFileTimestamp(Artifact *artifact) const void Executor::build() { try { - m_partialBuild = m_productsToBuild.size() != m_allProducts.size(); + m_partialBuild = size_t(m_productsToBuild.size()) != m_allProducts.size(); doBuild(); } catch (const ErrorInfo &e) { handleError(e); @@ -170,7 +165,7 @@ void Executor::setProject(const TopLevelProjectPtr &project) m_projectsByName.insert(std::make_pair(p->name, p.get())); } -void Executor::setProducts(const std::vector<ResolvedProductPtr> &productsToBuild) +void Executor::setProducts(const QVector<ResolvedProductPtr> &productsToBuild) { m_productsToBuild = productsToBuild; m_productsByName.clear(); @@ -197,7 +192,7 @@ public: allDependencies += dep; } const Set<ResolvedProductPtr> rootProducts - = Set<ResolvedProductPtr>::fromStdVector(m_allProducts) - allDependencies; + = rangeTo<Set<ResolvedProductPtr>>(m_allProducts) - allDependencies; m_priority = UINT_MAX; m_seenProducts.clear(); for (const ResolvedProductPtr &rootProduct : rootProducts) @@ -209,7 +204,7 @@ private: { if (!m_seenProducts.insert(product).second) return; - for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) + for (const ResolvedProductPtr &dependency : std::as_const(product->dependencies)) traverse(dependency); if (!product->buildData) return; @@ -266,9 +261,9 @@ void Executor::doBuild() doSanityChecks(); QBS_CHECK(!m_project->buildData->evaluationContext); - m_project->buildData->evaluationContext - = RulesEvaluationContextPtr(new RulesEvaluationContext(m_logger)); + m_project->buildData->evaluationContext = std::make_shared<RulesEvaluationContext>(m_logger); m_evalContext = m_project->buildData->evaluationContext; + m_progressObserver->addScriptEngine(m_evalContext->engine()); m_elapsedTimeRules = m_elapsedTimeScanners = m_elapsedTimeInstalling = 0; m_evalContext->engine()->enableProfiling(m_buildOptions.logElapsedTime()); @@ -334,7 +329,7 @@ void Executor::updateLeaves(BuildGraphNode *node, NodeSet &seenNodes) } bool isLeaf = true; - for (BuildGraphNode *child : qAsConst(node->children)) { + for (BuildGraphNode *child : std::as_const(node->children)) { if (child->buildState != BuildGraphNode::Built) { isLeaf = false; updateLeaves(child, seenNodes); @@ -402,8 +397,10 @@ bool Executor::schedulingBlockedByJobLimit(const BuildGraphNode *node) if (currentJobCount == 0) continue; const auto jobLimitIsExceeded = [currentJobCount, jobPool, this](const Transformer *t) { - const int maxJobCount = m_jobLimitsPerProduct.at(t->product().get()) - .getLimit(jobPool); + const auto it = m_jobLimitsPerProduct.find(t->product().get()); + if (it == m_jobLimitsPerProduct.cend()) + return false; // See checkNodeProduct() for why this is possible + const int maxJobCount = it->second.getLimit(jobPool); return maxJobCount > 0 && currentJobCount >= maxJobCount; }; @@ -455,7 +452,7 @@ bool Executor::isUpToDate(Artifact *artifact) const return false; } - for (FileDependency *fileDependency : qAsConst(artifact->fileDependencies)) { + for (FileDependency *fileDependency : std::as_const(artifact->fileDependencies)) { if (!fileDependency->timestamp().isValid()) { qCDebug(lcUpToDateCheck) << "file dependency doesn't exist" << fileDependency->filePath(); @@ -482,7 +479,7 @@ bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const bool hasAlwaysUpdatedArtifacts = false; bool hasUpToDateNotAlwaysUpdatedArtifacts = false; - for (Artifact *artifact : qAsConst(transformer->outputs)) { + for (Artifact *artifact : std::as_const(transformer->outputs)) { if (isUpToDate(artifact)) { if (artifact->alwaysUpdated) hasAlwaysUpdatedArtifacts = true; @@ -571,7 +568,7 @@ void Executor::finishJob(ExecutorJob *job, bool success) updateJobCounts(transformer.get(), -1); if (success) { m_project->buildData->setDirty(); - for (Artifact * const artifact : qAsConst(transformer->outputs)) { + for (Artifact * const artifact : std::as_const(transformer->outputs)) { if (artifact->alwaysUpdated) { artifact->setTimestamp(FileTime::currentTime()); for (Artifact * const parent : artifact->parentArtifacts()) @@ -624,14 +621,13 @@ void Executor::finishJob(ExecutorJob *job, bool success) static bool allChildrenBuilt(BuildGraphNode *node) { - return std::all_of(node->children.cbegin(), node->children.cend(), - std::mem_fn(&BuildGraphNode::isBuilt)); + return Internal::all_of(node->children, std::mem_fn(&BuildGraphNode::isBuilt)); } void Executor::finishNode(BuildGraphNode *leaf) { leaf->buildState = BuildGraphNode::Built; - for (BuildGraphNode * const parent : qAsConst(leaf->parents)) { + for (BuildGraphNode * const parent : std::as_const(leaf->parents)) { if (parent->buildState != BuildGraphNode::Buildable) { qCDebug(lcExec).noquote() << "parent" << parent->toString() << "build state:" << toString(parent->buildState); @@ -666,8 +662,8 @@ bool Executor::transformerHasMatchingOutputTags(const TransformerConstPtr &trans if (m_activeFileTags.empty()) return true; // No filtering requested. - return std::any_of(transformer->outputs.cbegin(), transformer->outputs.cend(), - [this](const Artifact *a) { return artifactHasMatchingOutputTags(a); }); + return Internal::any_of(transformer->outputs, [this](const Artifact *a) { + return artifactHasMatchingOutputTags(a); }); } bool Executor::artifactHasMatchingOutputTags(const Artifact *artifact) const @@ -684,7 +680,7 @@ bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &trans return false; if (transformer->inputs.empty()) return true; - for (const Artifact * const input : qAsConst(transformer->inputs)) { + for (const Artifact * const input : std::as_const(transformer->inputs)) { const auto files = m_buildOptions.filesToConsider(); for (const QString &filePath : files) { if (input->filePath() == filePath @@ -700,7 +696,7 @@ bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &trans void Executor::setupJobLimits() { Settings settings(m_buildOptions.settingsDirectory()); - for (const ResolvedProductConstPtr &p : m_productsToBuild) { + for (const auto &p : std::as_const(m_productsToBuild)) { const Preferences prefs(&settings, p->profile()); const JobLimits &jobLimitsFromSettings = prefs.jobLimits(); JobLimits effectiveJobLimits; @@ -737,7 +733,7 @@ void Executor::setupProgressObserver() if (!m_progressObserver) return; int totalEffort = 1; // For the effort after the last rule application; - for (const ResolvedProductConstPtr &product : qAsConst(m_productsToBuild)) { + for (const auto &product : std::as_const(m_productsToBuild)) { QBS_CHECK(product->buildData); const auto filtered = filterByType<RuleNode>(product->buildData->allNodes()); totalEffort += std::distance(filtered.begin(), filtered.end()); @@ -749,7 +745,7 @@ void Executor::doSanityChecks() { QBS_CHECK(m_project); QBS_CHECK(!m_productsToBuild.empty()); - for (const ResolvedProductConstPtr &product : qAsConst(m_productsToBuild)) { + for (const auto &product : std::as_const(m_productsToBuild)) { QBS_CHECK(product->buildData); QBS_CHECK(product->topLevelProject() == m_project.get()); } @@ -768,10 +764,13 @@ void Executor::handleError(const ErrorInfo &error) void Executor::addExecutorJobs() { - qCDebug(lcExec) << "preparing executor for" << m_buildOptions.maxJobCount() - << "jobs in parallel"; - for (int i = 1; i <= m_buildOptions.maxJobCount(); i++) { - const auto job = new ExecutorJob(m_logger, this); + const int count = m_buildOptions.maxJobCount(); + qCDebug(lcExec) << "preparing executor for" << count << "jobs in parallel"; + m_allJobs.reserve(count); + m_availableJobs.reserve(count); + for (int i = 1; i <= count; i++) { + m_allJobs.push_back(std::make_unique<ExecutorJob>(m_logger)); + const auto job = m_allJobs.back().get(); job->setMainThreadScriptEngine(m_evalContext->engine()); job->setObjectName(QStringLiteral("J%1").arg(i)); job->setDryRun(m_buildOptions.dryRun()); @@ -917,7 +916,7 @@ bool Executor::checkForUnbuiltDependencies(Artifact *artifact) { bool buildingDependenciesFound = false; NodeSet unbuiltDependencies; - for (BuildGraphNode * const dependency : qAsConst(artifact->children)) { + for (BuildGraphNode * const dependency : std::as_const(artifact->children)) { switch (dependency->buildState) { case BuildGraphNode::Untouched: case BuildGraphNode::Buildable: @@ -948,7 +947,7 @@ bool Executor::checkForUnbuiltDependencies(Artifact *artifact) void Executor::potentiallyRunTransformer(const TransformerPtr &transformer) { - for (Artifact * const output : qAsConst(transformer->outputs)) { + for (Artifact * const output : std::as_const(transformer->outputs)) { // Rescuing build data can introduce new dependencies, potentially delaying execution of // this transformer. bool childrenAddedDueToRescue; @@ -971,7 +970,7 @@ void Executor::potentiallyRunTransformer(const TransformerPtr &transformer) const bool mustExecute = mustExecuteTransformer(transformer); if (mustExecute || m_buildOptions.forceTimestampCheck()) { - for (Artifact * const output : qAsConst(transformer->outputs)) { + for (Artifact * const output : std::as_const(transformer->outputs)) { // Scan all input artifacts. If new dependencies were found during scanning, delay // execution of this transformer. InputArtifactScanner scanner(output, m_inputArtifactScanContext, m_logger); @@ -1002,7 +1001,7 @@ void Executor::runTransformer(const TransformerPtr &transformer) // create the output directories if (!m_buildOptions.dryRun()) { - for (Artifact * const output : qAsConst(transformer->outputs)) { + for (Artifact * const output : std::as_const(transformer->outputs)) { QDir outDir = QFileInfo(output->filePath()).absoluteDir(); if (!outDir.exists() && !outDir.mkpath(StringConstants::dot())) { throw ErrorInfo(tr("Failed to create directory '%1'.") @@ -1013,7 +1012,7 @@ void Executor::runTransformer(const TransformerPtr &transformer) QBS_CHECK(!m_availableJobs.empty()); ExecutorJob *job = m_availableJobs.takeFirst(); - for (Artifact * const artifact : qAsConst(transformer->outputs)) + for (Artifact * const artifact : std::as_const(transformer->outputs)) artifact->buildState = BuildGraphNode::Building; m_processingJobs.insert(job, transformer); updateJobCounts(transformer.get(), 1); @@ -1023,7 +1022,7 @@ void Executor::runTransformer(const TransformerPtr &transformer) void Executor::finishTransformer(const TransformerPtr &transformer) { transformer->markedForRerun = false; - for (Artifact * const artifact : qAsConst(transformer->outputs)) { + for (Artifact * const artifact : std::as_const(transformer->outputs)) { possiblyInstallArtifact(artifact); finishArtifact(artifact); } @@ -1058,7 +1057,7 @@ void Executor::onJobFinished(const qbs::ErrorInfo &err) if (m_evalContext->engine()->isActive()) { qCDebug(lcExec) << "Executor job finished while rule execution is pausing. " "Delaying slot execution."; - QTimer::singleShot(0, job, [job, err] { job->finished(err); }); + QTimer::singleShot(0, job, [job, err] { emit job->finished(err); }); return; } @@ -1084,9 +1083,9 @@ void Executor::checkForUnbuiltProducts() if (m_buildOptions.executeRulesOnly()) return; std::vector<ResolvedProductPtr> unbuiltProducts; - for (const ResolvedProductPtr &product : m_productsToBuild) { + for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) { bool productBuilt = true; - for (BuildGraphNode *rootNode : qAsConst(product->buildData->rootNodes())) { + for (BuildGraphNode *rootNode : std::as_const(product->buildData->rootNodes())) { if (rootNode->buildState != BuildGraphNode::Built) { productBuilt = false; unbuiltProducts.push_back(product); @@ -1109,10 +1108,8 @@ void Executor::checkForUnbuiltProducts() m_logger.qbsInfo() << Tr::tr("Build done%1.").arg(configString()); } else { m_error.append(Tr::tr("The following products could not be built%1:").arg(configString())); - QStringList productNames; - std::transform(unbuiltProducts.cbegin(), unbuiltProducts.cend(), - std::back_inserter(productNames), - [](const ResolvedProductConstPtr &p) { return p->fullDisplayName(); }); + auto productNames = transformed<QStringList>(unbuiltProducts, [](const auto &p) { + return p->fullDisplayName(); }); std::sort(productNames.begin(), productNames.end()); m_error.append(productNames.join(QLatin1String(", "))); } @@ -1202,11 +1199,11 @@ void Executor::prepareAllNodes() for (const ResolvedProductPtr &product : m_allProducts) { if (product->enabled) { QBS_CHECK(product->buildData); - for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes())) + for (BuildGraphNode * const node : std::as_const(product->buildData->allNodes())) node->buildState = BuildGraphNode::Untouched; } } - for (const ResolvedProductPtr &product : m_productsToBuild) { + for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) { QBS_CHECK(product->buildData); for (Artifact * const artifact : filterByType<Artifact>(product->buildData->allNodes())) prepareArtifact(artifact); @@ -1228,12 +1225,12 @@ void Executor::syncFileDependencies() "removing from lookup table"; m_project->buildData->removeFromLookupTable(dep); bool isReferencedByArtifact = false; - for (const ResolvedProductConstPtr &product : m_allProducts) { + for (const auto &product : m_allProducts) { if (!product->buildData) continue; const auto artifactList = filterByType<Artifact>(product->buildData->allNodes()); - isReferencedByArtifact = std::any_of(artifactList.begin(), artifactList.end(), - [dep](const Artifact *a) { return a->fileDependencies.contains(dep); }); + isReferencedByArtifact = Internal::any_of(artifactList, [dep](const Artifact *a) { + return a->fileDependencies.contains(dep); }); // TODO: Would it be safe to mark the artifact as "not up to date" here and clear // its list of file dependencies, rather than doing the check again in // isUpToDate()? @@ -1290,7 +1287,7 @@ void Executor::setupForBuildingSelectedFiles(const BuildGraphNode *node) */ void Executor::prepareReachableNodes() { - for (BuildGraphNode * const root : qAsConst(m_roots)) + for (BuildGraphNode * const root : std::as_const(m_roots)) prepareReachableNodes_impl(root); } @@ -1302,7 +1299,7 @@ void Executor::prepareReachableNodes_impl(BuildGraphNode *node) return; node->buildState = BuildGraphNode::Buildable; - for (BuildGraphNode *child : qAsConst(node->children)) + for (BuildGraphNode *child : std::as_const(node->children)) prepareReachableNodes_impl(child); } @@ -1310,7 +1307,7 @@ void Executor::prepareProducts() { ProductPrioritySetter prioritySetter(m_allProducts); prioritySetter.apply(); - for (const ResolvedProductPtr &product : m_productsToBuild) { + for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) { EnvironmentScriptRunner(product.get(), m_evalContext.get(), m_project->environment) .setupForBuild(); } @@ -1319,7 +1316,7 @@ void Executor::prepareProducts() void Executor::setupRootNodes() { m_roots.clear(); - for (const ResolvedProductPtr &product : m_productsToBuild) + for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) m_roots += product->buildData->rootNodes(); } diff --git a/src/lib/corelib/buildgraph/executor.h b/src/lib/corelib/buildgraph/executor.h index 1fd591176..cc879e125 100644 --- a/src/lib/corelib/buildgraph/executor.h +++ b/src/lib/corelib/buildgraph/executor.h @@ -81,7 +81,7 @@ public: ~Executor() override; void setProject(const TopLevelProjectPtr &project); - void setProducts(const std::vector<ResolvedProductPtr> &productsToBuild); + void setProducts(const QVector<ResolvedProductPtr> &productsToBuild); void setBuildOptions(const BuildOptions &buildOptions); void setProgressObserver(ProgressObserver *observer) { m_progressObserver = observer; } @@ -166,10 +166,11 @@ private: BuildOptions m_buildOptions; Logger m_logger; ProgressObserver *m_progressObserver; + std::vector<std::unique_ptr<ExecutorJob>> m_allJobs; QList<ExecutorJob*> m_availableJobs; ExecutorState m_state; TopLevelProjectPtr m_project; - std::vector<ResolvedProductPtr> m_productsToBuild; + QVector<ResolvedProductPtr> m_productsToBuild; std::vector<ResolvedProductPtr> m_allProducts; std::unordered_map<QString, const ResolvedProduct *> m_productsByName; std::unordered_map<QString, const ResolvedProject *> m_projectsByName; diff --git a/src/lib/corelib/buildgraph/executorjob.h b/src/lib/corelib/buildgraph/executorjob.h index bc8954072..1f8f0cd73 100644 --- a/src/lib/corelib/buildgraph/executorjob.h +++ b/src/lib/corelib/buildgraph/executorjob.h @@ -65,7 +65,7 @@ class ExecutorJob : public QObject { Q_OBJECT public: - ExecutorJob(const Logger &logger, QObject *parent); + explicit ExecutorJob(const Logger &logger, QObject *parent = nullptr); ~ExecutorJob() override; void setMainThreadScriptEngine(ScriptEngine *engine); diff --git a/src/lib/corelib/buildgraph/filedependency.h b/src/lib/corelib/buildgraph/filedependency.h index 802654e9f..93bfc5d05 100644 --- a/src/lib/corelib/buildgraph/filedependency.h +++ b/src/lib/corelib/buildgraph/filedependency.h @@ -77,8 +77,8 @@ private: FileTime m_timestamp; QString m_filePath; - QStringRef m_dirPath; - QStringRef m_fileName; + QStringView m_dirPath; + QStringView m_fileName; }; class FileDependency : public FileResourceBase diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp index 14f39bb2a..0c73f599f 100644 --- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp +++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp @@ -49,11 +49,12 @@ #include <language/language.h> #include <logging/categories.h> +#include <tools/error.h> #include <tools/fileinfo.h> -#include <tools/scannerpluginmanager.h> #include <tools/qbsassert.h> -#include <tools/error.h> #include <tools/qttools.h> +#include <tools/scannerpluginmanager.h> +#include <tools/stlutils.h> #include <QtCore/qdir.h> #include <QtCore/qstringlist.h> @@ -77,6 +78,7 @@ static void resolveDepencency(const RawScannedDependency &dependency, FileDependency *fileDependencyArtifact = nullptr; Artifact *dependencyInProduct = nullptr; Artifact *dependencyInOtherProduct = nullptr; + bool productOfDependencyIsDependency = false; const auto files = project->topLevelProject() ->buildData->lookupFiles(absDirPath, dependency.fileName()); for (FileResourceBase *lookupResult : files) { @@ -86,10 +88,13 @@ static void resolveDepencency(const RawScannedDependency &dependency, break; case FileResourceBase::FileTypeArtifact: { auto const foundArtifact = static_cast<Artifact *>(lookupResult); - if (foundArtifact->product == product) + if (foundArtifact->product == product) { dependencyInProduct = foundArtifact; - else + } else if (!productOfDependencyIsDependency) { dependencyInOtherProduct = foundArtifact; + productOfDependencyIsDependency + = contains(product->dependencies, dependencyInOtherProduct->product.lock()); + } break; } } @@ -98,10 +103,23 @@ static void resolveDepencency(const RawScannedDependency &dependency, } // prioritize found artifacts - if ((result->file = dependencyInProduct) - || (result->file = dependencyInOtherProduct) - || (result->file = fileDependencyArtifact)) { + if (dependencyInProduct) + result->file = dependencyInProduct; + else if (dependencyInOtherProduct) + result->file = dependencyInOtherProduct; + else + result->file = fileDependencyArtifact; + + if (result->file) { result->filePath = result->file->filePath(); + + if (result->file == dependencyInOtherProduct && !productOfDependencyIsDependency) { + qCDebug(lcDepScan) << "product" << dependencyInOtherProduct->product->fullDisplayName() + << "of scanned dependency" << result->filePath + << "is not a dependency of product" << product->fullDisplayName() + << ". The file dependency might get lost during change tracking."; + } + return; } @@ -145,7 +163,7 @@ void InputArtifactScanner::scan() for (Artifact * const dependency : childrenAddedByScanner) disconnect(m_artifact, dependency); - for (Artifact * const inputArtifact : qAsConst(m_artifact->transformer->inputs)) + for (Artifact * const inputArtifact : std::as_const(m_artifact->transformer->inputs)) scanForFileDependencies(inputArtifact); } @@ -200,19 +218,18 @@ Set<DependencyScanner *> InputArtifactScanner::scannersForArtifact(const Artifac InputArtifactScannerContext::DependencyScannerCacheItem &cache = scannerCache[fileTag]; if (!cache.valid) { cache.valid = true; - for (ScannerPlugin *scanner : ScannerPluginManager::scannersForFileTag(fileTag)) { - const auto pluginScanner = new PluginDependencyScanner(scanner); - cache.scanners.push_back(DependencyScannerPtr(pluginScanner)); - } + const auto scanners = ScannerPluginManager::scannersForFileTag(fileTag); + transform(scanners, cache.scanners, [](const auto &scanner) { + return std::make_shared<PluginDependencyScanner>(scanner); }); for (const ResolvedScannerConstPtr &scanner : product->scanners) { if (scanner->inputs.contains(fileTag)) { - cache.scanners.push_back(DependencyScannerPtr( - new UserDependencyScanner(scanner, engine))); + cache.scanners.push_back( + std::make_shared<UserDependencyScanner>(scanner, engine)); break; } } } - for (const DependencyScannerPtr &scanner : qAsConst(cache.scanners)) + for (const DependencyScannerPtr &scanner : std::as_const(cache.scanners)) scanners += scanner.get(); } return scanners; @@ -231,21 +248,16 @@ void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *sca cache.searchPaths = scanner->collectSearchPaths(inputArtifact); } qCDebug(lcDepScan) << "include paths (cache" << (cacheHit ? "hit)" : "miss)"); - for (const QString &s : qAsConst(cache.searchPaths)) + for (const QString &s : std::as_const(cache.searchPaths)) qCDebug(lcDepScan) << " " << s; const QString &filePathToBeScanned = fileToBeScanned->filePath(); RawScanResults::ScanData &scanData = m_rawScanResults.findScanData(fileToBeScanned, scanner, m_artifact->properties); if (scanData.lastScanTime < fileToBeScanned->timestamp()) { - try { - qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned); - scanWithScannerPlugin(scanner, inputArtifact, fileToBeScanned, &scanData.rawScanResult); - scanData.lastScanTime = FileTime::currentTime(); - } catch (const ErrorInfo &error) { - m_logger.printWarning(error); - return; - } + qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned); + scanWithScannerPlugin(scanner, inputArtifact, fileToBeScanned, &scanData.rawScanResult); + scanData.lastScanTime = FileTime::currentTime(); } resolveScanResultDependencies(inputArtifact, scanData.rawScanResult, filesToScan, cache); @@ -255,38 +267,45 @@ void InputArtifactScanner::resolveScanResultDependencies(const Artifact *inputAr const RawScanResult &scanResult, QList<FileResourceBase *> *artifactsToScan, InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache) { - for (const RawScannedDependency &dependency : scanResult.deps) { + auto getResolvedDependency = [inputArtifact, &cache](const RawScannedDependency &dependency) + -> ResolvedDependency* + { const QString &dependencyFilePath = dependency.filePath(); InputArtifactScannerContext::ResolvedDependencyCacheItem &cachedResolvedDependencyItem = cache.resolvedDependenciesCache[dependency.dirPath()][dependency.fileName()]; ResolvedDependency &resolvedDependency = cachedResolvedDependencyItem.resolvedDependency; if (cachedResolvedDependencyItem.valid) { if (resolvedDependency.filePath.isEmpty()) - goto unresolved; - goto resolved; + return nullptr; + return &resolvedDependency; } cachedResolvedDependencyItem.valid = true; if (FileInfo::isAbsolute(dependencyFilePath)) { resolveDepencency(dependency, inputArtifact->product.get(), &resolvedDependency); if (resolvedDependency.filePath.isEmpty()) - goto unresolved; - goto resolved; + return nullptr; + return &resolvedDependency; } // try include paths - for (const QString &includePath : qAsConst(cache.searchPaths)) { + for (const QString &includePath : std::as_const(cache.searchPaths)) { resolveDepencency(dependency, inputArtifact->product.get(), &resolvedDependency, includePath); if (resolvedDependency.isValid()) - goto resolved; + return &resolvedDependency; } + return nullptr; + }; -unresolved: - qCWarning(lcDepScan) << "unresolved dependency " << dependencyFilePath; - continue; + for (const RawScannedDependency &dependency : scanResult.deps) { + const auto maybeResolvedDependency = getResolvedDependency(dependency); + if (!maybeResolvedDependency) { + qCWarning(lcDepScan) << "unresolved dependency " << dependency.filePath(); + continue; + } + auto &resolvedDependency = *maybeResolvedDependency; -resolved: handleDependency(resolvedDependency); if (artifactsToScan && resolvedDependency.file) { if (resolvedDependency.file->fileType() == FileResourceBase::FileTypeArtifact) { @@ -349,6 +368,8 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency) if (m_artifact == dependency.file) return; + if (artifactDependency && artifactDependency->transformer == m_artifact->transformer) + return; if (fileDependency) { m_artifact->fileDependencies << fileDependency; diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp index aa72a7b5c..4d92bf7c3 100644 --- a/src/lib/corelib/buildgraph/jscommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp @@ -40,7 +40,6 @@ #include "jscommandexecutor.h" -#include "artifact.h" #include "buildgraph.h" #include "rulecommands.h" #include "transformer.h" @@ -53,8 +52,12 @@ #include <tools/codelocation.h> #include <tools/error.h> #include <tools/qbsassert.h> +#include <tools/qttools.h> + +#include <quickjs.h> #include <QtCore/qeventloop.h> +#include <QtCore/qmutex.h> #include <QtCore/qpointer.h> #include <QtCore/qthread.h> #include <QtCore/qtimer.h> @@ -86,10 +89,11 @@ public: void cancel(const qbs::ErrorInfo &reason) { + QMutexLocker locker(&m_resultMutex); m_result.success = !reason.hasError(); m_result.errorMessage = reason.toString(); if (m_scriptEngine) - m_scriptEngine->abortEvaluation(); + m_scriptEngine->cancel(); m_cancelled = true; } @@ -121,40 +125,34 @@ private: m_result.success = true; m_result.errorMessage.clear(); ScriptEngine * const scriptEngine = provideScriptEngine(); - QScriptValue scope = scriptEngine->newObject(); - scope.setPrototype(scriptEngine->globalObject()); - m_scriptEngine->clearRequestedProperties(); + JSContext * const ctx = scriptEngine->context(); + const ScopedJsValue scope(ctx, JS_NewObject(scriptEngine->context())); setupScriptEngineForFile(scriptEngine, transformer->rule->prepareScript.fileContext(), scope, ObserveMode::Enabled); - - QScriptValue importScopeForSourceCode; + ScopedJsValue importScopeForSourceCode(ctx, JS_UNDEFINED); if (!cmd->scopeName().isEmpty()) - importScopeForSourceCode = scope.property(cmd->scopeName()); + importScopeForSourceCode.setValue(getJsProperty(ctx, scope, cmd->scopeName())); setupScriptEngineForProduct(scriptEngine, transformer->product().get(), transformer->rule->module.get(), scope, true); - transformer->setupInputs(scope); - transformer->setupOutputs(scope); - transformer->setupExplicitlyDependsOn(scope); - - for (QVariantMap::const_iterator it = cmd->properties().constBegin(); - it != cmd->properties().constEnd(); ++it) { - scope.setProperty(it.key(), scriptEngine->toScriptValue(it.value())); - } - - scriptEngine->setGlobalObject(scope); - if (importScopeForSourceCode.isObject()) - scriptEngine->currentContext()->pushScope(importScopeForSourceCode); - scriptEngine->evaluate(cmd->sourceCode()); - scriptEngine->releaseResourcesOfScriptObjects(); - if (importScopeForSourceCode.isObject()) - scriptEngine->currentContext()->popScope(); - scriptEngine->setGlobalObject(scope.prototype()); + transformer->setupInputs(scriptEngine, scope); + transformer->setupOutputs(scriptEngine, scope); + transformer->setupExplicitlyDependsOn(scriptEngine, scope); + + for (auto it = cmd->properties().constBegin(); it != cmd->properties().constEnd(); ++it) + setJsProperty(ctx, scope, it.key(), scriptEngine->toScriptValue(it.value())); + + const TemporaryGlobalObjectSetter gos(scriptEngine, scope); + JSValueList scopeChain; + if (JS_IsObject(importScopeForSourceCode)) + scopeChain << importScopeForSourceCode; + const ScopedJsValue res(ctx, scriptEngine->evaluate(JsValueOwner::Caller, cmd->sourceCode(), + {}, 1, scopeChain)); transformer->propertiesRequestedInCommands += scriptEngine->propertiesRequestedInScript(); - transformer->propertiesRequestedFromArtifactInCommands - .unite(scriptEngine->propertiesRequestedFromArtifact()); + unite(transformer->propertiesRequestedFromArtifactInCommands, + scriptEngine->propertiesRequestedFromArtifact()); const std::vector<QString> &importFilesUsedInCommand = scriptEngine->importedFilesUsedInScript(); transformer->importedFilesUsedInCommands.insert( @@ -167,14 +165,17 @@ private: std::make_pair(p->uniqueName(), p->exportedModule)); } scriptEngine->clearRequestedProperties(); - if (scriptEngine->hasUncaughtException()) { + if (const JsException exception = scriptEngine->checkAndClearException(cmd->codeLocation())) { // ### We don't know the line number of the command's sourceCode property assignment. - setError(scriptEngine->uncaughtException().toString(), cmd->codeLocation()); + setError(exception.message(), cmd->codeLocation()); } } void setError(const QString &errorMessage, const CodeLocation &codeLocation) { + QMutexLocker locker(&m_resultMutex); + if (m_cancelled) + return; m_result.success = false; m_result.errorMessage = errorMessage; m_result.errorLocation = codeLocation; @@ -183,13 +184,16 @@ private: ScriptEngine *provideScriptEngine() { if (!m_scriptEngine) - m_scriptEngine = ScriptEngine::create(m_logger, EvalContext::JsCommand, this); - return m_scriptEngine; + m_scriptEngine = ScriptEngine::create(m_logger, EvalContext::JsCommand); + else + m_scriptEngine->reset(); + return m_scriptEngine.get(); } Logger m_logger; - ScriptEngine *m_scriptEngine; + std::unique_ptr<ScriptEngine> m_scriptEngine; JavaScriptCommandResult m_result; + QMutex m_resultMutex; bool m_running = false; bool m_cancelled = false; }; @@ -201,6 +205,9 @@ JsCommandExecutor::JsCommandExecutor(const Logger &logger, QObject *parent) , m_objectInThread(new JsCommandExecutorThreadObject(logger)) , m_running(false) { + qRegisterMetaType<Transformer *>("Transformer *"); + qRegisterMetaType<const JavaScriptCommand *>("const JavaScriptCommand *"); + m_objectInThread->moveToThread(m_thread); connect(m_objectInThread, &JsCommandExecutorThreadObject::finished, this, &JsCommandExecutor::onJavaScriptCommandFinished); @@ -254,11 +261,8 @@ bool JsCommandExecutor::doStart() void JsCommandExecutor::cancel(const qbs::ErrorInfo &reason) { - if (m_running && (!dryRun() || command()->ignoreDryRun())) - QTimer::singleShot(0, m_objectInThread, [objectInThread = QPointer<JsCommandExecutorThreadObject>{m_objectInThread}, reason] { - if (objectInThread) - objectInThread->cancel(reason); - }); + if (m_running && (!dryRun() || command()->ignoreDryRun()) && m_objectInThread) + m_objectInThread->cancel(reason); } void JsCommandExecutor::onJavaScriptCommandFinished() diff --git a/src/lib/corelib/buildgraph/nodeset.h b/src/lib/corelib/buildgraph/nodeset.h index 961a4bcc3..469988b19 100644 --- a/src/lib/corelib/buildgraph/nodeset.h +++ b/src/lib/corelib/buildgraph/nodeset.h @@ -71,11 +71,17 @@ public: { } - class const_iterator : public std::iterator<std::forward_iterator_tag, T *> + class const_iterator { const NodeSet &m_nodes; NodeSet::const_iterator m_it; public: + using value_type = T*; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + const_iterator(const NodeSet &nodes, const NodeSet::const_iterator &it) : m_nodes(nodes), m_it(it) { diff --git a/src/lib/corelib/buildgraph/nodetreedumper.cpp b/src/lib/corelib/buildgraph/nodetreedumper.cpp index 89975baf2..6ad597c70 100644 --- a/src/lib/corelib/buildgraph/nodetreedumper.cpp +++ b/src/lib/corelib/buildgraph/nodetreedumper.cpp @@ -51,13 +51,13 @@ namespace qbs { namespace Internal { -static unsigned int indentWidth() { return 4; } +static int indentWidth() { return 4; } NodeTreeDumper::NodeTreeDumper(QIODevice &outDevice) : m_outDevice(outDevice) { } -void NodeTreeDumper::start(const QList<ResolvedProductPtr> &products) +void NodeTreeDumper::start(const QVector<ResolvedProductPtr> &products) { m_indentation = 0; for (const ResolvedProductPtr &p : products) { @@ -120,7 +120,7 @@ bool NodeTreeDumper::doVisit(BuildGraphNode *node, const QString &nodeRepr) QByteArray NodeTreeDumper::indentation() const { - return QByteArray(m_indentation, ' '); + return {m_indentation, ' '}; } } // namespace Internal diff --git a/src/lib/corelib/buildgraph/nodetreedumper.h b/src/lib/corelib/buildgraph/nodetreedumper.h index 4175ce727..38ccd6dae 100644 --- a/src/lib/corelib/buildgraph/nodetreedumper.h +++ b/src/lib/corelib/buildgraph/nodetreedumper.h @@ -57,7 +57,7 @@ class NodeTreeDumper : public BuildGraphVisitor public: NodeTreeDumper(QIODevice &outDevice); - void start(const QList<ResolvedProductPtr> &products); + void start(const QVector<ResolvedProductPtr> &products); private: bool visit(Artifact *artifact) override; @@ -74,7 +74,7 @@ private: QIODevice &m_outDevice; ResolvedProductPtr m_currentProduct; NodeSet m_visited; - unsigned int m_indentation = 0; + int m_indentation = 0; }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp index 79edda320..52a8fe75d 100644 --- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp @@ -61,19 +61,19 @@ #include <tools/shellutils.h> #include <tools/stringconstants.h> +#include <quickjs.h> + #include <QtCore/qdir.h> #include <QtCore/qtemporaryfile.h> #include <QtCore/qtimer.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { ProcessCommandExecutor::ProcessCommandExecutor(const Logger &logger, QObject *parent) : AbstractCommandExecutor(logger, parent) { - connect(&m_process, static_cast<void (QbsProcess::*)(QProcess::ProcessError)>(&QbsProcess::error), + connect(&m_process, &QbsProcess::errorOccurred, this, &ProcessCommandExecutor::onProcessError); connect(&m_process, static_cast<void (QbsProcess::*)(int)>(&QbsProcess::finished), this, &ProcessCommandExecutor::onProcessFinished); @@ -166,6 +166,7 @@ bool ProcessCommandExecutor::doStart() .arg(responseFile.fileName()))); return false; } + const auto separator = cmd->responseFileSeparator().toUtf8(); for (int i = cmd->responseFileArgumentIndex(); i < cmd->arguments().size(); ++i) { const QString arg = cmd->arguments().at(i); if (arg.startsWith(cmd->responseFileUsagePrefix())) { @@ -179,12 +180,12 @@ bool ProcessCommandExecutor::doStart() } else { responseFile.write(qbs::Internal::shellQuote(arg).toLocal8Bit()); } - responseFile.write("\n"); + responseFile.write(separator); } responseFile.close(); m_responseFileName = responseFile.fileName(); - arguments = arguments.mid(0, - std::min(cmd->responseFileArgumentIndex(), arguments.size())); + arguments = arguments.mid(0, std::min<int>(cmd->responseFileArgumentIndex(), + arguments.size())); arguments += QDir::toNativeSeparators(cmd->responseFileUsagePrefix() + responseFile.fileName()); } @@ -210,38 +211,43 @@ void ProcessCommandExecutor::cancel(const qbs::ErrorInfo &reason) QString ProcessCommandExecutor::filterProcessOutput(const QByteArray &_output, const QString &filterFunctionSource) { - const QString output = QString::fromLocal8Bit(_output); + QString output = QString::fromLocal8Bit(_output); if (filterFunctionSource.isEmpty()) return output; - QScriptValue scope = scriptEngine()->newObject(); - scope.setPrototype(scriptEngine()->globalObject()); - for (QVariantMap::const_iterator it = command()->properties().constBegin(); - it != command()->properties().constEnd(); ++it) { - scope.setProperty(it.key(), scriptEngine()->toScriptValue(it.value())); + JSContext * const ctx = scriptEngine()->context(); + const ScopedJsValue scope(ctx, JS_NewObjectProto(ctx, scriptEngine()->globalObject())); + for (auto it = command()->properties().constBegin(); + it != command()->properties().constEnd(); ++it) { + setJsProperty(ctx, scope, it.key(), scriptEngine()->toScriptValue(it.value())); } - TemporaryGlobalObjectSetter tgos(scope); - QScriptValue filterFunction = scriptEngine()->evaluate(QLatin1String("var f = ") - + filterFunctionSource - + QLatin1String("; f")); - if (!filterFunction.isFunction()) { + TemporaryGlobalObjectSetter tgos(scriptEngine(), scope); + const ScopedJsValue filterFunction( + ctx, + scriptEngine()->evaluate(JsValueOwner::Caller, + QLatin1String("var f = ") + + filterFunctionSource + + QLatin1String("; f"))); + if (!JS_IsFunction(scriptEngine()->context(), filterFunction)) { logger().printWarning(ErrorInfo(Tr::tr("Error in filter function: %1.\n%2") - .arg(filterFunctionSource, filterFunction.toString()))); + .arg(filterFunctionSource, + getJsString(scriptEngine()->context(), filterFunction)))); return output; } - QScriptValue outputArg = scriptEngine()->newArray(1); - outputArg.setProperty(0, scriptEngine()->toScriptValue(output)); - QScriptValue filteredOutput = filterFunction.call(scriptEngine()->undefinedValue(), outputArg); - if (scriptEngine()->hasErrorOrException(filteredOutput)) { - logger().printWarning(ErrorInfo(Tr::tr("Error when calling output filter function: %1") - .arg(scriptEngine()->lastErrorString(filteredOutput)), - scriptEngine()->lastErrorLocation(filteredOutput))); + const ScopedJsValue outputArg(ctx, scriptEngine()->toScriptValue(output)); + JSValue outputArgForCall = outputArg; + const ScopedJsValue filteredOutput( + ctx, JS_Call(ctx, filterFunction, JS_UNDEFINED, 1, &outputArgForCall)); + if (const JsException ex = scriptEngine()->checkAndClearException({})) { + ErrorInfo err = ex.toErrorInfo(); + err.prepend(Tr::tr("Error when calling output filter function")); + logger().printWarning(err); return output; } - return filteredOutput.toString(); + return getJsString(ctx, filteredOutput); } static QProcess::ProcessError saveToFile(const QString &filePath, const QByteArray &content) @@ -285,7 +291,7 @@ void ProcessCommandExecutor::getProcessOutput(bool stdOut, ProcessResult &result } else { if (!contentString.isEmpty() && contentString.endsWith(QLatin1Char('\n'))) contentString.chop(1); - *target = contentString.split(QLatin1Char('\n'), QString::SkipEmptyParts); + *target = contentString.split(QLatin1Char('\n'), Qt::SkipEmptyParts); } } @@ -392,7 +398,7 @@ void ProcessCommandExecutor::doReportCommandDescription(const QString &productNa if (m_echoMode == CommandEchoModeCommandLineWithEnvironment) { QStringList keys = m_commandEnvironment.keys(); keys.sort(); - for (const QString &key : keys) + for (const QString &key : std::as_const(keys)) fullInvocation += environmentVariableString(key, m_commandEnvironment.value(key)); } fullInvocation += m_shellInvocation; diff --git a/src/lib/corelib/buildgraph/productbuilddata.cpp b/src/lib/corelib/buildgraph/productbuilddata.cpp index db51b2b9f..6ec01a1ce 100644 --- a/src/lib/corelib/buildgraph/productbuilddata.cpp +++ b/src/lib/corelib/buildgraph/productbuilddata.cpp @@ -102,7 +102,6 @@ void ProductBuildData::addFileTagToArtifact(Artifact *artifact, const FileTag &t ArtifactSetByFileTag ProductBuildData::artifactsByFileTag() const { - std::lock_guard<std::mutex> l(m_artifactsMapMutex); return m_artifactsByFileTag; } @@ -124,7 +123,6 @@ void ProductBuildData::addRescuableArtifactData(const QString &filePath, bool ProductBuildData::checkAndSetJsArtifactsMapUpToDateFlag() { - std::lock_guard<std::mutex> l(m_artifactsMapMutex); if (!m_jsArtifactsMapUpToDate) { m_jsArtifactsMapUpToDate = true; return false; diff --git a/src/lib/corelib/buildgraph/productbuilddata.h b/src/lib/corelib/buildgraph/productbuilddata.h index a7660af27..f6c531713 100644 --- a/src/lib/corelib/buildgraph/productbuilddata.h +++ b/src/lib/corelib/buildgraph/productbuilddata.h @@ -86,6 +86,9 @@ public: void setBuildPriority(unsigned int prio) { m_buildPriority = prio; } bool checkAndSetJsArtifactsMapUpToDateFlag(); + std::unique_lock<std::mutex> getArtifactsMapLock() { + return std::unique_lock(m_artifactsMapMutex); + } template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { diff --git a/src/lib/corelib/buildgraph/productinstaller.cpp b/src/lib/corelib/buildgraph/productinstaller.cpp index d4acc9ace..f757c8a85 100644 --- a/src/lib/corelib/buildgraph/productinstaller.cpp +++ b/src/lib/corelib/buildgraph/productinstaller.cpp @@ -60,7 +60,7 @@ namespace qbs { namespace Internal { ProductInstaller::ProductInstaller(TopLevelProjectPtr project, - std::vector<ResolvedProductPtr> products, InstallOptions options, + QVector<ResolvedProductPtr> products, InstallOptions options, ProgressObserver *observer, Logger logger) : m_project(std::move(project)), m_products(std::move(products)), @@ -96,7 +96,7 @@ void ProductInstaller::install() removeInstallRoot(); QList<const Artifact *> artifactsToInstall; - for (const ResolvedProductConstPtr &product : qAsConst(m_products)) { + for (const auto &product : std::as_const(m_products)) { QBS_CHECK(product->buildData); for (const Artifact *artifact : filterByType<Artifact>(product->buildData->allNodes())) { if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) @@ -105,7 +105,7 @@ void ProductInstaller::install() } m_observer->initialize(Tr::tr("Installing"), artifactsToInstall.size()); - for (const Artifact * const a : qAsConst(artifactsToInstall)) { + for (const Artifact * const a : std::as_const(artifactsToInstall)) { copyFile(a); m_observer->incrementProgressValue(); } @@ -250,5 +250,5 @@ void ProductInstaller::handleError(const QString &message) m_logger.qbsWarning() << message; } -} // namespace Intern +} // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/productinstaller.h b/src/lib/corelib/buildgraph/productinstaller.h index 09828cfe9..c07e0b7cf 100644 --- a/src/lib/corelib/buildgraph/productinstaller.h +++ b/src/lib/corelib/buildgraph/productinstaller.h @@ -56,7 +56,7 @@ class ProductInstaller { public: ProductInstaller(TopLevelProjectPtr project, - std::vector<ResolvedProductPtr> products, + QVector<ResolvedProductPtr> products, InstallOptions options, ProgressObserver *observer, Logger logger); void install(); @@ -72,7 +72,7 @@ private: void handleError(const QString &message); const TopLevelProjectConstPtr m_project; - const std::vector<ResolvedProductPtr> m_products; + const QVector<ResolvedProductPtr> m_products; InstallOptions m_options; ProgressObserver * const m_observer; Logger m_logger; diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp index 0c7f7bfdc..36ac75331 100644 --- a/src/lib/corelib/buildgraph/projectbuilddata.cpp +++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp @@ -157,7 +157,7 @@ void ProjectBuildData::insertFileDependency(FileDependency *dependency) static void disconnectArtifactChildren(Artifact *artifact) { qCDebug(lcBuildGraph) << "disconnect children of" << relativeArtifactFileName(artifact); - for (BuildGraphNode * const child : qAsConst(artifact->children)) + for (BuildGraphNode * const child : std::as_const(artifact->children)) child->parents.remove(artifact); artifact->children.clear(); artifact->childrenAddedByScanner.clear(); @@ -166,7 +166,7 @@ static void disconnectArtifactChildren(Artifact *artifact) static void disconnectArtifactParents(Artifact *artifact) { qCDebug(lcBuildGraph) << "disconnect parents of" << relativeArtifactFileName(artifact); - for (BuildGraphNode * const parent : qAsConst(artifact->parents)) { + for (BuildGraphNode * const parent : std::as_const(artifact->parents)) { parent->children.remove(artifact); if (parent->type() != BuildGraphNode::ArtifactNodeType) continue; @@ -240,7 +240,6 @@ void ProjectBuildData::removeArtifact(Artifact *artifact, artifact->transformer->outputs.remove(artifact); if (removeFromProduct) artifact->product->buildData->removeArtifact(artifact); - m_isDirty = false; } void ProjectBuildData::setDirty() @@ -258,7 +257,7 @@ void ProjectBuildData::setClean() void ProjectBuildData::load(PersistentPool &pool) { serializationOp<PersistentPool::Load>(pool); - for (FileDependency * const dep : qAsConst(fileDependencies)) + for (FileDependency * const dep : std::as_const(fileDependencies)) insertIntoLookupTable(dep); m_isDirty = false; } @@ -338,7 +337,7 @@ private: { if (!m_rulesOnPath.insert(rule.get()).second) { QString pathstr; - for (const Rule *r : qAsConst(m_rulePath)) { + for (const Rule *r : std::as_const(m_rulePath)) { pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t') + r->prepareScript.location().toString(); } @@ -395,7 +394,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc product->buildData = std::make_unique<ProductBuildData>(); ArtifactSetByFileTag artifactsPerFileTag; - for (const auto &dependency : qAsConst(product->dependencies)) { + for (const auto &dependency : std::as_const(product->dependencies)) { QBS_CHECK(dependency->enabled); resolveProductBuildData(dependency); } @@ -413,7 +412,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc artifactsPerFileTag["qbs"].insert(qbsFileArtifact); // read sources - for (const SourceArtifactConstPtr &sourceArtifact : product->allEnabledFiles()) { + for (const auto &sourceArtifact : product->allEnabledFiles()) { QString filePath = sourceArtifact->absoluteFilePath; if (lookupArtifact(product, filePath)) continue; // ignore duplicate artifacts @@ -445,7 +444,7 @@ void BuildDataResolver::connectRulesToDependencies(const ResolvedProductPtr &pro std::vector<RuleNode *> ruleNodes; for (RuleNode *ruleNode : filterByType<RuleNode>(product->buildData->allNodes())) ruleNodes.push_back(ruleNode); - for (const ResolvedProductConstPtr &dep : dependencies) { + for (const auto &dep : dependencies) { if (!dep->buildData) continue; for (RuleNode *depRuleNode : filterByType<RuleNode>(dep->buildData->allNodes())) { @@ -474,7 +473,7 @@ ScriptEngine *BuildDataResolver::engine() const return evalContext()->engine(); } -QScriptValue BuildDataResolver::scope() const +JSValue BuildDataResolver::scope() const { return evalContext()->scope(); } diff --git a/src/lib/corelib/buildgraph/projectbuilddata.h b/src/lib/corelib/buildgraph/projectbuilddata.h index 930344435..416651912 100644 --- a/src/lib/corelib/buildgraph/projectbuilddata.h +++ b/src/lib/corelib/buildgraph/projectbuilddata.h @@ -47,11 +47,11 @@ #include <tools/set.h> #include <tools/qttools.h> +#include <quickjs.h> + #include <QtCore/qlist.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptvalue.h> - #include <unordered_map> namespace qbs { @@ -126,7 +126,7 @@ private: RulesEvaluationContextPtr evalContext() const; ScriptEngine *engine() const; - QScriptValue scope() const; + JSValue scope() const; TopLevelProjectPtr m_project; Logger m_logger; diff --git a/src/lib/corelib/buildgraph/qtmocscanner.cpp b/src/lib/corelib/buildgraph/qtmocscanner.cpp index 4e054a636..7df84e52c 100644 --- a/src/lib/corelib/buildgraph/qtmocscanner.cpp +++ b/src/lib/corelib/buildgraph/qtmocscanner.cpp @@ -54,9 +54,6 @@ #include <QtCore/qdebug.h> -#include <QtScript/qscriptcontext.h> -#include <QtScript/qscriptengine.h> - namespace qbs { namespace Internal { @@ -102,21 +99,23 @@ private: static QString qtMocScannerJsName() { return QStringLiteral("QtMocScanner"); } -QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue) - : m_tags(*commonFileTags()) +QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, ScriptEngine *engine, JSValue targetScriptValue) + : m_engine(engine) + , m_tags(*commonFileTags()) , m_product(product) - , m_targetScriptValue(targetScriptValue) + , m_targetScriptValue(JS_DupValue(engine->context(), targetScriptValue)) { - const auto engine = static_cast<ScriptEngine *>(targetScriptValue.engine()); - QScriptValue scannerObj = engine->newObject(); - targetScriptValue.setProperty(qtMocScannerJsName(), scannerObj); - QScriptValue applyFunction = engine->newFunction(&js_apply, this); - scannerObj.setProperty(QStringLiteral("apply"), applyFunction); + JSValue scannerObj = JS_NewObjectClass(engine->context(), engine->dataWithPtrClass()); + attachPointerTo(scannerObj, this); + setJsProperty(engine->context(), targetScriptValue, qtMocScannerJsName(), scannerObj); + JSValue applyFunction = JS_NewCFunction(engine->context(), &js_apply, "QtMocScanner", 1); + setJsProperty(engine->context(), scannerObj, QStringLiteral("apply"), applyFunction); } QtMocScanner::~QtMocScanner() { - m_targetScriptValue.setProperty(qtMocScannerJsName(), QScriptValue()); + setJsProperty(m_engine->context(), m_targetScriptValue, qtMocScannerJsName(), JS_UNDEFINED); + JS_FreeValue(m_engine->context(), m_targetScriptValue); } static RawScanResult runScanner(ScannerPlugin *scanner, const Artifact *artifact) @@ -153,7 +152,7 @@ static RawScanResult runScanner(ScannerPlugin *scanner, const Artifact *artifact } QString baseDirOfInFilePath = artifact->dirPath(); - forever { + for (;;) { int flags = 0; const char *szOutFilePath = scanner->next(opaq, &length, &flags); if (szOutFilePath == nullptr) @@ -199,22 +198,24 @@ void QtMocScanner::findIncludedMocCppFiles() } } -QScriptValue QtMocScanner::js_apply(QScriptContext *ctx, QScriptEngine *engine, - QtMocScanner *that) +JSValue QtMocScanner::js_apply(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - QScriptValue input = ctx->argument(0); - return that->apply(engine, attachedPointer<Artifact>(input)); + if (argc < 1) + return throwError(ctx, Tr::tr("QtMocScanner.apply() requires an argument.")); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto scanner = attachedPointer<QtMocScanner>(this_val, engine->dataWithPtrClass()); + return scanner->apply(engine, attachedPointer<Artifact>(argv[0], engine->dataWithPtrClass())); } -static QScriptValue scannerCountError(QScriptEngine *engine, size_t scannerCount, - const QString &fileTag) +static JSValue scannerCountError(ScriptEngine *engine, size_t scannerCount, + const QString &fileTag) { - return engine->currentContext()->throwError( - Tr::tr("There are %1 scanners for the file tag %2. " - "Expected is exactly one.").arg(scannerCount).arg(fileTag)); + return throwError(engine->context(), + Tr::tr("There are %1 scanners for the file tag %2. " + "Expected is exactly one.").arg(scannerCount).arg(fileTag)); } -QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact) +JSValue QtMocScanner::apply(ScriptEngine *engine, const Artifact *artifact) { if (!m_cppScanner) { auto scanners = ScannerPluginManager::scannersForFileTag(m_tags.cpp); @@ -266,11 +267,13 @@ QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact << "mustCompile:" << mustCompile << "hasPluginMetaDataMacro:" << hasPluginMetaDataMacro; - QScriptValue obj = engine->newObject(); - obj.setProperty(QStringLiteral("hasQObjectMacro"), hasQObjectMacro); - obj.setProperty(QStringLiteral("mustCompile"), mustCompile); - obj.setProperty(QStringLiteral("hasPluginMetaDataMacro"), hasPluginMetaDataMacro); - static_cast<ScriptEngine *>(engine)->setUsesIo(); + JSValue obj = engine->newObject(); + JSContext * const ctx = m_engine->context(); + setJsProperty(ctx, obj, QStringLiteral("hasQObjectMacro"), JS_NewBool(ctx, hasQObjectMacro)); + setJsProperty(ctx, obj, QStringLiteral("mustCompile"), JS_NewBool(ctx, mustCompile)); + setJsProperty(ctx, obj, QStringLiteral("hasPluginMetaDataMacro"), + JS_NewBool(ctx, hasPluginMetaDataMacro)); + engine->setUsesIo(); return obj; } diff --git a/src/lib/corelib/buildgraph/qtmocscanner.h b/src/lib/corelib/buildgraph/qtmocscanner.h index 6455383f3..2e2a00007 100644 --- a/src/lib/corelib/buildgraph/qtmocscanner.h +++ b/src/lib/corelib/buildgraph/qtmocscanner.h @@ -41,12 +41,11 @@ #define QBS_QTMOCSCANNER_H #include <language/language.h> +#include <quickjs.h> #include <QtCore/qhash.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptvalue.h> - QT_BEGIN_NAMESPACE class QScriptContext; QT_END_NAMESPACE @@ -55,6 +54,7 @@ class ScannerPlugin; namespace qbs { namespace Internal { +class ScriptEngine; class Artifact; struct CommonFileTags; @@ -62,17 +62,19 @@ struct CommonFileTags; class QtMocScanner { public: - explicit QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue); + explicit QtMocScanner(const ResolvedProductPtr &product, ScriptEngine *engine, + JSValue targetScriptValue); ~QtMocScanner(); private: void findIncludedMocCppFiles(); - static QScriptValue js_apply(QScriptContext *ctx, QScriptEngine *engine, QtMocScanner *that); - QScriptValue apply(QScriptEngine *engine, const Artifact *artifact); + static JSValue js_apply(JSContext *ctx, JSValue this_val, int argc, JSValue *argv); + JSValue apply(ScriptEngine *engine, const Artifact *artifact); + ScriptEngine * const m_engine; const CommonFileTags &m_tags; const ResolvedProductPtr &m_product; - QScriptValue m_targetScriptValue; + JSValue m_targetScriptValue; QHash<QString, QString> m_includedMocCppFiles; ScannerPlugin *m_cppScanner = nullptr; }; diff --git a/src/lib/corelib/buildgraph/requesteddependencies.cpp b/src/lib/corelib/buildgraph/requesteddependencies.cpp index f993b2518..b95c8db94 100644 --- a/src/lib/corelib/buildgraph/requesteddependencies.cpp +++ b/src/lib/corelib/buildgraph/requesteddependencies.cpp @@ -48,9 +48,9 @@ namespace Internal { static Set<QString> depNamesForProduct(const ResolvedProduct *p) { Set<QString> names; - for (const ResolvedProductConstPtr &dep : p->dependencies) + for (const auto &dep : p->dependencies) names.insert(dep->uniqueName()); - for (const ResolvedModuleConstPtr &m : p->modules) { + for (const auto &m : p->modules) { if (!m->isProduct) names.insert(m->name); } @@ -73,7 +73,7 @@ bool RequestedDependencies::isUpToDate(const TopLevelProject *project) const { if (m_depsPerProduct.empty()) return true; - for (const ResolvedProductConstPtr &product : project->allProducts()) { + for (const auto &product : project->allProducts()) { const auto it = m_depsPerProduct.find(product->uniqueName()); if (it == m_depsPerProduct.cend()) continue; diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.h b/src/lib/corelib/buildgraph/rescuableartifactdata.h index 3e4d6e25f..6dd10f76c 100644 --- a/src/lib/corelib/buildgraph/rescuableartifactdata.h +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.h @@ -84,9 +84,12 @@ public: struct ChildData { - ChildData(const QString &n = QString(), const QString &m = QString(), - const QString &c = QString(), bool byScanner = false) - : productName(n), productMultiplexId(m), childFilePath(c), addedByScanner(byScanner) + ChildData(QString n = QString(), QString m = QString(), + QString c = QString(), bool byScanner = false) + : productName(std::move(n)) + , productMultiplexId(std::move(m)) + , childFilePath(std::move(c)) + , addedByScanner(byScanner) {} template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) diff --git a/src/lib/corelib/buildgraph/rulecommands.cpp b/src/lib/corelib/buildgraph/rulecommands.cpp index c09c31c2c..be90c2fd5 100644 --- a/src/lib/corelib/buildgraph/rulecommands.cpp +++ b/src/lib/corelib/buildgraph/rulecommands.cpp @@ -39,6 +39,8 @@ ****************************************************************************/ #include "rulecommands.h" + +#include <language/scriptengine.h> #include <logging/translator.h> #include <tools/error.h> #include <tools/fileinfo.h> @@ -48,9 +50,6 @@ #include <QtCore/qfile.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalueiterator.h> - namespace qbs { namespace Internal { @@ -70,6 +69,7 @@ static QString responseFileUsagePrefixProperty() { return QStringLiteral("responseFileUsagePrefix"); } +static QString responseFileSeparatorProperty() { return QStringLiteral("responseFileSeparator"); } static QString silentProperty() { return QStringLiteral("silent"); } static QString stderrFilePathProperty() { return QStringLiteral("stderrFilePath"); } static QString stderrFilterFunctionProperty() { return QStringLiteral("stderrFilterFunction"); } @@ -78,10 +78,11 @@ static QString stdoutFilterFunctionProperty() { return QStringLiteral("stdoutFil static QString timeoutProperty() { return QStringLiteral("timeout"); } static QString workingDirProperty() { return QStringLiteral("workingDirectory"); } -static QString invokedSourceCode(const QScriptValue codeOrFunction) +static QString invokedSourceCode(JSContext *ctx, const JSValue &codeOrFunction) { - const QString &code = codeOrFunction.toString(); - return codeOrFunction.isFunction() ? QStringLiteral("(") + code + QStringLiteral(")()") : code; + const QString &code = getJsString(ctx, codeOrFunction); + return JS_IsFunction(ctx, codeOrFunction) + ? QStringLiteral("(") + code + QStringLiteral(")()") : code; } AbstractCommand::AbstractCommand() @@ -98,28 +99,25 @@ AbstractCommand::~AbstractCommand() = default; bool AbstractCommand::equals(const AbstractCommand *other) const { - return type() == other->type() - && m_description == other->m_description - && m_extendedDescription == other->m_extendedDescription - && m_highlight == other->m_highlight - && m_ignoreDryRun == other->m_ignoreDryRun - && m_silent == other->m_silent - && m_jobPool == other->m_jobPool - && m_timeout == other->m_timeout - && m_properties == other->m_properties; -} - -void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) -{ - m_description = scriptValue->property(StringConstants::descriptionProperty()).toString(); - m_extendedDescription = scriptValue->property(extendedDescriptionProperty()).toString(); - m_highlight = scriptValue->property(highlightProperty()).toString(); - m_ignoreDryRun = scriptValue->property(ignoreDryRunProperty()).toBool(); - m_silent = scriptValue->property(silentProperty()).toBool(); - m_jobPool = scriptValue->property(StringConstants::jobPoolProperty()).toString(); - const auto timeoutScriptValue = scriptValue->property(timeoutProperty()); - if (!timeoutScriptValue.isUndefined() && !timeoutScriptValue.isNull()) - m_timeout = timeoutScriptValue.toInt32(); + return type() == other->type() && m_description == other->m_description + && m_extendedDescription == other->m_extendedDescription + && m_highlight == other->m_highlight && m_ignoreDryRun == other->m_ignoreDryRun + && m_silent == other->m_silent && m_jobPool == other->m_jobPool + && m_timeout == other->m_timeout && qVariantMapsEqual(m_properties, other->m_properties); +} + +void AbstractCommand::fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, + const CodeLocation &codeLocation) +{ + m_description = getJsStringProperty(ctx, *scriptValue, StringConstants::descriptionProperty()); + m_extendedDescription = getJsStringProperty(ctx, *scriptValue, extendedDescriptionProperty()); + m_highlight = getJsStringProperty(ctx, *scriptValue, highlightProperty()); + m_ignoreDryRun = getJsBoolProperty(ctx, *scriptValue, ignoreDryRunProperty()); + m_silent = getJsBoolProperty(ctx, *scriptValue, silentProperty()); + m_jobPool = getJsStringProperty(ctx, *scriptValue, StringConstants::jobPoolProperty()); + const auto timeoutScriptValue = getJsProperty(ctx, *scriptValue, timeoutProperty()); + if (!JS_IsUndefined(timeoutScriptValue) && !JS_IsNull(timeoutScriptValue)) + m_timeout = JS_VALUE_GET_INT(timeoutScriptValue); m_codeLocation = codeLocation; m_predefinedProperties @@ -137,6 +135,11 @@ QString AbstractCommand::fullDescription(const QString &productName) const return description() + QLatin1String(" [") + productName + QLatin1Char(']'); } +QString AbstractCommand::descriptionForCancelMessage(const QString &productName) const +{ + return fullDescription(productName); +} + void AbstractCommand::load(PersistentPool &pool) { serializationOp<PersistentPool::Load>(pool); @@ -147,98 +150,102 @@ void AbstractCommand::store(PersistentPool &pool) serializationOp<PersistentPool::Store>(pool); } -void AbstractCommand::applyCommandProperties(const QScriptValue *scriptValue) +void AbstractCommand::applyCommandProperties(JSContext *ctx, const JSValue *scriptValue) { - QScriptValueIterator it(*scriptValue); - while (it.hasNext()) { - it.next(); - if (m_predefinedProperties.contains(it.name())) - continue; - const QVariant value = it.value().toVariant(); - if (QMetaType::Type(value.type()) == QMetaType::QObjectStar - || it.value().scriptClass() - || it.value().data().isValid()) { + handleJsProperties(ctx, *scriptValue, [this, ctx](const JSAtom &prop, const JSPropertyDescriptor &desc) { + const QString name = getJsString(ctx, prop); + if (m_predefinedProperties.contains(name)) + return; + // TODO: Use script class for command objects, don't allow setting random properties + if (!isSimpleValue(desc.value)) { throw ErrorInfo(Tr::tr("Property '%1' has a type unsuitable for storing in a command " - "object.").arg(it.name()), m_codeLocation); + "object.").arg(name), m_codeLocation); } - m_properties.insert(it.name(), value); - } -} - -static QScriptValue js_CommandBase(QScriptContext *context, QScriptEngine *engine) -{ - QScriptValue cmd = context->thisObject(); - QBS_ASSERT(context->isCalledAsConstructor(), cmd = engine->newObject()); - cmd.setProperty(StringConstants::descriptionProperty(), - engine->toScriptValue(AbstractCommand::defaultDescription())); - cmd.setProperty(extendedDescriptionProperty(), - engine->toScriptValue(AbstractCommand::defaultExtendedDescription())); - cmd.setProperty(highlightProperty(), - engine->toScriptValue(AbstractCommand::defaultHighLight())); - cmd.setProperty(ignoreDryRunProperty(), - engine->toScriptValue(AbstractCommand::defaultIgnoreDryRun())); - cmd.setProperty(silentProperty(), - engine->toScriptValue(AbstractCommand::defaultIsSilent())); - cmd.setProperty(timeoutProperty(), - engine->toScriptValue(AbstractCommand::defaultTimeout())); + m_properties.insert(name, getJsVariant(ctx, desc.value)); + }); +} + +static JSValue js_CommandBase(JSContext *ctx) +{ + const JSValue cmd = JS_NewObject(ctx); + setJsProperty(ctx, cmd, StringConstants::descriptionProperty(), + makeJsString(ctx, AbstractCommand::defaultDescription())); + setJsProperty(ctx, cmd, extendedDescriptionProperty(), + makeJsString(ctx, AbstractCommand::defaultExtendedDescription())); + setJsProperty(ctx, cmd, highlightProperty(), + makeJsString(ctx, AbstractCommand::defaultHighLight())); + setJsProperty(ctx, cmd, ignoreDryRunProperty(), + JS_NewBool(ctx, AbstractCommand::defaultIgnoreDryRun())); + setJsProperty(ctx, cmd, silentProperty(), + JS_NewBool(ctx, AbstractCommand::defaultIsSilent())); + setJsProperty(ctx, cmd, timeoutProperty(), + JS_NewInt32(ctx, AbstractCommand::defaultTimeout())); return cmd; } -static QScriptValue js_Command(QScriptContext *context, QScriptEngine *engine) +static JSValue js_Command(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int) { - if (Q_UNLIKELY(!context->isCalledAsConstructor())) - return context->throwError(Tr::tr("Command constructor called without new.")); - static ProcessCommandPtr commandPrototype = ProcessCommand::create(); - - 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(StringConstants::classNameProperty(), - engine->toScriptValue(StringConstants::commandType())); - cmd.setProperty(programProperty(), program); - cmd.setProperty(argumentsProperty(), arguments); - cmd.setProperty(workingDirProperty(), - engine->toScriptValue(commandPrototype->workingDir())); - cmd.setProperty(maxExitCodeProperty(), - engine->toScriptValue(commandPrototype->maxExitCode())); - cmd.setProperty(stdoutFilterFunctionProperty(), - engine->toScriptValue(commandPrototype->stdoutFilterFunction())); - cmd.setProperty(stderrFilterFunctionProperty(), - engine->toScriptValue(commandPrototype->stderrFilterFunction())); - cmd.setProperty(responseFileThresholdProperty(), - engine->toScriptValue(commandPrototype->responseFileThreshold())); - cmd.setProperty(responseFileArgumentIndexProperty(), - engine->toScriptValue(commandPrototype->responseFileArgumentIndex())); - cmd.setProperty(responseFileUsagePrefixProperty(), - engine->toScriptValue(commandPrototype->responseFileUsagePrefix())); - cmd.setProperty(stdoutFilePathProperty(), - engine->toScriptValue(commandPrototype->stdoutFilePath())); - cmd.setProperty(stderrFilePathProperty(), - engine->toScriptValue(commandPrototype->stderrFilePath())); - cmd.setProperty(environmentProperty(), - engine->toScriptValue(commandPrototype->environment().toStringList())); - cmd.setProperty(ignoreDryRunProperty(), - engine->toScriptValue(commandPrototype->ignoreDryRun())); + JSValue program = JS_UNDEFINED; + if (argc > 0) + program = JS_DupValue(ctx, argv[0]); + if (JS_IsUndefined(program)) + program = makeJsString(ctx, commandPrototype->program()); + JSValue arguments = JS_UNDEFINED; + if (argc > 1) + arguments = JS_DupValue(ctx, argv[1]); + if (JS_IsUndefined(arguments)) + arguments = makeJsStringList(ctx, commandPrototype->arguments()); + if (JS_IsString(arguments)) { + JSValue args = JS_NewArray(ctx); + JS_SetPropertyUint32(ctx, args, 0, arguments); + arguments = args; + } + JSValue cmd = js_CommandBase(ctx); + setJsProperty(ctx, cmd, StringConstants::classNameProperty(), + makeJsString(ctx, StringConstants::commandType())); + setJsProperty(ctx, cmd, programProperty(), program); + setJsProperty(ctx, cmd, argumentsProperty(), arguments); + setJsProperty(ctx, cmd, workingDirProperty(), + makeJsString(ctx, commandPrototype->workingDir())); + setJsProperty(ctx, cmd, maxExitCodeProperty(), + JS_NewInt32(ctx, commandPrototype->maxExitCode())); + setJsProperty(ctx, cmd, stdoutFilterFunctionProperty(), + makeJsString(ctx, commandPrototype->stdoutFilterFunction())); + setJsProperty(ctx, cmd, stderrFilterFunctionProperty(), + makeJsString(ctx, commandPrototype->stderrFilterFunction())); + setJsProperty(ctx, cmd, responseFileThresholdProperty(), + JS_NewInt32(ctx, commandPrototype->responseFileThreshold())); + setJsProperty(ctx, cmd, responseFileArgumentIndexProperty(), + JS_NewInt32(ctx, commandPrototype->responseFileArgumentIndex())); + setJsProperty(ctx, cmd, responseFileUsagePrefixProperty(), + makeJsString(ctx, commandPrototype->responseFileUsagePrefix())); + setJsProperty(ctx, cmd, responseFileSeparatorProperty(), + makeJsString(ctx, commandPrototype->responseFileSeparator())); + setJsProperty(ctx, cmd, stdoutFilePathProperty(), + makeJsString(ctx, commandPrototype->stdoutFilePath())); + setJsProperty(ctx, cmd, stderrFilePathProperty(), + makeJsString(ctx, commandPrototype->stderrFilePath())); + setJsProperty(ctx, cmd, environmentProperty(), + makeJsStringList(ctx, commandPrototype->environment().toStringList())); + setJsProperty(ctx, cmd, ignoreDryRunProperty(), + JS_NewBool(ctx, commandPrototype->ignoreDryRun())); return cmd; } -void ProcessCommand::setupForJavaScript(QScriptValue targetObject) +void ProcessCommand::setupForJavaScript(ScriptEngine *engine, JSValue targetObject) { - QBS_CHECK(targetObject.isObject()); - QScriptValue ctor = targetObject.engine()->newFunction(js_Command, 2); - targetObject.setProperty(StringConstants::commandType(), ctor); + engine->registerClass(StringConstants::commandType().toUtf8().constData(), js_Command, nullptr, + targetObject); } ProcessCommand::ProcessCommand() : m_maxExitCode(0) , m_responseFileThreshold(defaultResponseFileThreshold()) , m_responseFileArgumentIndex(0) + , m_responseFileSeparator(QStringLiteral("\n")) { } @@ -277,6 +284,7 @@ bool ProcessCommand::equals(const AbstractCommand *otherAbstractCommand) const && m_responseFileThreshold == other->m_responseFileThreshold && m_responseFileArgumentIndex == other->m_responseFileArgumentIndex && m_responseFileUsagePrefix == other->m_responseFileUsagePrefix + && m_responseFileSeparator == other->m_responseFileSeparator && m_stdoutFilePath == other->m_stdoutFilePath && m_stderrFilePath == other->m_stderrFilePath && m_relevantEnvVars == other->m_relevantEnvVars @@ -284,38 +292,37 @@ bool ProcessCommand::equals(const AbstractCommand *otherAbstractCommand) const && m_environment == other->m_environment; } -void ProcessCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) -{ - AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); - m_program = scriptValue->property(programProperty()).toString(); - m_arguments = scriptValue->property(argumentsProperty()).toVariant().toStringList(); - m_workingDir = scriptValue->property(workingDirProperty()).toString(); - m_maxExitCode = scriptValue->property(maxExitCodeProperty()).toInt32(); - - // toString() is required, presumably due to QtScript bug that manifests itself on Windows - const QScriptValue stdoutFilterFunction - = scriptValue->property(stdoutFilterFunctionProperty()).toString(); - - m_stdoutFilterFunction = invokedSourceCode(stdoutFilterFunction); - - // toString() is required, presumably due to QtScript bug that manifests itself on Windows - const QScriptValue stderrFilterFunction - = scriptValue->property(stderrFilterFunctionProperty()).toString(); - - m_stderrFilterFunction = invokedSourceCode(stderrFilterFunction); - m_relevantEnvVars = scriptValue->property(QStringLiteral("relevantEnvironmentVariables")) - .toVariant().toStringList(); - m_responseFileThreshold = scriptValue->property(responseFileThresholdProperty()) - .toInt32(); - m_responseFileArgumentIndex = scriptValue->property(responseFileArgumentIndexProperty()) - .toInt32(); - m_responseFileUsagePrefix = scriptValue->property(responseFileUsagePrefixProperty()) - .toString(); - QStringList envList = scriptValue->property(environmentProperty()).toVariant() - .toStringList(); +void ProcessCommand::fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, const CodeLocation &codeLocation) +{ + AbstractCommand::fillFromScriptValue(ctx, scriptValue, codeLocation); + m_program = getJsStringProperty(ctx, *scriptValue, programProperty()); + m_arguments = getJsStringListProperty(ctx, *scriptValue, argumentsProperty()); + m_workingDir = getJsStringProperty(ctx, *scriptValue, workingDirProperty()); + m_maxExitCode = getJsIntProperty(ctx, *scriptValue, maxExitCodeProperty()); + + const ScopedJsValue stdoutFilterFunction(ctx, getJsProperty(ctx, *scriptValue, + stdoutFilterFunctionProperty())); + if (JS_IsFunction(ctx, stdoutFilterFunction)) + m_stdoutFilterFunction = QLatin1Char('(') + getJsString(ctx, stdoutFilterFunction) + QLatin1Char(')'); + + const ScopedJsValue stderrFilterFunction(ctx, getJsProperty(ctx, *scriptValue, + stderrFilterFunctionProperty())); + if (JS_IsFunction(ctx, stderrFilterFunction)) + m_stderrFilterFunction = QLatin1Char('(') + getJsString(ctx, stderrFilterFunction) + QLatin1Char(')'); + + m_relevantEnvVars = getJsStringListProperty(ctx, *scriptValue, + QStringLiteral("relevantEnvironmentVariables")); + m_responseFileThreshold = getJsIntProperty(ctx, *scriptValue, responseFileThresholdProperty()); + m_responseFileArgumentIndex = getJsIntProperty(ctx, *scriptValue, + responseFileArgumentIndexProperty()); + m_responseFileUsagePrefix = getJsStringProperty(ctx, *scriptValue, + responseFileUsagePrefixProperty()); + m_responseFileSeparator = getJsStringProperty(ctx, *scriptValue, + responseFileSeparatorProperty()); + QStringList envList = getJsStringListProperty(ctx, *scriptValue, environmentProperty()); getEnvironmentFromList(envList); - m_stdoutFilePath = scriptValue->property(stdoutFilePathProperty()).toString(); - m_stderrFilePath = scriptValue->property(stderrFilePathProperty()).toString(); + m_stdoutFilePath = getJsStringProperty(ctx, *scriptValue, stdoutFilePathProperty()); + m_stderrFilePath = getJsStringProperty(ctx, *scriptValue, stderrFilePathProperty()); m_predefinedProperties << programProperty() @@ -330,7 +337,13 @@ void ProcessCommand::fillFromScriptValue(const QScriptValue *scriptValue, const << environmentProperty() << stdoutFilePathProperty() << stderrFilePathProperty(); - applyCommandProperties(scriptValue); + applyCommandProperties(ctx, scriptValue); +} + +QString ProcessCommand::descriptionForCancelMessage(const QString &productName) const +{ + return description() + QLatin1String(" (") + QDir::toNativeSeparators(m_program) + + QLatin1String(") [") + productName + QLatin1Char(']'); } QStringList ProcessCommand::relevantEnvVars() const @@ -358,43 +371,42 @@ void ProcessCommand::store(PersistentPool &pool) serializationOp<PersistentPool::Store>(pool); } -static QString currentImportScopeName(QScriptContext *context) +static QString currentImportScopeName(JSContext *ctx) { - for (; context; context = context->parentContext()) { - QScriptValue v = context->thisObject() - .property(StringConstants::importScopeNamePropertyInternal()); - if (v.isString()) - return v.toString(); + const ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const JSValueList &contextStack = engine->contextStack(); + for (auto it = contextStack.rbegin(); it != contextStack.rend(); ++it) { + if (!JS_IsObject(*it)) + continue; + const ScopedJsValue v(ctx, getJsProperty(ctx, *it, + StringConstants::importScopeNamePropertyInternal())); + if (JS_IsString(v)) + return getJsString(ctx, v); } return {}; } -static QScriptValue js_JavaScriptCommand(QScriptContext *context, QScriptEngine *engine) +static JSValue js_JavaScriptCommand(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *, int) { - 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, - Tr::tr("JavaScriptCommand c'tor doesn't take arguments.")); - } + if (argc > 0) + return throwError(ctx, Tr::tr("JavaScriptCommand c'tor doesn't take arguments.")); static JavaScriptCommandPtr commandPrototype = JavaScriptCommand::create(); - QScriptValue cmd = js_CommandBase(context, engine); - cmd.setProperty(StringConstants::classNameProperty(), - engine->toScriptValue(StringConstants::javaScriptCommandType())); - cmd.setProperty(StringConstants::sourceCodeProperty(), - engine->toScriptValue(commandPrototype->sourceCode())); - cmd.setProperty(StringConstants::importScopeNamePropertyInternal(), - engine->toScriptValue(currentImportScopeName(context))); - + JSValue cmd = js_CommandBase(ctx); + setJsProperty(ctx, cmd, StringConstants::classNameProperty(), + makeJsString(ctx, StringConstants::javaScriptCommandType())); + setJsProperty(ctx, cmd, StringConstants::sourceCodeProperty(), + makeJsString(ctx, commandPrototype->sourceCode())); + setJsProperty(ctx, cmd, StringConstants::importScopeNamePropertyInternal(), + makeJsString(ctx, currentImportScopeName(ctx))); return cmd; } -void JavaScriptCommand::setupForJavaScript(QScriptValue targetObject) +void JavaScriptCommand::setupForJavaScript(ScriptEngine *engine, JSValue targetObject) { - QBS_CHECK(targetObject.isObject()); - QScriptValue ctor = targetObject.engine()->newFunction(js_JavaScriptCommand, 0); - targetObject.setProperty(StringConstants::javaScriptCommandType(), ctor); + engine->registerClass(StringConstants::javaScriptCommandType().toUtf8().constData(), + js_JavaScriptCommand, nullptr, targetObject); } JavaScriptCommand::JavaScriptCommand() = default; @@ -407,22 +419,24 @@ bool JavaScriptCommand::equals(const AbstractCommand *otherAbstractCommand) cons return m_sourceCode == other->m_sourceCode; } -void JavaScriptCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +void JavaScriptCommand::fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, + const CodeLocation &codeLocation) { - AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); + AbstractCommand::fillFromScriptValue(ctx, scriptValue, codeLocation); - const QScriptValue importScope = scriptValue->property( - StringConstants::importScopeNamePropertyInternal()); - if (importScope.isString()) - m_scopeName = importScope.toString(); + const ScopedJsValue importScope(ctx, getJsProperty(ctx, *scriptValue, + StringConstants::importScopeNamePropertyInternal())); + if (JS_IsString(importScope)) + m_scopeName = getJsString(ctx, importScope); - const QScriptValue sourceCode = scriptValue->property(StringConstants::sourceCodeProperty()); - m_sourceCode = invokedSourceCode(sourceCode); + const ScopedJsValue sourceCode(ctx, getJsProperty(ctx, *scriptValue, + StringConstants::sourceCodeProperty())); + m_sourceCode = invokedSourceCode(ctx, sourceCode); m_predefinedProperties << StringConstants::classNameProperty() << StringConstants::sourceCodeProperty() << StringConstants::importScopeNamePropertyInternal(); - applyCommandProperties(scriptValue); + applyCommandProperties(ctx, scriptValue); } void JavaScriptCommand::load(PersistentPool &pool) @@ -461,7 +475,7 @@ void CommandList::load(PersistentPool &pool) void CommandList::store(PersistentPool &pool) const { - pool.store(m_commands.size()); + pool.store(int(m_commands.size())); for (const AbstractCommandPtr &cmd : m_commands) { pool.store(static_cast<quint8>(cmd->type())); pool.store(cmd); diff --git a/src/lib/corelib/buildgraph/rulecommands.h b/src/lib/corelib/buildgraph/rulecommands.h index d4d70d591..4296146d2 100644 --- a/src/lib/corelib/buildgraph/rulecommands.h +++ b/src/lib/corelib/buildgraph/rulecommands.h @@ -47,14 +47,15 @@ #include <tools/persistence.h> #include <tools/set.h> +#include <quickjs.h> + #include <QtCore/qprocess.h> #include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { +class ScriptEngine; class AbstractCommand { @@ -75,7 +76,9 @@ public: virtual CommandType type() const = 0; virtual bool equals(const AbstractCommand *other) const; - virtual void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); + virtual void fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, + const CodeLocation &codeLocation); + virtual QString descriptionForCancelMessage(const QString &productName) const; QString fullDescription(const QString &productName) const; const QString description() const { return m_description; } @@ -94,7 +97,7 @@ public: protected: AbstractCommand(); - void applyCommandProperties(const QScriptValue *scriptValue); + void applyCommandProperties(JSContext *ctx, const JSValue *scriptValue); Set<QString> m_predefinedProperties; @@ -121,12 +124,13 @@ class ProcessCommand : public AbstractCommand { public: static ProcessCommandPtr create() { return ProcessCommandPtr(new ProcessCommand); } - static void setupForJavaScript(QScriptValue targetObject); + static void setupForJavaScript(ScriptEngine *engine, JSValue targetObject); CommandType type() const override { return ProcessCommandType; } bool equals(const AbstractCommand *otherAbstractCommand) const override; - void fillFromScriptValue(const QScriptValue *scriptValue, + void fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, const CodeLocation &codeLocation) override; + QString descriptionForCancelMessage(const QString &productName) const override; const QString program() const { return m_program; } const QStringList arguments() const { return m_arguments; } const QString workingDir() const { return m_workingDir; } @@ -136,6 +140,7 @@ public: int responseFileThreshold() const { return m_responseFileThreshold; } int responseFileArgumentIndex() const { return m_responseFileArgumentIndex; } QString responseFileUsagePrefix() const { return m_responseFileUsagePrefix; } + QString responseFileSeparator() const { return m_responseFileSeparator; } QProcessEnvironment environment() const { return m_environment; } QStringList relevantEnvVars() const; void clearRelevantEnvValues() { m_relevantEnvValues.clear(); } @@ -158,10 +163,10 @@ private: { pool.serializationOp<opType>(m_program, m_arguments, m_environment, m_workingDir, m_stdoutFilterFunction, m_stderrFilterFunction, - m_responseFileUsagePrefix, m_maxExitCode, - m_responseFileThreshold, m_responseFileArgumentIndex, - m_relevantEnvVars, m_relevantEnvValues, m_stdoutFilePath, - m_stderrFilePath); + m_responseFileUsagePrefix, m_responseFileSeparator, + m_maxExitCode, m_responseFileThreshold, + m_responseFileArgumentIndex, m_relevantEnvVars, + m_relevantEnvValues, m_stdoutFilePath, m_stderrFilePath); } QString m_program; @@ -173,6 +178,7 @@ private: int m_responseFileThreshold; // When to use response files? In bytes of (program name + arguments). int m_responseFileArgumentIndex; QString m_responseFileUsagePrefix; + QString m_responseFileSeparator; QProcessEnvironment m_environment; QStringList m_relevantEnvVars; QProcessEnvironment m_relevantEnvValues; @@ -184,16 +190,15 @@ class JavaScriptCommand : public AbstractCommand { public: static JavaScriptCommandPtr create() { return JavaScriptCommandPtr(new JavaScriptCommand); } - static void setupForJavaScript(QScriptValue targetObject); + static void setupForJavaScript(ScriptEngine *engine, JSValue targetObject); CommandType type() const override { return JavaScriptCommandType; } bool equals(const AbstractCommand *otherAbstractCommand) const override; - void fillFromScriptValue(const QScriptValue *scriptValue, + void fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue, const CodeLocation &codeLocation) override; const QString &scopeName() const { return m_scopeName; } const QString &sourceCode() const { return m_sourceCode; } - void setSourceCode(const QString &str) { m_sourceCode = str; } void load(PersistentPool &pool) override; void store(PersistentPool &pool) override; diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp index e01a8bda8..2acc2a97e 100644 --- a/src/lib/corelib/buildgraph/rulegraph.cpp +++ b/src/lib/corelib/buildgraph/rulegraph.cpp @@ -61,11 +61,11 @@ void RuleGraph::build(const std::vector<RulePtr> &rules, const FileTags &product m_parents.resize(rules.size()); m_children.resize(rules.size()); - for (const RuleConstPtr &rule : qAsConst(m_rules)) { + for (const auto &rule : std::as_const(m_rules)) { FileTags inFileTags = rule->inputs; inFileTags += rule->auxiliaryInputs; inFileTags += rule->explicitlyDependsOn; - for (const FileTag &fileTag : qAsConst(inFileTags)) { + for (const FileTag &fileTag : std::as_const(inFileTags)) { inputFileTagToRule[fileTag].push_back(rule.get()); for (const Rule * const producingRule : m_outputFileTagToRule.value(fileTag)) { if (!producingRule->collectedOutputFileTags().intersects( @@ -82,38 +82,38 @@ void RuleGraph::build(const std::vector<RulePtr> &rules, const FileTags &product productRules << rules; //### check: the rule graph must be a in valid shape! } - for (const Rule *r : qAsConst(productRules)) + for (const Rule *r : std::as_const(productRules)) m_rootRules += r->ruleGraphId; } void RuleGraph::accept(RuleGraphVisitor *visitor) const { const RuleConstPtr nullParent; - for (int rootIndex : qAsConst(m_rootRules)) + for (int rootIndex : std::as_const(m_rootRules)) traverse(visitor, nullParent, m_rules.at(rootIndex)); } void RuleGraph::dump() const { QByteArray indent; - printf("---rule graph dump:\n"); + std::printf("---rule graph dump:\n"); Set<int> rootRules; - for (const RuleConstPtr &rule : qAsConst(m_rules)) + for (const auto &rule : std::as_const(m_rules)) if (m_parents[rule->ruleGraphId].empty()) rootRules += rule->ruleGraphId; - for (int idx : qAsConst(rootRules)) + for (int idx : std::as_const(rootRules)) dump_impl(indent, idx); } void RuleGraph::dump_impl(QByteArray &indent, int rootIndex) const { const RuleConstPtr r = m_rules[rootIndex]; - printf("%s", indent.constData()); - printf("%s", qPrintable(r->toString())); - printf("\n"); + std::printf("%s", indent.constData()); + std::printf("%s", qPrintable(r->toString())); + std::printf("\n"); indent.append(" "); - for (int childIndex : qAsConst(m_children[rootIndex])) + for (int childIndex : std::as_const(m_children[rootIndex])) dump_impl(indent, childIndex); indent.chop(2); } diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp index 48d17934f..8568e4098 100644 --- a/src/lib/corelib/buildgraph/rulenode.cpp +++ b/src/lib/corelib/buildgraph/rulenode.cpp @@ -229,7 +229,7 @@ int RuleNode::transformerCount() const ArtifactSet RuleNode::currentInputArtifacts() const { ArtifactSet s; - for (const FileTag &t : qAsConst(m_rule->inputs)) { + for (const FileTag &t : std::as_const(m_rule->inputs)) { for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) { if (artifact->isTargetOfModule()) continue; @@ -246,7 +246,7 @@ ArtifactSet RuleNode::currentInputArtifacts() const if (m_rule->inputsFromDependencies.empty()) return s; - for (const FileTag &t : qAsConst(m_rule->inputsFromDependencies)) { + for (const FileTag &t : std::as_const(m_rule->inputsFromDependencies)) { for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) { if (!artifact->isTargetOfModule()) continue; @@ -258,7 +258,7 @@ ArtifactSet RuleNode::currentInputArtifacts() const } } - for (const ResolvedProductConstPtr &dep : qAsConst(product->dependencies)) { + for (const auto &dep : std::as_const(product->dependencies)) { if (!dep->buildData) continue; for (Artifact * const a : filterByType<Artifact>(dep->buildData->allNodes())) { diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp index 437e3f4da..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> @@ -79,12 +74,13 @@ namespace Internal { RulesApplicator::RulesApplicator( ResolvedProductPtr product, - std::unordered_map<QString, const ResolvedProduct *> productsByName, - std::unordered_map<QString, const ResolvedProject *> projectsByName, + const std::unordered_map<QString, const ResolvedProduct *> &productsByName, + const std::unordered_map<QString, const ResolvedProject *> &projectsByName, Logger logger) : m_product(std::move(product)) - , m_productsByName(std::move(productsByName)) - , m_projectsByName(std::move(projectsByName)) + // m_productsByName and m_projectsByName are references, cannot move-construct + , m_productsByName(productsByName) + , m_projectsByName(projectsByName) , m_mocScanner(nullptr) , m_logger(std::move(logger)) { @@ -112,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(), @@ -133,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, @@ -149,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; @@ -163,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) @@ -179,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) @@ -201,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 RuleArtifactConstPtr &ruleArtifact : m_rule->artifacts) { - const OutputArtifactInfo outputInfo = createOutputArtifactFromRuleArtifact( + for (const auto &ruleArtifact : m_rule->artifacts) { + const OutputArtifactInfo &outputInfo = createOutputArtifactFromRuleArtifact( ruleArtifact, inputArtifacts, &outputFilePaths); if (!outputInfo.artifact) continue; @@ -227,7 +226,7 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &p } } - ArtifactSet newOutputs = ArtifactSet::fromList(outputArtifacts); + const auto newOutputs = rangeTo<ArtifactSet>(outputArtifacts); const ArtifactSet oldOutputs = collectOldOutputArtifacts(inputArtifacts); handleRemovedRuleOutputs(m_completeInputSet, oldOutputs - newOutputs, m_removedArtifacts, m_logger); @@ -248,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()); @@ -310,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; } @@ -354,7 +358,7 @@ ArtifactSet RulesApplicator::collectAdditionalInputs(const FileTags &tags, const } if (inputsSources.testFlag(Dependencies)) { - for (const ResolvedProductConstPtr &depProduct : product->dependencies) { + for (const auto &depProduct : product->dependencies) { for (Artifact * const ta : depProduct->targetArtifacts()) { if (ta->fileTags().contains(fileTag) && !ta->fileTags().intersects(rule->excludedInputs)) { @@ -385,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 { @@ -414,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()) { @@ -513,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), @@ -555,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() @@ -569,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; @@ -613,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); @@ -641,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; @@ -658,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; } @@ -669,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 diff --git a/src/lib/corelib/buildgraph/rulesapplicator.h b/src/lib/corelib/buildgraph/rulesapplicator.h index 1160f3d09..72f9aadcc 100644 --- a/src/lib/corelib/buildgraph/rulesapplicator.h +++ b/src/lib/corelib/buildgraph/rulesapplicator.h @@ -44,12 +44,13 @@ #include "nodeset.h" #include <language/filetags.h> #include <language/forward_decls.h> +#include <language/scriptengine.h> #include <logging/logger.h> +#include <quickjs.h> #include <QtCore/qflags.h> #include <QtCore/qhash.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptvalue.h> #include <unordered_map> @@ -63,8 +64,8 @@ class RulesApplicator { public: RulesApplicator(ResolvedProductPtr product, - std::unordered_map<QString, const ResolvedProduct *> productsByName, - std::unordered_map<QString, const ResolvedProject *> projectsByName, + const std::unordered_map<QString, const ResolvedProduct *> &productsByName, + const std::unordered_map<QString, const ResolvedProject *> &projectsByName, Logger logger); ~RulesApplicator(); @@ -85,7 +86,7 @@ public: Q_DECLARE_FLAGS(InputsSources, InputsSourceFlag) private: - void doApply(const ArtifactSet &inputArtifacts, QScriptValue &prepareScriptContext); + void doApply(const ArtifactSet &inputArtifacts, JSValue prepareScriptContext); ArtifactSet collectOldOutputArtifacts(const ArtifactSet &inputArtifacts) const; struct OutputArtifactInfo { @@ -100,13 +101,14 @@ private: OutputArtifactInfo createOutputArtifact(const QString &filePath, const FileTags &fileTags, bool alwaysUpdated, const ArtifactSet &inputArtifacts); QList<Artifact *> runOutputArtifactsScript(const ArtifactSet &inputArtifacts, - const QScriptValueList &args); - Artifact *createOutputArtifactFromScriptValue(const QScriptValue &obj, + const JSValueList &args); + Artifact *createOutputArtifactFromScriptValue(const JSValue &obj, const ArtifactSet &inputArtifacts); QString resolveOutPath(const QString &path) const; const RulesEvaluationContextPtr &evalContext() const; ScriptEngine *engine() const; - QScriptValue scope() const; + JSContext *jsContext() const; + JSValue scope() const; static ArtifactSet collectAdditionalInputs(const FileTags &tags, const Rule *rule, const ResolvedProduct *product, diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp index 6ae230329..2d0f5d44d 100644 --- a/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp @@ -38,9 +38,7 @@ ****************************************************************************/ #include "rulesevaluationcontext.h" -#include "artifact.h" #include "rulecommands.h" -#include "transformer.h" #include <language/language.h> #include <language/scriptengine.h> #include <logging/translator.h> @@ -57,17 +55,18 @@ RulesEvaluationContext::RulesEvaluationContext(Logger logger) : m_logger(std::move(logger)), m_engine(ScriptEngine::create(m_logger, EvalContext::RuleExecution)), m_observer(nullptr), - m_initScopeCalls(0) + m_initScopeCalls(0), + m_prepareScriptScope(m_engine->newObject()) { - m_prepareScriptScope = m_engine->newObject(); - m_prepareScriptScope.setPrototype(m_engine->globalObject()); - ProcessCommand::setupForJavaScript(m_prepareScriptScope); - JavaScriptCommand::setupForJavaScript(m_prepareScriptScope); + + JS_SetPrototype(m_engine->context(), m_prepareScriptScope, m_engine->globalObject()); + ProcessCommand::setupForJavaScript(m_engine.get(), m_prepareScriptScope); + JavaScriptCommand::setupForJavaScript(m_engine.get(), m_prepareScriptScope); } RulesEvaluationContext::~RulesEvaluationContext() { - delete m_engine; + JS_FreeValue(m_engine->context(), m_prepareScriptScope); } void RulesEvaluationContext::initializeObserver(const QString &description, int maximumProgress) @@ -95,7 +94,7 @@ void RulesEvaluationContext::initScope() m_engine->setActive(true); m_scope = m_engine->newObject(); - m_scope.setPrototype(m_prepareScriptScope); + JS_SetPrototype(m_engine->context(), m_scope, m_prepareScriptScope); m_engine->setGlobalObject(m_scope); } @@ -105,8 +104,11 @@ void RulesEvaluationContext::cleanupScope() if (--m_initScopeCalls > 0) return; - m_scope = QScriptValue(); - m_engine->setGlobalObject(m_prepareScriptScope.prototype()); + JS_FreeValue(m_engine->context(), m_scope); + m_scope = JS_UNDEFINED; + const ScopedJsValue proto(engine()->context(), + JS_GetPrototype(m_engine->context(), m_prepareScriptScope)); + m_engine->setGlobalObject(proto); m_engine->setActive(false); } diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.h b/src/lib/corelib/buildgraph/rulesevaluationcontext.h index a5d81ce61..7ff75b51f 100644 --- a/src/lib/corelib/buildgraph/rulesevaluationcontext.h +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.h @@ -42,12 +42,11 @@ #include <language/forward_decls.h> #include <logging/logger.h> +#include <quickjs.h> + #include <QtCore/qhash.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptprogram.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { class ProgressObserver; @@ -69,8 +68,8 @@ public: RulesEvaluationContext * const m_evalContext; }; - ScriptEngine *engine() const { return m_engine; } - QScriptValue scope() const { return m_scope; } + ScriptEngine *engine() const { return m_engine.get(); } + JSValue scope() const { return m_scope; } void setObserver(ProgressObserver *observer) { m_observer = observer; } ProgressObserver *observer() const { return m_observer; } @@ -79,17 +78,15 @@ public: void checkForCancelation(); private: - friend class Scope; - void initScope(); void cleanupScope(); Logger m_logger; - ScriptEngine * const m_engine; + const std::unique_ptr<ScriptEngine> m_engine; ProgressObserver *m_observer; unsigned int m_initScopeCalls; - QScriptValue m_scope; - QScriptValue m_prepareScriptScope; + JSValue m_scope = JS_UNDEFINED; + const JSValue m_prepareScriptScope; }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/timestampsupdater.cpp b/src/lib/corelib/buildgraph/timestampsupdater.cpp index d31f57445..3f5279dd2 100644 --- a/src/lib/corelib/buildgraph/timestampsupdater.cpp +++ b/src/lib/corelib/buildgraph/timestampsupdater.cpp @@ -83,7 +83,7 @@ private: }; void TimestampsUpdater::updateTimestamps(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const Logger &logger) + const QVector<ResolvedProductPtr> &products, const Logger &logger) { TimestampsUpdateVisitor v; for (const ResolvedProductPtr &product : products) diff --git a/src/lib/corelib/buildgraph/timestampsupdater.h b/src/lib/corelib/buildgraph/timestampsupdater.h index cfe20df12..8184ca708 100644 --- a/src/lib/corelib/buildgraph/timestampsupdater.h +++ b/src/lib/corelib/buildgraph/timestampsupdater.h @@ -51,7 +51,7 @@ class TimestampsUpdater { public: void updateTimestamps(const TopLevelProjectPtr &project, - const QList<ResolvedProductPtr> &products, const Logger &logger); + const QVector<ResolvedProductPtr> &products, const Logger &logger); }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/transformer.cpp b/src/lib/corelib/buildgraph/transformer.cpp index 5e27c3e74..2346ad5c9 100644 --- a/src/lib/corelib/buildgraph/transformer.cpp +++ b/src/lib/corelib/buildgraph/transformer.cpp @@ -39,6 +39,7 @@ #include "transformer.h" #include "artifact.h" +#include "productbuilddata.h" #include <jsextensions/moduleproperties.h> #include <language/language.h> #include <language/preparescriptobserver.h> @@ -54,6 +55,7 @@ #include <QtCore/qdir.h> #include <algorithm> +#include <vector> namespace qbs { namespace Internal { @@ -64,78 +66,82 @@ Transformer::Transformer() : alwaysRun(false) Transformer::~Transformer() = default; -static QScriptValue js_baseName(QScriptContext *ctx, QScriptEngine *engine, - const Artifact *artifact) +static JSValue js_baseName(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - Q_UNUSED(ctx); - Q_UNUSED(engine); - return {FileInfo::baseName(artifact->filePath())}; + return ScriptEngine::engineForContext(ctx)->getArtifactProperty(this_val, + [ctx](const Artifact *a) { + return makeJsString(ctx, FileInfo::baseName(a->filePath())); + }); } -static QScriptValue js_completeBaseName(QScriptContext *ctx, QScriptEngine *engine, - const Artifact *artifact) +static JSValue js_completeBaseName(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - Q_UNUSED(ctx); - Q_UNUSED(engine); - return {FileInfo::completeBaseName(artifact->filePath())}; + return ScriptEngine::engineForContext(ctx)->getArtifactProperty(this_val, + [ctx](const Artifact *a) { + return makeJsString(ctx, FileInfo::completeBaseName(a->filePath())); + }); } -static QScriptValue js_baseDir(QScriptContext *ctx, QScriptEngine *engine, - const Artifact *artifact) +static JSValue js_baseDir(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - Q_UNUSED(ctx); - Q_UNUSED(engine); - QString basedir; - if (artifact->artifactType == Artifact::SourceFile) { - QDir sourceDir(artifact->product->sourceDirectory); - basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath())); - } else { - QDir buildDir(artifact->product->buildDirectory()); - basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath())); - } - return basedir; + return ScriptEngine::engineForContext(ctx)->getArtifactProperty(this_val, + [ctx](const Artifact *artifact) { + QString basedir; + if (artifact->artifactType == Artifact::SourceFile) { + QDir sourceDir(artifact->product->sourceDirectory); + basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath())); + } else { + QDir buildDir(artifact->product->buildDirectory()); + basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath())); + } + return makeJsString(ctx, basedir); + }); } -static QScriptValue js_children(QScriptContext *ctx, QScriptEngine *engine, const Artifact *artifact) +static JSValue js_children(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - Q_UNUSED(ctx); - QScriptValue sv = engine->newArray(); - uint idx = 0; - for (const Artifact *child : artifact->childArtifacts()) { - sv.setProperty(idx++, Transformer::translateFileConfig(static_cast<ScriptEngine *>(engine), - child, QString())); - } - return sv; + return ScriptEngine::engineForContext(ctx)->getArtifactProperty(this_val, + [ctx](const Artifact *artifact) { + JSValue sv = JS_NewArray(ctx); + uint idx = 0; + + // FIXME: childArtifacts() is not guarded by any mutex ... + for (Artifact *child : artifact->childArtifacts()) { + JS_SetPropertyUint32(ctx, sv, idx++, Transformer::translateFileConfig( + ScriptEngine::engineForContext(ctx), child, QString())); + } + return sv; + }); } -static void setArtifactProperty(QScriptValue &obj, const QString &name, - QScriptValue (*func)(QScriptContext *, QScriptEngine *, const Artifact *), - const Artifact *artifact) +static void setArtifactProperty(JSContext *ctx, JSValue &obj, const QString &name, + JSCFunction *func) { - obj.setProperty(name, static_cast<ScriptEngine *>(obj.engine())->newFunction(func, artifact), - QScriptValue::PropertyGetter); + const QByteArray nameBa = name.toUtf8(); + const JSValue jsFunc = JS_NewCFunction(ctx, func, nameBa.constData(), 0); + const ScopedJsAtom nameAtom(ctx, nameBa); + JS_DefinePropertyGetSet(ctx, obj, nameAtom, jsFunc, JS_UNDEFINED, JS_PROP_HAS_GET); } -QScriptValue Transformer::translateFileConfig(ScriptEngine *scriptEngine, const Artifact *artifact, - const QString &defaultModuleName) +JSValue Transformer::translateFileConfig(ScriptEngine *engine, Artifact *artifact, + const QString &defaultModuleName) { - QScriptValue obj = scriptEngine->newObject(); - attachPointerTo(obj, artifact); - ModuleProperties::init(obj, artifact); - obj.setProperty(StringConstants::fileNameProperty(), artifact->fileName()); - obj.setProperty(StringConstants::filePathProperty(), artifact->filePath()); - setArtifactProperty(obj, StringConstants::baseNameProperty(), js_baseName, artifact); - setArtifactProperty(obj, StringConstants::completeBaseNameProperty(), js_completeBaseName, - artifact); - setArtifactProperty(obj, QStringLiteral("baseDir"), js_baseDir, artifact); - setArtifactProperty(obj, QStringLiteral("children"), js_children, artifact); - const QStringList fileTags = sorted(artifact->fileTags().toStringList()); - scriptEngine->setObservedProperty(obj, StringConstants::fileTagsProperty(), - scriptEngine->toScriptValue(fileTags)); - scriptEngine->observer()->addArtifactId(obj.objectId()); - if (!defaultModuleName.isEmpty()) - obj.setProperty(StringConstants::moduleNameProperty(), defaultModuleName); - return obj; + return engine->getArtifactScriptValue(artifact, defaultModuleName, [&](JSValue obj) { + ModuleProperties::init(engine, obj, artifact); + JSContext * const ctx = engine->context(); + setJsProperty(ctx, obj, StringConstants::fileNameProperty(), artifact->fileName()); + setJsProperty(ctx, obj, StringConstants::filePathProperty(), artifact->filePath()); + setArtifactProperty(ctx, obj, StringConstants::baseNameProperty(), js_baseName); + setArtifactProperty(ctx, obj, StringConstants::completeBaseNameProperty(), js_completeBaseName); + setArtifactProperty(ctx, obj, QStringLiteral("baseDir"), js_baseDir); + setArtifactProperty(ctx, obj, QStringLiteral("children"), js_children); + const QStringList fileTags = sorted(artifact->fileTags().toStringList()); + const ScopedJsValue jsFileTags(ctx, engine->toScriptValue(fileTags)); + engine->setObservedProperty(obj, StringConstants::fileTagsProperty(), jsFileTags); + engine->observer()->addArtifactId(jsObjectId(obj)); + if (!defaultModuleName.isEmpty()) + setJsProperty(ctx, obj, StringConstants::moduleNameProperty(), defaultModuleName); + }); } static bool compareByFilePath(const Artifact *a1, const Artifact *a2) @@ -143,9 +149,8 @@ static bool compareByFilePath(const Artifact *a1, const Artifact *a2) return a1->filePath() < a2->filePath(); } -QScriptValue Transformer::translateInOutputs(ScriptEngine *scriptEngine, - const ArtifactSet &artifacts, - const QString &defaultModuleName) +JSValue Transformer::translateInOutputs(ScriptEngine *engine, const ArtifactSet &artifacts, + const QString &defaultModuleName) { using TagArtifactsMap = QMap<QString, QList<Artifact*>>; TagArtifactsMap tagArtifactsMap; @@ -155,16 +160,16 @@ QScriptValue Transformer::translateInOutputs(ScriptEngine *scriptEngine, for (TagArtifactsMap::Iterator it = tagArtifactsMap.begin(); it != tagArtifactsMap.end(); ++it) std::sort(it.value().begin(), it.value().end(), compareByFilePath); - QScriptValue jsTagFiles = scriptEngine->newObject(); - for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) { + JSValue jsTagFiles = engine->newObject(); + for (auto tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) { const QList<Artifact*> &artifacts = tag.value(); - QScriptValue jsFileConfig = scriptEngine->newArray(artifacts.size()); + JSValue jsFileConfig = JS_NewArray(engine->context()); int i = 0; for (Artifact * const artifact : artifacts) { - jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact, - defaultModuleName)); + JS_SetPropertyUint32(engine->context(), jsFileConfig, i++, + translateFileConfig(engine, artifact, defaultModuleName)); } - jsTagFiles.setProperty(tag.key(), jsFileConfig); + setJsProperty(engine->context(), jsTagFiles, tag.key(), jsFileConfig); } return jsTagFiles; @@ -177,66 +182,70 @@ ResolvedProductPtr Transformer::product() const return (*outputs.cbegin())->product.lock(); } -void Transformer::setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, - const QString &defaultModuleName) +void Transformer::setupInputs(ScriptEngine *engine, JSValue targetScriptValue, + const ArtifactSet &inputs, const QString &defaultModuleName) { - const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine()); - QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName); - targetScriptValue.setProperty(StringConstants::inputsVar(), scriptValue); - QScriptValue inputScriptValue; + JSValue scriptValue = translateInOutputs(engine, inputs, defaultModuleName); + setJsProperty(engine->context(), targetScriptValue, StringConstants::inputsVar(), scriptValue); + JSValue inputScriptValue = JS_UNDEFINED; if (inputs.size() == 1) { Artifact *input = *inputs.cbegin(); const FileTags &fileTags = input->fileTags(); QBS_ASSERT(!fileTags.empty(), return); - QScriptValue inputsForFileTag = scriptValue.property(fileTags.cbegin()->toString()); - inputScriptValue = inputsForFileTag.property(0); + const ScopedJsValue inputsForFileTag( + engine->context(), + getJsProperty(engine->context(), scriptValue, fileTags.cbegin()->toString())); + inputScriptValue = JS_GetPropertyUint32(engine->context(), inputsForFileTag, 0); } - targetScriptValue.setProperty(StringConstants::inputVar(), inputScriptValue); + setJsProperty(engine->context(), targetScriptValue, StringConstants::inputVar(), + inputScriptValue); } -void Transformer::setupInputs(QScriptValue targetScriptValue) +void Transformer::setupInputs(ScriptEngine *engine, const JSValue &targetScriptValue) { - setupInputs(targetScriptValue, inputs, rule->module->name); + setupInputs(engine, targetScriptValue, inputs, rule->module->name); } -void Transformer::setupOutputs(QScriptValue targetScriptValue) +void Transformer::setupOutputs(ScriptEngine *engine, JSValue targetScriptValue) { - const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine()); const QString &defaultModuleName = rule->module->name; - QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName); - targetScriptValue.setProperty(StringConstants::outputsVar(), scriptValue); - QScriptValue outputScriptValue; + JSValue scriptValue = translateInOutputs(engine, outputs, defaultModuleName); + setJsProperty(engine->context(), targetScriptValue, StringConstants::outputsVar(), scriptValue); + JSValue outputScriptValue = JS_UNDEFINED; if (outputs.size() == 1) { Artifact *output = *outputs.cbegin(); const FileTags &fileTags = output->fileTags(); QBS_ASSERT(!fileTags.empty(), return); - QScriptValue outputsForFileTag = scriptValue.property(fileTags.cbegin()->toString()); - outputScriptValue = outputsForFileTag.property(0); + const ScopedJsValue outputsForFileTag( + engine->context(), + getJsProperty(engine->context(), scriptValue, fileTags.cbegin()->toString())); + outputScriptValue = JS_GetPropertyUint32(engine->context(), outputsForFileTag, 0); } - targetScriptValue.setProperty(StringConstants::outputVar(), outputScriptValue); + setJsProperty(engine->context(), targetScriptValue, StringConstants::outputVar(), + outputScriptValue); } -void Transformer::setupExplicitlyDependsOn(QScriptValue targetScriptValue) +void Transformer::setupExplicitlyDependsOn(ScriptEngine *engine, JSValue targetScriptValue) { - const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine()); - QScriptValue scriptValue = translateInOutputs(scriptEngine, explicitlyDependsOn, - rule->module->name); - targetScriptValue.setProperty(StringConstants::explicitlyDependsOnVar(), scriptValue); + JSValue scriptValue = translateInOutputs(engine, explicitlyDependsOn, rule->module->name); + setJsProperty(engine->context(), targetScriptValue, StringConstants::explicitlyDependsOnVar(), + scriptValue); } -AbstractCommandPtr Transformer::createCommandFromScriptValue(const QScriptValue &scriptValue, - const CodeLocation &codeLocation) +AbstractCommandPtr Transformer::createCommandFromScriptValue( + ScriptEngine *engine, const JSValue &scriptValue, const CodeLocation &codeLocation) { AbstractCommandPtr cmdBase; - if (scriptValue.isUndefined() || !scriptValue.isValid()) + if (JS_IsUndefined(scriptValue) || JS_IsUninitialized(scriptValue)) return cmdBase; - QString className = scriptValue.property(StringConstants::classNameProperty()).toString(); + QString className = getJsStringProperty(engine->context(), scriptValue, + StringConstants::classNameProperty()); if (className == StringConstants::commandType()) cmdBase = ProcessCommand::create(); else if (className == StringConstants::javaScriptCommandType()) cmdBase = JavaScriptCommand::create(); if (cmdBase) - cmdBase->fillFromScriptValue(&scriptValue, codeLocation); + cmdBase->fillFromScriptValue(engine->context(), &scriptValue, codeLocation); if (className == StringConstants::commandType()) { auto procCmd = static_cast<ProcessCommand *>(cmdBase.get()); procCmd->clearRelevantEnvValues(); @@ -248,18 +257,14 @@ AbstractCommandPtr Transformer::createCommandFromScriptValue(const QScriptValue } void Transformer::createCommands(ScriptEngine *engine, const PrivateScriptFunction &script, - const QScriptValueList &args) + const JSValueList &args) { - if (!script.scriptFunction.isValid() || script.scriptFunction.engine() != engine) { - script.scriptFunction = engine->evaluate(script.sourceCode(), - script.location().filePath(), - script.location().line()); - if (Q_UNLIKELY(!script.scriptFunction.isFunction())) - throw ErrorInfo(Tr::tr("Invalid prepare script."), script.location()); - } - - QScriptValue scriptValue = script.scriptFunction.call(QScriptValue(), args); - engine->releaseResourcesOfScriptObjects(); + JSValueList argv(args.cbegin(), args.cend()); + const JSValue function = script.getFunction(engine, Tr::tr("Invalid prepare script.")); + const ScopedJsValue scriptValue( + engine->context(), + JS_Call(engine->context(), function, engine->globalObject(), + int(argv.size()), argv.data())); propertiesRequestedInPrepareScript = engine->propertiesRequestedInScript(); propertiesRequestedFromArtifactInPrepareScript = engine->propertiesRequestedFromArtifact(); importedFilesUsedInPrepareScript = engine->importedFilesUsedInScript(); @@ -271,22 +276,24 @@ void Transformer::createCommands(ScriptEngine *engine, const PrivateScriptFuncti p->exportedModule)); } engine->clearRequestedProperties(); - if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) - throw engine->lastError(scriptValue, script.location()); + if (JsException ex = engine->checkAndClearException(script.location())) + throw ex.toErrorInfo(); commands.clear(); - if (scriptValue.isArray()) { - const int count = scriptValue.property(StringConstants::lengthProperty()).toInt32(); + if (JS_IsArray(engine->context(), scriptValue)) { + const int count = JS_VALUE_GET_INT(getJsProperty(engine->context(), scriptValue, + StringConstants::lengthProperty())); for (qint32 i = 0; i < count; ++i) { - QScriptValue item = scriptValue.property(i); - if (item.isValid() && !item.isUndefined()) { - const AbstractCommandPtr cmd - = createCommandFromScriptValue(item, script.location()); + ScopedJsValue item(engine->context(), + JS_GetPropertyUint32(engine->context(), scriptValue, i)); + if (!JS_IsUninitialized(item) && !JS_IsUndefined(item)) { + const AbstractCommandPtr cmd = createCommandFromScriptValue(engine, item, + script.location()); if (cmd) commands.addCommand(cmd); } } } else { - const AbstractCommandPtr cmd = createCommandFromScriptValue(scriptValue, + const AbstractCommandPtr cmd = createCommandFromScriptValue(engine, scriptValue, script.location()); if (cmd) commands.addCommand(cmd); diff --git a/src/lib/corelib/buildgraph/transformer.h b/src/lib/corelib/buildgraph/transformer.h index 2f6a8e56d..927572310 100644 --- a/src/lib/corelib/buildgraph/transformer.h +++ b/src/lib/corelib/buildgraph/transformer.h @@ -52,6 +52,8 @@ #include <tools/filetime.h> #include <tools/persistence.h> +#include <quickjs.h> + #include <QtCore/qhash.h> namespace qbs { @@ -91,15 +93,14 @@ public: bool commandsNeedChangeTracking = false; bool markedForRerun = false; - static QScriptValue translateFileConfig(ScriptEngine *scriptEngine, - const Artifact *artifact, - const QString &defaultModuleName); + static JSValue translateFileConfig(ScriptEngine *engine, Artifact *artifact, + const QString &defaultModuleName); ResolvedProductPtr product() const; - void setupInputs(QScriptValue targetScriptValue); - void setupOutputs(QScriptValue targetScriptValue); - void setupExplicitlyDependsOn(QScriptValue targetScriptValue); + void setupInputs(ScriptEngine *engine, const JSValue &targetScriptValue); + void setupOutputs(ScriptEngine *engine, JSValue targetScriptValue); + void setupExplicitlyDependsOn(ScriptEngine *engine, JSValue targetScriptValue); void createCommands(ScriptEngine *engine, const PrivateScriptFunction &script, - const QScriptValueList &args); + const JSValueList &args); void rescueChangeTrackingData(const TransformerConstPtr &other); Set<QString> jobPools() const; @@ -124,14 +125,13 @@ public: private: Transformer(); - AbstractCommandPtr createCommandFromScriptValue(const QScriptValue &scriptValue, + AbstractCommandPtr createCommandFromScriptValue(ScriptEngine *engine, const JSValue &scriptValue, const CodeLocation &codeLocation); - static void setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, - const QString &defaultModuleName); - static QScriptValue translateInOutputs(ScriptEngine *scriptEngine, - const ArtifactSet &artifacts, - const QString &defaultModuleName); + static void setupInputs(ScriptEngine *engine, JSValue targetScriptValue, + const ArtifactSet &inputs, const QString &defaultModuleName); + static JSValue translateInOutputs(ScriptEngine *engine, const ArtifactSet &artifacts, + const QString &defaultModuleName); }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.cpp b/src/lib/corelib/buildgraph/transformerchangetracking.cpp index 505f0cbba..ae43e8219 100644 --- a/src/lib/corelib/buildgraph/transformerchangetracking.cpp +++ b/src/lib/corelib/buildgraph/transformerchangetracking.cpp @@ -39,6 +39,7 @@ #include <tools/fileinfo.h> #include <tools/qbsassert.h> #include <tools/qttools.h> +#include <tools/stlutils.h> #include <QtCore/qvariant.h> @@ -71,7 +72,7 @@ private: const char *context) const; bool isExportedModuleUpToDate(const QString &productName, const ExportedModule &module) const; bool areExportedModulesUpToDate( - const std::unordered_map<QString, ExportedModule> exportedModules) const; + const std::unordered_map<QString, ExportedModule> &exportedModules) const; const Artifact *getArtifact(const QString &filePath, const QString &productName) const; const ResolvedProduct *getProduct(const QString &name) const; @@ -157,7 +158,7 @@ bool TrafoChangeTracker::checkForPropertyChange(const Property &restoredProperty case Property::PropertyInArtifact: QBS_CHECK(false); } - if (restoredProperty.value != v) { + if (!qVariantsEqual(restoredProperty.value, v)) { qCDebug(lcBuildGraph).noquote().nospace() << "Value for property '" << restoredProperty.moduleName << "." << restoredProperty.propertyName << "' has changed.\n" @@ -207,13 +208,11 @@ bool TrafoChangeTracker::isExportedModuleUpToDate(const QString &productName, } bool TrafoChangeTracker::areExportedModulesUpToDate( - const std::unordered_map<QString, ExportedModule> exportedModules) const + const std::unordered_map<QString, ExportedModule> &exportedModules) const { - for (const auto &kv : exportedModules) { - if (!isExportedModuleUpToDate(kv.first, kv.second)) - return false; - } - return true; + return Internal::all_of(exportedModules, [this](const auto &kv) { + return isExportedModuleUpToDate(kv.first, kv.second); + }); } const Artifact *TrafoChangeTracker::getArtifact(const QString &filePath, @@ -258,7 +257,7 @@ const ResolvedProduct *TrafoChangeTracker::getProduct(const QString &name) const bool TrafoChangeTracker::prepareScriptNeedsRerun() const { - for (const Property &property : qAsConst(m_transformer->propertiesRequestedInPrepareScript)) { + for (const Property &property : std::as_const(m_transformer->propertiesRequestedInPrepareScript)) { if (checkForPropertyChange(property, propertyMapByKind(property))) return true; } @@ -270,7 +269,7 @@ bool TrafoChangeTracker::prepareScriptNeedsRerun() const for (auto it = m_transformer->propertiesRequestedFromArtifactInPrepareScript.constBegin(); it != m_transformer->propertiesRequestedFromArtifactInPrepareScript.constEnd(); ++it) { - for (const Property &property : qAsConst(it.value())) { + for (const Property &property : std::as_const(it.value())) { const Artifact * const artifact = getArtifact(it.key(), property.productName); if (!artifact) return true; @@ -298,15 +297,14 @@ bool TrafoChangeTracker::prepareScriptNeedsRerun() const bool TrafoChangeTracker::commandsNeedRerun() const { - for (const Property &property : qAsConst(m_transformer->propertiesRequestedInCommands)) { + for (const Property &property : std::as_const(m_transformer->propertiesRequestedInCommands)) { if (checkForPropertyChange(property, propertyMapByKind(property))) return true; } - QMap<QString, SourceArtifactConstPtr> artifactMap; for (auto it = m_transformer->propertiesRequestedFromArtifactInCommands.cbegin(); it != m_transformer->propertiesRequestedFromArtifactInCommands.cend(); ++it) { - for (const Property &property : qAsConst(it.value())) { + for (const Property &property : std::as_const(it.value())) { const Artifact * const artifact = getArtifact(it.key(), property.productName); if (!artifact) return true; @@ -333,7 +331,7 @@ bool TrafoChangeTracker::commandsNeedRerun() const return true; // TODO: Also track env access in JS commands and prepare scripts - for (const AbstractCommandPtr &c : qAsConst(m_transformer->commands.commands())) { + for (const AbstractCommandPtr &c : std::as_const(m_transformer->commands.commands())) { if (c->type() != AbstractCommand::ProcessCommandType) continue; const ProcessCommandPtr &processCmd = std::static_pointer_cast<ProcessCommand>(c); diff --git a/src/lib/corelib/corelib.pro b/src/lib/corelib/corelib.pro deleted file mode 100644 index 002e36683..000000000 --- a/src/lib/corelib/corelib.pro +++ /dev/null @@ -1,42 +0,0 @@ -TARGET = qbscore -include(../library.pri) -include(../bundledlibs.pri) - -qbs_use_bundled_qtscript { - include(../scriptengine/use_scriptengine.pri) -} else { - QT += script -} - -isEmpty(QBS_RELATIVE_LIBEXEC_PATH) { - win32:QBS_RELATIVE_LIBEXEC_PATH=../bin - else:QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs -} -DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\" - -QT += core-private network -qbs_enable_project_file_updates: QT += gui - -INCLUDEPATH += $$PWD - -include(api/api.pri) -include(buildgraph/buildgraph.pri) -include(generators/generators.pri) -include(jsextensions/jsextensions.pri) -include(language/language.pri) -include(logging/logging.pri) -include(parser/parser.pri) -include(tools/tools.pri) - -win32:LIBS += -lpsapi -lshell32 - -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 index bd44ac3ed..1a7890166 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -1,37 +1,32 @@ -import qbs 1.0 import qbs.Utilities QbsLibrary { Depends { name: "cpp" } Depends { name: "Qt"; submodules: ["core-private", "network", "xml"] } Depends { - name: "Qt.script" - condition: !qbsbuildconfig.useBundledQtScript - required: false + name: "Qt.core5compat"; + condition: Utilities.versionCompare(Qt.core.version, "6.0.0") >= 0 } - Depends { - name: "qbsscriptengine" - condition: qbsbuildconfig.useBundledQtScript || !Qt.script.present - } - Depends { condition: qbsbuildconfig.enableProjectFileUpdates; name: "Qt.gui" } + Depends { name: "quickjs" } + Depends { name: "qbspkgconfig" } name: "qbscore" - property stringList bundledQtScriptIncludes: qbsbuildconfig.useBundledQtScript - || !Qt.script.present ? qbsscriptengine.includePaths : [] - cpp.includePaths: base.concat(bundledQtScriptIncludes).concat([ + cpp.includePaths: base.concat([ ".", "../.." // for the plugin headers ]) - property stringList projectFileUpdateDefines: - qbsbuildconfig.enableProjectFileUpdates ? ["QBS_ENABLE_PROJECT_FILE_UPDATES"] : [] - property stringList enableUnitTestsDefines: - qbsbuildconfig.enableUnitTests ? ["QBS_ENABLE_UNIT_TESTS"] : [] - property stringList systemSettingsDirDefines: qbsbuildconfig.systemSettingsDir - ? ['QBS_SYSTEM_SETTINGS_DIR="' + qbsbuildconfig.systemSettingsDir + '"'] : [] - cpp.defines: base.concat([ - "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath), - "QBS_VERSION=" + Utilities.cStringQuote(version), - ]).concat(projectFileUpdateDefines).concat(enableUnitTestsDefines) - .concat(systemSettingsDirDefines) + cpp.defines: { + var defines = base.concat([ + "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath), + "QBS_VERSION=" + Utilities.cStringQuote(version), + ]); + if (project.withTests) + defines.push("QBS_WITH_TESTS"); + if (qbsbuildconfig.enableUnitTests) + defines.push("QBS_ENABLE_UNIT_TESTS"); + if (qbsbuildconfig.systemSettingsDir) + defines.push('QBS_SYSTEM_SETTINGS_DIR="' + qbsbuildconfig.systemSettingsDir + '"'); + return defines; + } Properties { condition: qbs.targetOS.contains("windows") @@ -52,7 +47,6 @@ QbsLibrary { } Group { name: "project file updating" - condition: qbsbuildconfig.enableProjectFileUpdates prefix: "api/" files: [ "changeset.cpp", @@ -173,7 +167,6 @@ QbsLibrary { "rulesapplicator.h", "rulesevaluationcontext.cpp", "rulesevaluationcontext.h", - "scriptclasspropertyiterator.h", "timestampsupdater.cpp", "timestampsupdater.h", "transformer.cpp", @@ -233,10 +226,14 @@ QbsLibrary { "environmentextension.cpp", "file.cpp", "fileinfoextension.cpp", + "host.cpp", + "jsextension.h", "jsextensions.cpp", "jsextensions.h", "moduleproperties.cpp", "moduleproperties.h", + "pkgconfigjs.cpp", + "pkgconfigjs.h", "process.cpp", "temporarydir.cpp", "textfile.cpp", @@ -258,7 +255,7 @@ QbsLibrary { prefix: "jsextensions/" condition: qbs.targetOS.contains("darwin") files: [ - "propertylist.mm", + "propertylist_darwin.mm", "propertylistutils.h", "propertylistutils.mm", ] @@ -269,20 +266,13 @@ QbsLibrary { files: [ "artifactproperties.cpp", "artifactproperties.h", - "astimportshandler.cpp", - "astimportshandler.h", - "astpropertiesitemhandler.cpp", - "astpropertiesitemhandler.h", "asttools.cpp", "asttools.h", "builtindeclarations.cpp", "builtindeclarations.h", "deprecationinfo.h", - "evaluationdata.h", "evaluator.cpp", "evaluator.h", - "evaluatorscriptclass.cpp", - "evaluatorscriptclass.h", "filecontext.cpp", "filecontext.h", "filecontextbase.cpp", @@ -298,27 +288,13 @@ QbsLibrary { "itemobserver.h", "itempool.cpp", "itempool.h", - "itemreader.cpp", - "itemreader.h", - "itemreaderastvisitor.cpp", - "itemreaderastvisitor.h", - "itemreadervisitorstate.cpp", - "itemreadervisitorstate.h", "itemtype.h", "jsimports.h", "language.cpp", "language.h", - "loader.cpp", - "loader.h", - "moduleloader.cpp", - "moduleloader.h", - "modulemerger.cpp", - "modulemerger.h", "moduleproviderinfo.h", "preparescriptobserver.cpp", "preparescriptobserver.h", - "projectresolver.cpp", - "projectresolver.h", "property.cpp", "property.h", "propertydeclaration.cpp", @@ -346,6 +322,50 @@ QbsLibrary { files: "language/forward_decls.h" } Group { + name: "loader" + prefix: name + '/' + files: [ + "astimportshandler.cpp", + "astimportshandler.h", + "astpropertiesitemhandler.cpp", + "astpropertiesitemhandler.h", + "dependenciesresolver.cpp", + "dependenciesresolver.h", + "groupshandler.cpp", + "groupshandler.h", + "itemreader.cpp", + "itemreader.h", + "itemreaderastvisitor.cpp", + "itemreaderastvisitor.h", + "itemreadervisitorstate.cpp", + "itemreadervisitorstate.h", + "loaderutils.cpp", + "loaderutils.h", + "localprofiles.cpp", + "localprofiles.h", + "moduleinstantiator.cpp", + "moduleinstantiator.h", + "moduleloader.cpp", + "moduleloader.h", + "modulepropertymerger.cpp", + "modulepropertymerger.h", + "moduleproviderloader.cpp", + "moduleproviderloader.h", + "probesresolver.cpp", + "probesresolver.h", + "productitemmultiplexer.cpp", + "productitemmultiplexer.h", + "productresolver.cpp", + "productresolver.h", + "productscollector.cpp", + "productscollector.h", + "productsresolver.cpp", + "productsresolver.h", + "projectresolver.cpp", + "projectresolver.h", + ] + } + Group { name: "logging" prefix: name + '/' files: [ @@ -369,6 +389,7 @@ QbsLibrary { files: [ "qmlerror.cpp", "qmlerror.h", + "qmljs.g", "qmljsast.cpp", "qmljsast_p.h", "qmljsastfwd_p.h", @@ -395,9 +416,12 @@ QbsLibrary { "buildgraphlocker.cpp", "buildgraphlocker.h", "buildoptions.cpp", + "clangclinfo.cpp", + "clangclinfo.h", "cleanoptions.cpp", "codelocation.cpp", "commandechomode.cpp", + "deprecationwarningmode.cpp", "dynamictypecheck.h", "error.cpp", "executablefinder.cpp", @@ -427,8 +451,10 @@ QbsLibrary { "msvcinfo.cpp", "msvcinfo.h", "pathutils.h", + "pimpl.h", "persistence.cpp", "persistence.h", + "porting.h", "preferences.cpp", "processresult.cpp", "processresult_p.h", @@ -440,6 +466,7 @@ QbsLibrary { "progressobserver.cpp", "progressobserver.h", "projectgeneratormanager.cpp", + "propagate_const.h", "qbsassert.cpp", "qbsassert.h", "qbspluginmanager.cpp", @@ -482,10 +509,12 @@ QbsLibrary { "cleanoptions.h", "codelocation.h", "commandechomode.h", + "deprecationwarningmode.h", "error.h", "generateoptions.h", "installoptions.h", "joblimits.h", + "mutexdata.h", "preferences.h", "processresult.h", "profile.h", @@ -510,17 +539,4 @@ QbsLibrary { "applecodesignutils.h" ] } - Group { - name: "use_installed.pri" - files: [ - "use_installed_corelib.pri", - "../../../qbs_version.pri" - ] - qbs.install: qbsbuildconfig.installApiHeaders - qbs.installDir: headerInstallPrefix - } - Export { - Depends { name: "cpp" } - cpp.defines: base.concat(product.projectFileUpdateDefines) - } } diff --git a/src/lib/corelib/generators/generator.cpp b/src/lib/corelib/generators/generator.cpp index 90bebdcaa..8fdd3555e 100644 --- a/src/lib/corelib/generators/generator.cpp +++ b/src/lib/corelib/generators/generator.cpp @@ -58,14 +58,11 @@ public: }; ProjectGenerator::ProjectGenerator() - : d(new ProjectGeneratorPrivate) + : d(std::make_unique<ProjectGeneratorPrivate>()) { } -ProjectGenerator::~ProjectGenerator() -{ - delete d; -} +ProjectGenerator::~ProjectGenerator() = default; static QString _configurationName(const Project &project) { @@ -85,14 +82,11 @@ ErrorInfo ProjectGenerator::generate(const QList<Project> &projects, const QString &qbsSettingsDir, const Internal::Logger &logger) { - d->projects = projects; - std::sort(d->projects.begin(), d->projects.end(), - [](const Project &a, const Project &b) { - return _configurationName(a) < _configurationName(b); }); - d->buildConfigurations = buildConfigurations; - std::sort(d->buildConfigurations.begin(), d->buildConfigurations.end(), - [](const QVariantMap &a, const QVariantMap &b) { - return _configurationName(a) < _configurationName(b); }); + d->projects = Internal::sorted(projects, [](const Project &lhs, const Project &rhs) { + return _configurationName(lhs) < _configurationName(rhs); }); + d->buildConfigurations = Internal::sorted( + buildConfigurations, [](const QVariantMap &lhs, const QVariantMap &rhs) { + return _configurationName(lhs) < _configurationName(rhs); }); d->installOptions = installOptions; d->qbsSettingsDir = qbsSettingsDir; d->logger = logger; @@ -213,7 +207,7 @@ const GeneratableProject ProjectGenerator::project() const { QMap<QString, ProjectData> rootProjects; GeneratableProject proj; - for (const auto &project : qAsConst(d->projects)) { + for (const auto &project : std::as_const(d->projects)) { const QString configurationName = _configurationName(project); rootProjects.insert(configurationName, project.projectData()); proj.projects.insert(configurationName, project); diff --git a/src/lib/corelib/generators/generator.h b/src/lib/corelib/generators/generator.h index 775469f18..a7cc56b51 100644 --- a/src/lib/corelib/generators/generator.h +++ b/src/lib/corelib/generators/generator.h @@ -45,6 +45,8 @@ #include <QtCore/qlist.h> #include <QtCore/qstring.h> +#include <memory> + namespace qbs { class ProjectGeneratorPrivate; @@ -89,7 +91,7 @@ private: QVariantMap buildConfiguration(const Project &project) const; QStringList buildConfigurationCommandLine(const Project &project) const; - ProjectGeneratorPrivate *d; + const std::unique_ptr<ProjectGeneratorPrivate> d; }; } // namespace qbs diff --git a/src/lib/corelib/generators/generatordata.cpp b/src/lib/corelib/generators/generatordata.cpp index 7c6573484..25afae23d 100644 --- a/src/lib/corelib/generators/generatordata.cpp +++ b/src/lib/corelib/generators/generatordata.cpp @@ -133,7 +133,7 @@ QFileInfo GeneratableProject::filePath() const filePath.insert(it.value().location().filePath()); } Q_ASSERT(filePath.size() == 1); - return *filePath.begin(); + return QFileInfo(*filePath.begin()); } bool GeneratableProject::hasMultipleConfigurations() const diff --git a/src/lib/corelib/generators/generators.pri b/src/lib/corelib/generators/generators.pri deleted file mode 100644 index e9730d895..000000000 --- a/src/lib/corelib/generators/generators.pri +++ /dev/null @@ -1,38 +0,0 @@ -include(../../../install_prefix.pri) - -SOURCES += \ - $$PWD/generatableprojectiterator.cpp \ - $$PWD/generator.cpp \ - $$PWD/generatordata.cpp \ - $$PWD/generatorutils.cpp \ - $$PWD/generatorversioninfo.cpp \ - $$PWD/xmlproject.cpp \ - $$PWD/xmlprojectwriter.cpp\ - $$PWD/xmlproperty.cpp \ - $$PWD/xmlpropertygroup.cpp \ - $$PWD/xmlworkspace.cpp \ - $$PWD/xmlworkspacewriter.cpp - -HEADERS += \ - $$PWD/generatableprojectiterator.h \ - $$PWD/generator.h \ - $$PWD/generatordata.h \ - $$PWD/generatorutils.h \ - $$PWD/generatorversioninfo.h \ - $$PWD/igeneratableprojectvisitor.h \ - $$PWD/ixmlnodevisitor.h \ - $$PWD/ixmlnodevisitor.h \ - $$PWD/xmlproject.h \ - $$PWD/xmlprojectwriter.h \ - $$PWD/xmlproperty.h \ - $$PWD/xmlpropertygroup.h \ - $$PWD/xmlworkspace.h \ - $$PWD/xmlworkspacewriter.h - -!qbs_no_dev_install { - generators_headers.files = \ - $$PWD/generator.h \ - $$PWD/generatordata.h - generators_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/generators - INSTALLS += generators_headers -} diff --git a/src/lib/corelib/generators/generatorutils.cpp b/src/lib/corelib/generators/generatorutils.cpp index 9c00eef05..d2dcbba25 100644 --- a/src/lib/corelib/generators/generatorutils.cpp +++ b/src/lib/corelib/generators/generatorutils.cpp @@ -30,6 +30,8 @@ #include "generatorutils.h" +#include <tools/stlutils.h> + namespace qbs { namespace gen { namespace utils { @@ -124,8 +126,8 @@ std::vector<ProductData> dependenciesOf(const ProductData &qbsProduct, const QString &configurationName) { std::vector<ProductData> result; - const auto depsNames = qbsProduct.dependencies(); - for (const auto &product : qAsConst(genProject.products)) { + const auto &depsNames = qbsProduct.dependencies(); + for (const auto &product : std::as_const(genProject.products)) { const auto pt = product.type(); if (!pt.contains(QLatin1String("staticlibrary"))) continue; @@ -139,12 +141,12 @@ std::vector<ProductData> dependenciesOf(const ProductData &qbsProduct, QString targetBinary(const ProductData &qbsProduct) { - const auto type = qbsProduct.type(); + const auto &type = qbsProduct.type(); if (type.contains(QLatin1String("application"))) { return QFileInfo(qbsProduct.targetExecutable()).fileName(); - } else if (type.contains(QLatin1String("staticlibrary"))) { - const auto artifacts = qbsProduct.targetArtifacts(); - for (const auto &artifact : artifacts) { + } + if (type.contains(QLatin1String("staticlibrary"))) { + for (const auto &artifact : qbsProduct.targetArtifacts()) { if (artifact.fileTags().contains(QLatin1String("staticlibrary"))) return QFileInfo(artifact.filePath()).fileName(); } @@ -189,11 +191,10 @@ QStringList cppStringModuleProperties(const PropertyMap &qbsProps, { QStringList properties; for (const auto &propertyName : propertyNames) { - const auto entries = qbsProps.getModuleProperty( - Internal::StringConstants::cppModule(), - propertyName).toStringList(); - for (const auto &entry : entries) - properties.push_back(entry.trimmed()); + const auto entries = qbsProps.getModuleProperty(Internal::StringConstants::cppModule(), + propertyName).toStringList(); + Internal::transform(entries, properties, [](const auto &entry) { + return entry.trimmed(); }); } return properties; } @@ -221,7 +222,7 @@ static QString parseFlagValue(const QString &flagKey, return parts.at(1).trimmed(); } else if (flagKey < *flagIt) { // In this case an option is in form of 'flagKey<flagValue>'. - return flagIt->mid(flagKey.count()).trimmed(); + return flagIt->mid(flagKey.size()).trimmed(); } else { // In this case an option is in form of 'flagKey <flagValue>'. ++flagIt; diff --git a/src/lib/corelib/generators/generatorutils.h b/src/lib/corelib/generators/generatorutils.h index 9348ab18c..58e59cbf8 100644 --- a/src/lib/corelib/generators/generatorutils.h +++ b/src/lib/corelib/generators/generatorutils.h @@ -41,14 +41,17 @@ namespace gen { namespace utils { enum class Architecture { - Arm, - Avr, - Mcs51, - Stm8, - Msp430, - Unknown + Unknown = 0, + Arm = 1 << 1, + Avr = 1 << 2, + Mcs51 = 1 << 3, + Stm8 = 1 << 4, + Msp430 = 1 << 5 }; +Q_DECLARE_FLAGS(ArchitectureFlags, Architecture) +Q_DECLARE_OPERATORS_FOR_FLAGS(ArchitectureFlags) + QBS_EXPORT QString architectureName(Architecture arch); QBS_EXPORT Architecture architecture(const Project &qbsProject); QBS_EXPORT QString buildConfigurationName(const Project &qbsProject); diff --git a/src/lib/corelib/generators/generatorversioninfo.cpp b/src/lib/corelib/generators/generatorversioninfo.cpp index 3e2106b57..c5c8db03f 100644 --- a/src/lib/corelib/generators/generatorversioninfo.cpp +++ b/src/lib/corelib/generators/generatorversioninfo.cpp @@ -42,42 +42,10 @@ namespace qbs { namespace gen { -VersionInfo::VersionInfo(const Version &version, - const std::set<utils::Architecture> &archs) - : m_version(version), m_archs(archs) -{ -} - -bool VersionInfo::operator<(const VersionInfo &other) const -{ - return m_version < other.m_version; -} - -bool VersionInfo::operator==(const VersionInfo &other) const -{ - return m_version == other.m_version - && m_archs == other.m_archs; -} - -Version VersionInfo::version() const -{ - return m_version; -} - -bool VersionInfo::containsArchitecture(utils::Architecture arch) const -{ - return m_archs.find(arch) != m_archs.cend(); -} - int VersionInfo::marketingVersion() const { return m_version.majorVersion(); } -quint32 qHash(const VersionInfo &info) -{ - return qHash(info.version().toString()); -} - } // namespace gen } // namespace qbs diff --git a/src/lib/corelib/generators/generatorversioninfo.h b/src/lib/corelib/generators/generatorversioninfo.h index 65bfcf685..5c867326f 100644 --- a/src/lib/corelib/generators/generatorversioninfo.h +++ b/src/lib/corelib/generators/generatorversioninfo.h @@ -45,7 +45,7 @@ #include <tools/qbs_export.h> #include <tools/version.h> -#include <set> +#include <QFlags> namespace qbs { namespace gen { @@ -53,24 +53,31 @@ namespace gen { class QBS_EXPORT VersionInfo { public: - VersionInfo(const Version &version, - const std::set<utils::Architecture> &archs); - virtual ~VersionInfo() = default; + constexpr VersionInfo(const Version &version, utils::ArchitectureFlags archs) + : m_version(version), m_archs(archs) + { + } - bool operator<(const VersionInfo &other) const; - bool operator==(const VersionInfo &other) const; + constexpr bool operator<(const VersionInfo &other) const { return m_version < other.m_version; } + constexpr bool operator==(const VersionInfo &other) const + { + return m_version == other.m_version && m_archs == other.m_archs; + } - Version version() const; - bool containsArchitecture(utils::Architecture arch) const; + constexpr Version version() const { return m_version; } + constexpr bool containsArchitecture(utils::Architecture arch) const { return m_archs & arch; } - virtual int marketingVersion() const; + int marketingVersion() const; private: Version m_version; - std::set<utils::Architecture> m_archs; + utils::ArchitectureFlags m_archs; }; -quint32 qHash(const VersionInfo &info); +inline auto qHash(const VersionInfo &info) +{ + return qHash(info.version().toString()); +} } // namespace gen } // namespace qbs diff --git a/src/lib/corelib/generators/xmlpropertygroup.cpp b/src/lib/corelib/generators/xmlpropertygroup.cpp index 1dc92e553..c9e6a97c1 100644 --- a/src/lib/corelib/generators/xmlpropertygroup.cpp +++ b/src/lib/corelib/generators/xmlpropertygroup.cpp @@ -45,8 +45,7 @@ void PropertyGroup::appendProperty(QByteArray name, QVariant value) appendChild<Property>(std::move(name), std::move(value)); } -void PropertyGroup::appendMultiLineProperty( - QByteArray key, QStringList values, QChar sep) +void PropertyGroup::appendMultiLineProperty(QByteArray key, const QStringList &values, QChar sep) { const auto line = values.join(sep); appendProperty(std::move(key), QVariant::fromValue(line)); diff --git a/src/lib/corelib/generators/xmlpropertygroup.h b/src/lib/corelib/generators/xmlpropertygroup.h index e63b515fc..e7e051a87 100644 --- a/src/lib/corelib/generators/xmlpropertygroup.h +++ b/src/lib/corelib/generators/xmlpropertygroup.h @@ -52,7 +52,7 @@ public: explicit PropertyGroup(QByteArray name); void appendProperty(QByteArray name, QVariant value); - void appendMultiLineProperty(QByteArray key, QStringList values, + void appendMultiLineProperty(QByteArray key, const QStringList &values, QChar sep = QLatin1Char(',')); void accept(INodeVisitor *visitor) const final; diff --git a/src/lib/corelib/jsextensions/binaryfile.cpp b/src/lib/corelib/jsextensions/binaryfile.cpp index 5f28f689b..e4d2583dd 100644 --- a/src/lib/corelib/jsextensions/binaryfile.cpp +++ b/src/lib/corelib/jsextensions/binaryfile.cpp @@ -39,26 +39,22 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/hostosinfo.h> +#include <tools/stlutils.h> #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qobject.h> -#include <QtCore/qvariant.h> - -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> namespace qbs { namespace Internal { -class BinaryFile : public QObject, public QScriptable, public ResourceAcquiringScriptObject +class BinaryFile : public JsExtension<BinaryFile> { - Q_OBJECT - Q_ENUMS(OpenMode) + friend class JsExtension<BinaryFile>; public: enum OpenMode { ReadOnly = 1, @@ -66,69 +62,85 @@ public: ReadWrite = ReadOnly | WriteOnly }; - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - ~BinaryFile() override; - - Q_INVOKABLE void close(); - Q_INVOKABLE QString filePath(); - Q_INVOKABLE bool atEof() const; - Q_INVOKABLE qint64 size() const; - Q_INVOKABLE void resize(qint64 size); - Q_INVOKABLE qint64 pos() const; - Q_INVOKABLE void seek(qint64 pos); - Q_INVOKABLE QVariantList read(qint64 size); - Q_INVOKABLE void write(const QVariantList &data); + static const char *name() { return "BinaryFile"; } + static void declareEnums(JSContext *ctx, JSValue classObj); + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int); private: - explicit BinaryFile(QScriptContext *context, const QString &filePath, OpenMode mode = ReadOnly); - - bool checkForClosed() const; - - // ResourceAcquiringScriptObject implementation - void releaseResources() override; - - QFile *m_file = nullptr; + static void setupMethods(JSContext *ctx, JSValue obj); + + DEFINE_JS_FORWARDER(jsClose, &BinaryFile::close, "BinaryFile.close") + DEFINE_JS_FORWARDER(jsFilePath, &BinaryFile::filePath, "BinaryFile.filePath") + DEFINE_JS_FORWARDER(jsAtEof, &BinaryFile::atEof, "BinaryFile.atEof") + DEFINE_JS_FORWARDER(jsSize, &BinaryFile::size, "BinaryFile.size") + DEFINE_JS_FORWARDER(jsResize, &BinaryFile::resize, "BinaryFile.resize") + DEFINE_JS_FORWARDER(jsPos, &BinaryFile::pos, "BinaryFile.pos") + DEFINE_JS_FORWARDER(jsSeek, &BinaryFile::seek, "BinaryFile.seek") + DEFINE_JS_FORWARDER(jsRead, &BinaryFile::read, "BinaryFile.read") + DEFINE_JS_FORWARDER(jsWrite, &BinaryFile::write, "BinaryFile.write") + + void close(); + QString filePath(); + bool atEof() const; + qint64 size() const; + void resize(qint64 size); + qint64 pos() const; + void seek(qint64 pos); + QByteArray read(qint64 size); + void write(const QByteArray &data); + + explicit BinaryFile(JSContext *, const QString &filePath, OpenMode mode); + + void checkForClosed() const; + + std::unique_ptr<QFile> m_file; }; -QScriptValue BinaryFile::ctor(QScriptContext *context, QScriptEngine *engine) +void BinaryFile::declareEnums(JSContext *ctx, JSValue classObj) { - BinaryFile *t = nullptr; - switch (context->argumentCount()) { - case 0: - return context->throwError(Tr::tr("BinaryFile constructor needs " - "path of file to be opened.")); - case 1: - t = new BinaryFile(context, context->argument(0).toString()); - break; - case 2: - t = new BinaryFile(context, - context->argument(0).toString(), - static_cast<OpenMode>(context->argument(1).toInt32())); - break; - default: - return context->throwError(Tr::tr("BinaryFile constructor takes at most two parameters.")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - se->addResourceAcquiringScriptObject(t); - const DubiousContextList dubiousContexts { - DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }; - se->checkContext(QStringLiteral("qbs.BinaryFile"), dubiousContexts); - se->setUsesIo(); + DECLARE_ENUM(ctx, classObj, ReadOnly); + DECLARE_ENUM(ctx, classObj, WriteOnly); + DECLARE_ENUM(ctx, classObj, ReadWrite); +} - return engine->newQObject(t, QScriptEngine::QtOwnership); +void BinaryFile::setupMethods(JSContext *ctx, JSValue obj) +{ + setupMethod(ctx, obj, "close", &jsClose, 0); + setupMethod(ctx, obj, "filePath", &jsFilePath, 0); + setupMethod(ctx, obj, "atEof", &jsAtEof, 0); + setupMethod(ctx, obj, "size", &jsSize, 0); + setupMethod(ctx, obj, "resize", &jsResize, 0); + setupMethod(ctx, obj, "pos", &jsPos, 0); + setupMethod(ctx, obj, "seek", &jsSeek, 0); + setupMethod(ctx, obj, "read", &jsRead, 0); + setupMethod(ctx, obj, "write", &jsWrite, 0); } -BinaryFile::~BinaryFile() -{ - delete m_file; +JSValue BinaryFile::ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int) +{ + try { + const auto filePath = getArgument<QString>(ctx, "BinaryFile constructor", argc, argv); + OpenMode mode = ReadOnly; + if (argc > 1) { + mode = static_cast<OpenMode> + (fromArg<qint32>(ctx, "BinaryFile constructor", 2, argv[1])); + } + const JSValue obj = createObject(ctx, filePath, mode); + + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts { + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }; + se->checkContext(QStringLiteral("qbs.BinaryFile"), dubiousContexts); + se->setUsesIo(); + return obj; + } catch (const QString &error) { return throwError(ctx, error); } } -BinaryFile::BinaryFile(QScriptContext *context, const QString &filePath, OpenMode mode) +BinaryFile::BinaryFile(JSContext *, const QString &filePath, OpenMode mode) { - Q_ASSERT(thisObject().engine() == engine()); - QIODevice::OpenMode m = QIODevice::NotOpen; switch (mode) { case ReadWrite: @@ -141,136 +153,91 @@ BinaryFile::BinaryFile(QScriptContext *context, const QString &filePath, OpenMod m = QIODevice::WriteOnly; break; default: - context->throwError(Tr::tr("Unable to open file '%1': Undefined mode '%2'") - .arg(filePath, mode)); - return; + throw Tr::tr("Unable to open file '%1': Undefined mode '%2'").arg(filePath).arg(mode); } - m_file = new QFile(filePath); - if (Q_UNLIKELY(!m_file->open(m))) { - context->throwError(Tr::tr("Unable to open file '%1': %2") - .arg(filePath, m_file->errorString())); - delete m_file; - m_file = nullptr; - } + auto file = std::make_unique<QFile>(filePath); + if (Q_UNLIKELY(!file->open(m))) + throw Tr::tr("Unable to open file '%1': %2").arg(filePath, file->errorString()); + m_file = std::move(file); } void BinaryFile::close() { - if (checkForClosed()) - return; + checkForClosed(); m_file->close(); - delete m_file; - m_file = nullptr; + m_file.reset(); } QString BinaryFile::filePath() { - if (checkForClosed()) - return {}; + checkForClosed(); return QFileInfo(*m_file).absoluteFilePath(); } bool BinaryFile::atEof() const { - if (checkForClosed()) - return true; + checkForClosed(); return m_file->atEnd(); } qint64 BinaryFile::size() const { - if (checkForClosed()) - return -1; + checkForClosed(); return m_file->size(); } void BinaryFile::resize(qint64 size) { - if (checkForClosed()) - return; - if (Q_UNLIKELY(!m_file->resize(size))) { - context()->throwError(Tr::tr("Could not resize '%1': %2") - .arg(m_file->fileName(), m_file->errorString())); - } + checkForClosed(); + if (Q_UNLIKELY(!m_file->resize(size))) + throw Tr::tr("Could not resize '%1': %2").arg(m_file->fileName(), m_file->errorString()); } qint64 BinaryFile::pos() const { - if (checkForClosed()) - return -1; + checkForClosed(); return m_file->pos(); } void BinaryFile::seek(qint64 pos) { - if (checkForClosed()) - return; - if (Q_UNLIKELY(!m_file->seek(pos))) { - context()->throwError(Tr::tr("Could not seek '%1': %2") - .arg(m_file->fileName(), m_file->errorString())); - } + checkForClosed(); + if (Q_UNLIKELY(!m_file->seek(pos))) + throw Tr::tr("Could not seek '%1': %2").arg(m_file->fileName(), m_file->errorString()); } -QVariantList BinaryFile::read(qint64 size) +QByteArray BinaryFile::read(qint64 size) { - if (checkForClosed()) - return {}; - const QByteArray bytes = m_file->read(size); + checkForClosed(); + QByteArray bytes = m_file->read(size); if (Q_UNLIKELY(bytes.size() == 0 && m_file->error() != QFile::NoError)) { - context()->throwError(Tr::tr("Could not read from '%1': %2") - .arg(m_file->fileName(), m_file->errorString())); + throw (Tr::tr("Could not read from '%1': %2") + .arg(m_file->fileName(), m_file->errorString())); } - - QVariantList data; - std::for_each(bytes.constBegin(), bytes.constEnd(), [&data](const char &c) { - data.append(c); }); - return data; + return bytes; } -void BinaryFile::write(const QVariantList &data) +void BinaryFile::write(const QByteArray &data) { - if (checkForClosed()) - return; - - QByteArray bytes; - std::for_each(data.constBegin(), data.constEnd(), [&bytes](const QVariant &v) { - bytes.append(v.toUInt() & 0xFF); }); - - const qint64 size = m_file->write(bytes); + checkForClosed(); + const qint64 size = m_file->write(data); if (Q_UNLIKELY(size == -1)) { - context()->throwError(Tr::tr("Could not write to '%1': %2") - .arg(m_file->fileName(), m_file->errorString())); + throw Tr::tr("Could not write to '%1': %2") + .arg(m_file->fileName(), m_file->errorString()); } } -bool BinaryFile::checkForClosed() const -{ - if (m_file) - return false; - if (QScriptContext *ctx = context()) - ctx->throwError(Tr::tr("Access to BinaryFile object that was already closed.")); - return true; -} - -void BinaryFile::releaseResources() +void BinaryFile::checkForClosed() const { - close(); - deleteLater(); + if (!m_file) + throw Tr::tr("Access to BinaryFile object that was already closed."); } } // namespace Internal } // namespace qbs -void initializeJsExtensionBinaryFile(QScriptValue extensionObject) +void initializeJsExtensionBinaryFile(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - const QScriptValue obj = engine->newQMetaObject(&BinaryFile::staticMetaObject, - engine->newFunction(&BinaryFile::ctor)); - extensionObject.setProperty(QStringLiteral("BinaryFile"), obj); + qbs::Internal::BinaryFile::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::BinaryFile *) - -#include "binaryfile.moc" diff --git a/src/lib/corelib/jsextensions/domxml.cpp b/src/lib/corelib/jsextensions/domxml.cpp index 118e8d5e1..35cff186b 100644 --- a/src/lib/corelib/jsextensions/domxml.cpp +++ b/src/lib/corelib/jsextensions/domxml.cpp @@ -38,429 +38,516 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <QtCore/qfile.h> -#include <QtCore/qobject.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> -#include <QtScript/qscriptable.h> - #include <QtXml/qdom.h> namespace qbs { namespace Internal { -class XmlDomDocument; - -class XmlDomNode: public QObject, public QScriptable +template<class C> class XmlDomNode : public JsExtension<XmlDomNode<C>> { - 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; -}; + static const char *name(); + XmlDomNode(const C &value) : m_value(value) {} + XmlDomNode(JSContext *, const QString &name); + XmlDomNode(JSContext *) {} -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); + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int); + static void setupMethods(JSContext *ctx, JSValue obj); - 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()); + C value() const { return m_value; } private: - QDomDocument m_domDocument; -}; - -QScriptValue XmlDomDocument::ctor(QScriptContext *context, QScriptEngine *engine) -{ - XmlDomDocument *xml = nullptr; - 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(QStringLiteral("DomXml(QString file = QLatin1String(\"\"))")); - } - QScriptValue obj = engine->newQObject(xml, QScriptEngine::ScriptOwnership); - static_cast<ScriptEngine *>(engine)->setUsesIo(); - 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(QStringLiteral("unable to open '%1'") - .arg(filePath)); - return; +#define XML_JS_FWD(name, func, text) DEFINE_JS_FORWARDER_QUAL(XmlDomNode, name, func, text) + + XML_JS_FWD(jsIsElement, &XmlDomNode::isElement, "DomNode.isElement"); + XML_JS_FWD(jsIsCDATASection, &XmlDomNode::isCDATASection, "DomNode.isCDATASection"); + XML_JS_FWD(jsIsText, &XmlDomNode::isText, "DomNode.isText"); + XML_JS_FWD(jsHasAttribute, &XmlDomNode::hasAttribute, "DomNode.hasAttribute"); + XML_JS_FWD(jsTagName, &XmlDomNode::tagName, "DomNode.tagName"); + XML_JS_FWD(jsSetTagName, &XmlDomNode::setTagName, "DomNode.setTagName"); + XML_JS_FWD(jsText, &XmlDomNode::text, "DomNode.text"); + XML_JS_FWD(jsData, &XmlDomNode::data, "DomNode.data"); + XML_JS_FWD(jsSetData, &XmlDomNode::setData, "DomNode.setData"); + XML_JS_FWD(jsClear, &XmlDomNode::clear, "DomNode.clear"); + XML_JS_FWD(jsHasAttributes, &XmlDomNode::hasAttributes, "DomNode.hasAttributes"); + XML_JS_FWD(jsHasChildNodes, &XmlDomNode::hasChildNodes, "DomNode.hasChildNodes"); + XML_JS_FWD(jsSetAttribute, &XmlDomNode::setAttribute, "DomNode.setAttribute"); + XML_JS_FWD(jsAppendChild, &XmlDomNode::appendChild, "DomNode.appendChild"); + XML_JS_FWD(jsInsertBefore, &XmlDomNode::insertBefore, "DomNode.insertBefore"); + XML_JS_FWD(jsInsertAfter, &XmlDomNode::insertAfter, "DomNode.insertAfter"); + XML_JS_FWD(jsReplaceChild, &XmlDomNode::replaceChild, "DomNode.replaceChild"); + XML_JS_FWD(jsRemoveChild, &XmlDomNode::removeChild, "DomNode.removeChild"); + + static JSValue jsAttribute(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + try { + const auto name = JsExtension<XmlDomNode>::template getArgument<QString> + (ctx, "DomNode.attribute", argc, argv); + QString defaultValue; + if (argc > 1) + defaultValue = JsExtension<XmlDomNode>::template fromArg<QString> + (ctx, "DomNode.attribute", 2, argv[1]); + return makeJsString(ctx, JsExtension<XmlDomNode>::fromJsObject + (ctx, this_val)->attribute(name, defaultValue)); + } catch (const QString &error) { + return throwError(ctx, error); + } } - - QByteArray buff(m_domDocument.toByteArray(indent)); - if (buff.size() != f.write(buff)) + static JSValue jsParentNode(JSContext *ctx, JSValueConst this_val, int, JSValueConst *) { - context()->throwError(f.errorString()); - f.close(); - return; + return JsExtension<XmlDomNode<QDomNode>>::createObjectDirect( + ctx, JsExtension<XmlDomNode>::fromJsObject(ctx, this_val)->parentNode()); } - - 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(QStringLiteral("unable to open '%1'") - .arg(filePath)); - return; + static JSValue jsFirstChild(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + try { + QString tagName; + if (argc > 0) + tagName = JsExtension<XmlDomNode>::template getArgument<QString> + (ctx, "DomNode.firstChild", argc, argv); + return JsExtension<XmlDomNode<QDomNode>>::createObjectDirect + (ctx, JsExtension<XmlDomNode>::fromJsObject(ctx, this_val)->firstChild(tagName)); + } catch (const QString &error) { + return throwError(ctx, error); + } + } + static JSValue jsLastChild(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + try { + QString tagName; + if (argc > 0) + tagName = JsExtension<XmlDomNode>::template getArgument<QString> + (ctx, "DomNode.lastChild", argc, argv); + return JsExtension<XmlDomNode<QDomNode>>::createObjectDirect + (ctx, JsExtension<XmlDomNode>::fromJsObject(ctx, this_val)->lastChild(tagName)); + } catch (const QString &error) { + return throwError(ctx, error); + } + } + static JSValue jsPreviousSibling(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) + { + try { + QString tagName; + if (argc > 0) + tagName = JsExtension<XmlDomNode>::template getArgument<QString> + (ctx, "DomNode.previousSibling", argc, argv); + return JsExtension<XmlDomNode<QDomNode>>::createObjectDirect(ctx, JsExtension<XmlDomNode>::fromJsObject + (ctx, this_val)->previousSibling(tagName)); + } catch (const QString &error) { + return throwError(ctx, error); + } + } + static JSValue jsNextSibling(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) + { + try { + QString tagName; + if (argc > 0) + tagName = JsExtension<C>::template getArgument<QString> + (ctx, "Xml.DomElement.nextSibling", argc, argv); + return JsExtension<XmlDomNode<QDomNode>>::createObjectDirect + (ctx, JsExtension<XmlDomNode>::fromJsObject(ctx, this_val)->nextSibling(tagName)); + } catch (const QString &error) { + return throwError(ctx, error); + } } - QString errorMsg; - if (!m_domDocument.setContent(&f, &errorMsg)) { - context()->throwError(errorMsg); - return; + // Implemented for QDomDocument only. + static JSValue jsDocumentElement(JSContext *ctx, JSValueConst this_val, int, JSValueConst *); + static JSValue jsCreateElement(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); + static JSValue jsCreateCDATASection(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); + static JSValue jsCreateTextNode(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); + static JSValue jsSetContent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); + static JSValue jsToString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); + static JSValue jsSave(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); + static JSValue jsLoad(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); + + bool isElement() const { return m_value.isElement(); } + bool isCDATASection() const { return m_value.isCDATASection(); } + bool isText() const { return m_value.isText(); } + QString attribute(const QString &name, const QString &defaultValue) + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + return el.attribute(name, defaultValue); + } + void setAttribute(const QString &name, const QString &value) + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + el.setAttribute(name, value); + } + bool hasAttribute(const QString &name) const + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + return el.hasAttribute(name); + } + QString tagName() const + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + return el.tagName(); + } + void setTagName(const QString &name) + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + el.setTagName(name); } -} -XmlDomDocument::XmlDomDocument(QScriptContext *context, const QString &name):m_domDocument(name) -{ - Q_UNUSED(context) - m_domNode = m_domDocument; -} + QString text() const + { + QDomElement el = m_value.toElement(); + if (el.isNull()) + throw QStringLiteral("Node '%1' is not an element node").arg(m_value.nodeName()); + return el.text(); + } -QScriptValue XmlDomNode::ctor(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(context) - return engine->newQObject(new XmlDomNode(), QScriptEngine::ScriptOwnership); -} + QString data() const + { + if (m_value.isText()) + return m_value.toText().data(); + if (m_value.isCDATASection()) + return m_value.toCDATASection().data(); + if (m_value.isCharacterData()) + return m_value.toCharacterData().data(); + throw QStringLiteral("Node '%1' is not a character data node").arg(m_value.nodeName()); + } + void setData(const QString &v) const + { + if (m_value.isText()) + return m_value.toText().setData(v); + if (m_value.isCDATASection()) + return m_value.toCDATASection().setData(v); + if (m_value.isCharacterData()) + return m_value.toCharacterData().setData(v); + throw QStringLiteral("Node '%1' is not a character data node").arg(m_value.nodeName()); + } + void clear() { m_value.clear(); } + bool hasAttributes() const { return m_value.hasAttributes(); } + bool hasChildNodes() const { return m_value.hasChildNodes(); } -bool XmlDomNode::isElement() const -{ - return m_domNode.isElement(); -} + XmlDomNode<QDomNode> *parentNode() const + { + return new XmlDomNode<QDomNode>(m_value.parentNode()); + } + XmlDomNode<QDomNode> *firstChild(const QString &tagName) + { + if (tagName.isEmpty()) + return new XmlDomNode<QDomNode>(m_value.firstChild()); + return new XmlDomNode<QDomNode>(m_value.firstChildElement(tagName)); + } + XmlDomNode<QDomNode> *lastChild(const QString &tagName) const + { + if (tagName.isEmpty()) + return new XmlDomNode<QDomNode>(m_value.lastChild()); + return new XmlDomNode<QDomNode>(m_value.lastChildElement(tagName)); + } + XmlDomNode<QDomNode> *previousSibling(const QString &tagName) const + { + if (tagName.isEmpty()) + return new XmlDomNode<QDomNode>(m_value.previousSibling()); + return new XmlDomNode<QDomNode>(m_value.previousSiblingElement(tagName)); + } + XmlDomNode<QDomNode> *nextSibling(const QString &tagName) const + { + if (tagName.isEmpty()) + return new XmlDomNode<QDomNode>(m_value.nextSibling()); + return new XmlDomNode<QDomNode>(m_value.nextSiblingElement(tagName)); + } + void appendChild(const XmlDomNode<QDomNode> *newChild) + { + m_value.appendChild(newChild->value()); + } + void insertBefore(const XmlDomNode<QDomNode> *newChild, const XmlDomNode<QDomNode> *refChild) + { + m_value.insertBefore(newChild->value(), refChild->value()); + } + void insertAfter(const XmlDomNode<QDomNode> *newChild, const XmlDomNode<QDomNode> *refChild) + { + m_value.insertAfter(newChild->value(), refChild->value()); + } + void replaceChild(const XmlDomNode<QDomNode> *newChild, const XmlDomNode<QDomNode> *oldChild) + { + m_value.replaceChild(newChild->value(), oldChild->value()); + } + void removeChild(const XmlDomNode<QDomNode> *oldChild) + { + m_value.removeChild(oldChild->value()); + } -bool XmlDomNode::isCDATASection() const -{ - return m_domNode.isCDATASection(); -} + // Implemented for QDomDocument only. + XmlDomNode<QDomNode> *documentElement(); + XmlDomNode<QDomNode> *createElement(const QString &tagName); + XmlDomNode<QDomNode> *createCDATASection(const QString &value); + XmlDomNode<QDomNode> *createTextNode(const QString &value); -bool XmlDomNode::isText() const -{ - return m_domNode.isText(); -} + bool setContent(const QString &content); + QString toString(int indent = 1); -QString XmlDomNode::attribute(const QString &name, const QString &defValue) -{ - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return defValue; - } - return el.attribute(name, defValue); -} + void save(const QString &filePath, int indent = 1); + void load(const QString &filePath); -void XmlDomNode::setAttribute(const QString &name, const QString &value) -{ - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return; - } - el.setAttribute(name, value); -} + C m_value; +}; -bool XmlDomNode::hasAttribute(const QString &name) const -{ - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return false; - } - return el.hasAttribute(name); -} +template<> const char *XmlDomNode<QDomNode>::name() { return "DomNode"; } +template<> const char *XmlDomNode<QDomDocument>::name() { return "DomDocument"; } -QString XmlDomNode::tagName() const +template<> JSValue XmlDomNode<QDomNode>::ctor(JSContext *ctx, JSValue, JSValue, int , JSValue *, + int) { - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return {}; - } - return el.tagName(); + const JSValue obj = createObject(ctx); + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }; + se->checkContext(QStringLiteral("qbs.Xml.DomNode"), dubiousContexts); + se->setUsesIo(); + return obj; } -void XmlDomNode::setTagName(const QString &name) +template<> JSValue XmlDomNode<QDomDocument>::ctor(JSContext *ctx, JSValue, JSValue, + int argc, JSValue *argv, int) { - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return; - } - el.setTagName(name); + try { + JSValue obj; + if (argc == 0) { + obj = createObject(ctx); + } else { + const auto name = getArgument<QString>(ctx, "XmlDomDocument constructor", + argc, argv); + obj = createObject(ctx, name); + } + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }; + se->checkContext(QStringLiteral("qbs.Xml.DomDocument"), dubiousContexts); + se->setUsesIo(); + return obj; + } catch (const QString &error) { return throwError(ctx, error); } } -QString XmlDomNode::text() const +template<> XmlDomNode<QDomNode> *XmlDomNode<QDomDocument>::documentElement() { - QDomElement el = m_domNode.toElement(); - if (el.isNull()) { - context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); - return {}; - } - return el.text(); + return new XmlDomNode<QDomNode>(m_value.documentElement()); } -QString XmlDomNode::data() const +template<> XmlDomNode<QDomNode> *XmlDomNode<QDomDocument>::createElement(const QString &tagName) { - 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(QStringLiteral("Node '%1' is not a character data node").arg(m_domNode.nodeName())); - return {}; + return new XmlDomNode<QDomNode>(m_value.createElement(tagName)); } -void XmlDomNode::setData(const QString &v) const +template<> XmlDomNode<QDomNode> *XmlDomNode<QDomDocument>::createCDATASection(const QString &value) { - 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(QStringLiteral("Node '%1' is not a character data node").arg(m_domNode.nodeName())); - return; + return new XmlDomNode<QDomNode>(m_value.createCDATASection(value)); } -void XmlDomNode::clear() +template<> XmlDomNode<QDomNode> *XmlDomNode<QDomDocument>::createTextNode(const QString &value) { - m_domNode.clear(); + return new XmlDomNode<QDomNode>(m_value.createTextNode(value)); } -bool XmlDomNode::hasAttributes() const +template<> bool XmlDomNode<QDomDocument>::setContent(const QString &content) { - return m_domNode.hasAttributes(); + return static_cast<bool>(m_value.setContent(content)); } -bool XmlDomNode::hasChildNodes() const +template<> QString XmlDomNode<QDomDocument>::toString(int indent) { - return m_domNode.hasChildNodes(); + return m_value.toString(indent); } -QScriptValue XmlDomNode::parentNode() const +template<> JSValue XmlDomNode<QDomDocument>::jsDocumentElement(JSContext *ctx, JSValue this_val, + int, JSValue *) { - return engine()->newQObject(new XmlDomNode(m_domNode.parentNode()), QScriptEngine::ScriptOwnership); + return XmlDomNode<QDomNode>::createObjectDirect( + ctx, fromJsObject(ctx, this_val)->documentElement()); } -QScriptValue XmlDomNode::firstChild(const QString &tagName) +template<> JSValue XmlDomNode<QDomDocument>::jsCreateElement(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - if (tagName.isEmpty()) - return engine()->newQObject(new XmlDomNode(m_domNode.firstChild()), QScriptEngine::ScriptOwnership); - return engine()->newQObject(new XmlDomNode(m_domNode.firstChildElement(tagName)), QScriptEngine::ScriptOwnership); + try { + const auto tagName = getArgument<QString>(ctx, "DomDocument.createElement", argc, argv); + const JSValue obj = XmlDomNode<QDomNode>::createObjectDirect( + ctx, fromJsObject(ctx, this_val)->createElement(tagName)); + return obj; + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue XmlDomNode::lastChild(const QString &tagName) const +template<> JSValue XmlDomNode<QDomDocument>::jsCreateCDATASection(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - if (tagName.isEmpty()) - return engine()->newQObject(new XmlDomNode(m_domNode.lastChild()), QScriptEngine::ScriptOwnership); - return engine()->newQObject(new XmlDomNode(m_domNode.lastChildElement(tagName)), QScriptEngine::ScriptOwnership); + try { + const auto value = getArgument<QString>(ctx, "DomDocument.createCDATASection", argc, argv); + return XmlDomNode<QDomNode>::createObjectDirect( + ctx, fromJsObject(ctx, this_val)->createCDATASection(value)); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue XmlDomNode::previousSibling(const QString &tagName) const +template<> JSValue XmlDomNode<QDomDocument>::jsCreateTextNode(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - if (tagName.isEmpty()) - return engine()->newQObject(new XmlDomNode(m_domNode.previousSibling()), QScriptEngine::ScriptOwnership); - return engine()->newQObject(new XmlDomNode(m_domNode.previousSiblingElement(tagName)), QScriptEngine::ScriptOwnership); + try { + const auto value = getArgument<QString>(ctx, "DomDocument.createTextNode", argc, argv); + return XmlDomNode<QDomNode>::createObjectDirect( + ctx, fromJsObject(ctx, this_val)->createTextNode(value)); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue XmlDomNode::nextSibling(const QString &tagName) const +template<> JSValue XmlDomNode<QDomDocument>::jsSetContent(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - if (tagName.isEmpty()) - return engine()->newQObject(new XmlDomNode(m_domNode.nextSibling()), QScriptEngine::ScriptOwnership); - return engine()->newQObject(new XmlDomNode(m_domNode.nextSiblingElement(tagName)), QScriptEngine::ScriptOwnership); + try { + const auto content = getArgument<QString>(ctx, "DomDocument.setContent", argc, argv); + return JS_NewBool(ctx, fromJsObject(ctx, this_val)->setContent(content)); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue XmlDomNode::appendChild(QScriptValue newChild) +template<> JSValue XmlDomNode<QDomDocument>::jsToString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - auto newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); - if (!newNode) { - context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); - return {}; + try { + qint32 indent = 1; + if (argc > 0) + indent = getArgument<qint32>(ctx, "DomDocument.toString", argc, argv); + return makeJsString(ctx, fromJsObject(ctx, this_val)->toString(indent)); + } catch (const QString &error) { + return throwError(ctx, error); } - return engine()->newQObject(new XmlDomNode(m_domNode.appendChild(newNode->m_domNode)), QScriptEngine::ScriptOwnership); } -QScriptValue XmlDomNode::insertBefore(const QScriptValue &newChild, const QScriptValue &refChild) +template<> void XmlDomNode<QDomDocument>::save(const QString &filePath, int indent) { - auto newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); - if (!newNode) { - context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); - return {}; - } + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly)) + throw QStringLiteral("unable to open '%1'").arg(filePath); - auto refNode = qobject_cast<XmlDomNode*>(refChild.toQObject()); - if (!refNode) { - context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); - return {}; - } + QByteArray buff(m_value.toByteArray(indent)); + if (buff.size() != f.write(buff)) + throw f.errorString(); - return engine()->newQObject(new XmlDomNode(m_domNode.insertBefore(newNode->m_domNode, refNode->m_domNode)), QScriptEngine::ScriptOwnership); + f.close(); + if (f.error() != QFile::NoError) + throw f.errorString(); } -QScriptValue XmlDomNode::insertAfter(const QScriptValue &newChild, const QScriptValue &refChild) +template<> JSValue XmlDomNode<QDomDocument>::jsSave(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - auto newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); - if (!newNode) { - context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); - return {}; - } - - auto refNode = qobject_cast<XmlDomNode*>(refChild.toQObject()); - if (!refNode) { - context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); - return {}; + try { + const auto filePath = getArgument<QString>(ctx, "DomDocument.save", argc, argv); + qint32 indent = 1; + if (argc > 1) + indent = fromArg<qint32>(ctx, "toString", 2, argv[1]); + fromJsObject(ctx, this_val)->save(filePath, indent); + return JS_UNDEFINED; + } catch (const QString &error) { + return throwError(ctx, error); } - - 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) +template<> void XmlDomNode<QDomDocument>::load(const QString &filePath) { - auto newNode = qobject_cast<XmlDomNode*>(newChild.toQObject()); - if (!newNode) { - context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); - return {}; - } - - auto oldNode = qobject_cast<XmlDomNode*>(oldChild.toQObject()); - if (!oldNode) { - context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); - return {}; - } + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) + throw QStringLiteral("unable to open '%1'").arg(filePath); - return engine()->newQObject(new XmlDomNode(m_domNode.replaceChild(newNode->m_domNode, oldNode->m_domNode)), QScriptEngine::ScriptOwnership); + QString errorMsg; + if (!m_value.setContent(&f, &errorMsg)) + throw errorMsg; } -QScriptValue XmlDomNode::removeChild(const QScriptValue &oldChild) +template<> JSValue XmlDomNode<QDomDocument>::jsLoad(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) { - auto oldNode = qobject_cast<XmlDomNode*>(oldChild.toQObject()); - if (!oldNode) { - context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); - return {}; + try { + const auto filePath = getArgument<QString>(ctx, "DomDocument.load", argc, argv); + fromJsObject(ctx, this_val)->load(filePath); + return JS_UNDEFINED; + } catch (const QString &error) { + return throwError(ctx, error); } - - return engine()->newQObject(new XmlDomNode(m_domNode.removeChild(oldNode->m_domNode)), QScriptEngine::ScriptOwnership); } -XmlDomNode::XmlDomNode(const QDomNode &other) -{ - m_domNode = other; +template<> XmlDomNode<QDomDocument>::XmlDomNode(JSContext *, const QString &name) : m_value(name) {} + +template<class C> +void XmlDomNode<C>::setupMethods(JSContext *ctx, JSValue obj) +{ + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "appendChild", &jsAppendChild, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "attribute", &jsAttribute, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "clear", &jsClear, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "data", &jsData, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "firstChild", &jsFirstChild, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "hasAttribute", &jsHasAttribute, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "hasAttributes", &jsHasAttributes, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "hasChildNodes", &jsHasChildNodes, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "insertAfter", &jsInsertAfter, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "insertBefore", &jsInsertBefore, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "isCDATASection", &jsIsCDATASection, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "isElement", &jsIsElement, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "isText", &jsIsText, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "lastChild", &jsLastChild, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "nextSibling", &jsNextSibling, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "parentNode", &jsParentNode, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "previousSibling", &jsPreviousSibling, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "removeChild", &jsRemoveChild, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "replaceChild", &jsReplaceChild, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "setAttribute", &jsSetAttribute, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "setData", &jsSetData, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "setTagName", &jsSetTagName, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "tagName", &jsTagName, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "text", &jsText, 0); + if constexpr (std::is_same_v<C, QDomDocument>) { + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "documentElement", &jsDocumentElement, 0); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "createElement", &jsCreateElement, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "createCDATASection", + &jsCreateCDATASection, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "createTextNode", &jsCreateTextNode, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "setContent", &jsSetContent, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "toString", &jsToString, 1); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "save", &jsSave, 2); + JsExtension<XmlDomNode>::setupMethod(ctx, obj, "load", &jsLoad, 1); + } } } // namespace Internal } // namespace qbs -void initializeJsExtensionXml(QScriptValue extensionObject) +void initializeJsExtensionXml(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue docObj = engine->newQMetaObject(&XmlDomDocument::staticMetaObject, - engine->newFunction(&XmlDomDocument::ctor)); - QScriptValue nodeObj = engine->newQMetaObject(&XmlDomNode::staticMetaObject, - engine->newFunction(&XmlDomNode::ctor)); - QScriptValue contextObject = engine->newObject(); - contextObject.setProperty(QStringLiteral("DomDocument"), docObj); - contextObject.setProperty(QStringLiteral("DomElement"), nodeObj); - - extensionObject.setProperty(QStringLiteral("Xml"), contextObject); + JSValue contextObject = engine->newObject(); + XmlDomNode<QDomNode>::registerClass(engine, contextObject); + XmlDomNode<QDomDocument>::registerClass(engine, contextObject); + setJsProperty(engine->context(), extensionObject, QLatin1String("Xml"), contextObject); } - -Q_DECLARE_METATYPE(qbs::Internal::XmlDomDocument *) -Q_DECLARE_METATYPE(qbs::Internal::XmlDomNode *) - -#include "domxml.moc" diff --git a/src/lib/corelib/jsextensions/environmentextension.cpp b/src/lib/corelib/jsextensions/environmentextension.cpp index cf17c938b..e973072d0 100644 --- a/src/lib/corelib/jsextensions/environmentextension.cpp +++ b/src/lib/corelib/jsextensions/environmentextension.cpp @@ -37,6 +37,8 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/hostosinfo.h> @@ -44,93 +46,89 @@ #include <QtCore/qdir.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> - namespace qbs { namespace Internal { -class EnvironmentExtension : public QObject, QScriptable +class EnvironmentExtension : public JsExtension<EnvironmentExtension> { - Q_OBJECT public: - void initializeJsExtensionEnvironment(QScriptValue extensionObject); - static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_putEnv(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_unsetEnv(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_currentEnv(QScriptContext *context, QScriptEngine *engine); + static const char *name() { return "Environment"; } + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static JSValue jsGetEnv(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsPutEnv(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsUnsetEnv(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsCurrentEnv(JSContext *ctx, JSValueConst, int, JSValueConst *); }; -QScriptValue EnvironmentExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +void EnvironmentExtension::setupStaticMethods(JSContext *ctx, JSValue classObj) { - Q_UNUSED(engine); - return context->throwError(Tr::tr("'Environment' cannot be instantiated.")); + setupMethod(ctx, classObj, "getEnv", &EnvironmentExtension::jsGetEnv, 1); + setupMethod(ctx, classObj, "putEnv", &EnvironmentExtension::jsPutEnv, 2); + setupMethod(ctx, classObj, "unsetEnv", &EnvironmentExtension::jsUnsetEnv, 1); + setupMethod(ctx, classObj, "currentEnv", &EnvironmentExtension::jsCurrentEnv, 0); } -static QProcessEnvironment *getProcessEnvironment(QScriptContext *context, QScriptEngine *engine, - const QString &func, bool doThrow = true) +static QProcessEnvironment *getProcessEnvironment(ScriptEngine *engine, const QString &func, + bool doThrow = true) { QVariant v = engine->property(StringConstants::qbsProcEnvVarInternal()); auto procenv = reinterpret_cast<QProcessEnvironment *>(v.value<void *>()); - if (!procenv && doThrow) - throw context->throwError(QScriptContext::UnknownError, - QStringLiteral("%1 can only be called from ").arg(func) + - QStringLiteral("Module.setupBuildEnvironment and ") + - QStringLiteral("Module.setupRunEnvironment")); + if (!procenv && doThrow) { + throw QStringLiteral("%1 can only be called from " + "Module.setupBuildEnvironment and " + "Module.setupRunEnvironment").arg(func); + } return procenv; } -QScriptValue EnvironmentExtension::js_getEnv(QScriptContext *context, QScriptEngine *engine) +JSValue EnvironmentExtension::jsGetEnv(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("getEnv expects 1 argument")); - const QProcessEnvironment env = static_cast<ScriptEngine *>(engine)->environment(); - const QProcessEnvironment *procenv = getProcessEnvironment(context, engine, - QStringLiteral("getEnv"), false); - if (!procenv) - procenv = &env; - - const QString name = context->argument(0).toString(); - const QString value = procenv->value(name); - return value.isNull() ? engine->undefinedValue() : value; + try { + const auto name = getArgument<QString>(ctx, "Environment.getEnv", argc, argv); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const QProcessEnvironment env = engine->environment(); + const QProcessEnvironment *procenv = getProcessEnvironment(engine, QStringLiteral("getEnv"), + false); + if (!procenv) + procenv = &env; + const QString value = procenv->value(name); + return value.isNull() ? engine->undefinedValue() : makeJsString(ctx, value); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue EnvironmentExtension::js_putEnv(QScriptContext *context, QScriptEngine *engine) +JSValue EnvironmentExtension::jsPutEnv(JSContext *ctx, JSValue, int argc, JSValue *argv) { - if (Q_UNLIKELY(context->argumentCount() != 2)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("putEnv expects 2 arguments")); - getProcessEnvironment(context, engine, QStringLiteral("putEnv"))->insert( - context->argument(0).toString(), - context->argument(1).toString()); - return engine->undefinedValue(); + try { + const auto args = getArguments<QString, QString>(ctx, "Environment.putEnv", argc, argv); + getProcessEnvironment(ScriptEngine::engineForContext(ctx), QStringLiteral("putEnv")) + ->insert(std::get<0>(args), std::get<1>(args)); + return JS_UNDEFINED; + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue EnvironmentExtension::js_unsetEnv(QScriptContext *context, QScriptEngine *engine) +JSValue EnvironmentExtension::jsUnsetEnv(JSContext *ctx, JSValue, int argc, JSValue *argv) { - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("unsetEnv expects 1 argument")); - getProcessEnvironment(context, engine, QStringLiteral("unsetEnv"))->remove( - context->argument(0).toString()); - return engine->undefinedValue(); + try { + const auto name = getArgument<QString>(ctx, "Environment.unsetEnv", argc, argv); + getProcessEnvironment(ScriptEngine::engineForContext(ctx), QStringLiteral("unsetEnv")) + ->remove(name); + return JS_UNDEFINED; + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue EnvironmentExtension::js_currentEnv(QScriptContext *context, QScriptEngine *engine) +JSValue EnvironmentExtension::jsCurrentEnv(JSContext *ctx, JSValue, int, JSValue *) { - Q_UNUSED(context); - const QProcessEnvironment env = static_cast<ScriptEngine *>(engine)->environment(); - const QProcessEnvironment *procenv = getProcessEnvironment(context, engine, - QStringLiteral("currentEnv"), false); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const QProcessEnvironment env = engine->environment(); + const QProcessEnvironment *procenv = getProcessEnvironment(engine, QStringLiteral("currentEnv"), + false); if (!procenv) procenv = &env; - QScriptValue envObject = engine->newObject(); + JSValue envObject = engine->newObject(); const auto keys = procenv->keys(); for (const QString &key : keys) { const QString keyName = HostOsInfo::isWindowsHost() ? key.toUpper() : key; - envObject.setProperty(keyName, QScriptValue(procenv->value(key))); + setJsProperty(ctx, envObject, keyName, procenv->value(key)); } return envObject; } @@ -138,23 +136,7 @@ QScriptValue EnvironmentExtension::js_currentEnv(QScriptContext *context, QScrip } // namespace Internal } // namespace qbs -void initializeJsExtensionEnvironment(QScriptValue extensionObject) +void initializeJsExtensionEnvironment(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue environmentObj = engine->newQMetaObject(&EnvironmentExtension::staticMetaObject, - engine->newFunction(&EnvironmentExtension::js_ctor)); - environmentObj.setProperty(QStringLiteral("getEnv"), - engine->newFunction(EnvironmentExtension::js_getEnv, 1)); - environmentObj.setProperty(QStringLiteral("putEnv"), - engine->newFunction(EnvironmentExtension::js_putEnv, 2)); - environmentObj.setProperty(QStringLiteral("unsetEnv"), - engine->newFunction(EnvironmentExtension::js_unsetEnv, 1)); - environmentObj.setProperty(QStringLiteral("currentEnv"), - engine->newFunction(EnvironmentExtension::js_currentEnv, 0)); - extensionObject.setProperty(QStringLiteral("Environment"), environmentObj); + qbs::Internal::EnvironmentExtension::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::EnvironmentExtension *) - -#include "environmentextension.moc" diff --git a/src/lib/corelib/jsextensions/file.cpp b/src/lib/corelib/jsextensions/file.cpp index 7cd2aadb8..6c24635a0 100644 --- a/src/lib/corelib/jsextensions/file.cpp +++ b/src/lib/corelib/jsextensions/file.cpp @@ -37,6 +37,8 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/fileinfo.h> @@ -44,15 +46,11 @@ #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> - namespace qbs { namespace Internal { -class File : public QObject, QScriptable +class File : public JsExtension<File> { - Q_OBJECT public: enum Filter { Dirs = 0x001, @@ -82,209 +80,198 @@ public: NoFilter = -1 }; Q_DECLARE_FLAGS(Filters, Filter) - Q_ENUMS(Filter) - 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_directoryEntries(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_lastModified(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_makePath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_move(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_remove(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_canonicalFilePath(QScriptContext *context, QScriptEngine *engine); + static const char *name() { return "File"; } + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static void declareEnums(JSContext *ctx, JSValue classObj); + static JSValue jsCopy(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsExists(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsDirectoryEntries(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsLastModified(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsMakePath(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsMove(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsRemove(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsCanonicalFilePath(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); }; -QScriptValue File::js_ctor(QScriptContext *context, QScriptEngine *engine) +void File::setupStaticMethods(JSContext *ctx, JSValue classObj) { - Q_UNUSED(engine); - return context->throwError(Tr::tr("'File' cannot be instantiated.")); + setupMethod(ctx, classObj, "copy", &File::jsCopy, 2); + setupMethod(ctx, classObj, "exists", &File::jsExists, 1); + setupMethod(ctx, classObj, "directoryEntries", File::jsDirectoryEntries, 2); + setupMethod(ctx, classObj, "lastModified", File::jsLastModified, 1); + setupMethod(ctx, classObj, "makePath", &File::jsMakePath, 1); + setupMethod(ctx, classObj, "move", &File::jsMove, 3); + setupMethod(ctx, classObj, "remove", &File::jsRemove, 1); + setupMethod(ctx, classObj, "canonicalFilePath", &File::jsCanonicalFilePath, 1); } -QScriptValue File::js_copy(QScriptContext *context, QScriptEngine *engine) +void File::declareEnums(JSContext *ctx, JSValue classObj) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 2)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("copy expects 2 arguments")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ - DubiousContext(EvalContext::PropertyEvaluation), - DubiousContext(EvalContext::RuleExecution, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("File.copy()"), dubiousContexts); - - const QString sourceFile = context->argument(0).toString(); - const QString targetFile = context->argument(1).toString(); - QString errorMessage; - if (Q_UNLIKELY(!copyFileRecursion(sourceFile, targetFile, true, true, &errorMessage))) - return context->throwError(errorMessage); - return true; + DECLARE_ENUM(ctx, classObj, Dirs); + DECLARE_ENUM(ctx, classObj, Files); + DECLARE_ENUM(ctx, classObj, Drives); + DECLARE_ENUM(ctx, classObj, NoSymLinks); + DECLARE_ENUM(ctx, classObj, AllEntries); + DECLARE_ENUM(ctx, classObj, TypeMask); + DECLARE_ENUM(ctx, classObj, Readable); + DECLARE_ENUM(ctx, classObj, Writable); + DECLARE_ENUM(ctx, classObj, Executable); + DECLARE_ENUM(ctx, classObj, PermissionMask); + DECLARE_ENUM(ctx, classObj, Modified); + DECLARE_ENUM(ctx, classObj, Hidden); + DECLARE_ENUM(ctx, classObj, System); + DECLARE_ENUM(ctx, classObj, AccessMask); + DECLARE_ENUM(ctx, classObj, AllDirs); + DECLARE_ENUM(ctx, classObj, CaseSensitive); + DECLARE_ENUM(ctx, classObj, NoDot); + DECLARE_ENUM(ctx, classObj, NoDotDot); + DECLARE_ENUM(ctx, classObj, NoDotAndDotDot); + DECLARE_ENUM(ctx, classObj, NoFilter); } -QScriptValue File::js_exists(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsCopy(JSContext *ctx, JSValue, int argc, JSValue *argv) { - 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); - const auto se = static_cast<ScriptEngine *>(engine); - se->addFileExistsResult(filePath, exists); - return exists; + try { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation), + DubiousContext(EvalContext::RuleExecution, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("File.copy()"), dubiousContexts); + + const auto args = getArguments<QString, QString>(ctx, "File.copy", argc, argv); + QString errorMessage; + if (Q_UNLIKELY(!copyFileRecursion(std::get<0>(args), std::get<1>(args), true, true, + &errorMessage))) { + throw errorMessage; + } + return JS_TRUE; + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_directoryEntries(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsExists(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 2)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("directoryEntries expects 2 arguments")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ - DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("File.directoryEntries()"), dubiousContexts); - - const QString path = context->argument(0).toString(); - const auto filters = static_cast<QDir::Filters>(context->argument(1).toUInt32()); - QDir dir(path); - const QStringList entries = dir.entryList(filters, QDir::Name); - se->addDirectoryEntriesResult(path, filters, entries); - return qScriptValueFromSequence(engine, entries); + try { + const auto filePath = getArgument<QString>(ctx, "File.exists", argc, argv); + const bool exists = FileInfo::exists(filePath); + ScriptEngine::engineForContext(ctx)->addFileExistsResult(filePath, exists); + return JS_NewBool(ctx, exists); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_remove(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsDirectoryEntries(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("remove expects 1 argument")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); - se->checkContext(QStringLiteral("File.remove()"), dubiousContexts); - - QString fileName = context->argument(0).toString(); - - QString errorMessage; - if (Q_UNLIKELY(!removeFileRecursion(QFileInfo(fileName), &errorMessage))) - return context->throwError(errorMessage); - return true; + try { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("File.directoryEntries()"), dubiousContexts); + + const auto args = getArguments<QString, qint32>(ctx, "Environment.directoryEntries", + argc, argv); + const QString path = std::get<0>(args); + const QDir dir(path); + const auto filters = static_cast<QDir::Filters>(std::get<1>(args)); + const QStringList entries = dir.entryList(filters, QDir::Name); + se->addDirectoryEntriesResult(path, filters, entries); + return makeJsStringList(ctx, entries); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_lastModified(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsRemove(JSContext *ctx, JSValue, int argc, JSValue *argv) { - 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(); - const auto se = static_cast<ScriptEngine *>(engine); - se->addFileLastModifiedResult(filePath, timestamp); - return timestamp.asDouble(); + try { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{DubiousContext(EvalContext::PropertyEvaluation)}; + se->checkContext(QStringLiteral("File.remove()"), dubiousContexts); + const auto fileName = getArgument<QString>(ctx, "Environment.remove", argc, argv); + QString errorMessage; + if (Q_UNLIKELY(!removeFileRecursion(QFileInfo(fileName), &errorMessage))) + throw errorMessage; + return JS_TRUE; + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_makePath(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsLastModified(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("makePath expects 1 argument")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); - se->checkContext(QStringLiteral("File.makePath()"), dubiousContexts); - - return QDir::root().mkpath(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "Environment.lastModified", argc, argv); + const FileTime timestamp = FileInfo(filePath).lastModified(); + const auto se = ScriptEngine::engineForContext(ctx); + se->addFileLastModifiedResult(filePath, timestamp); + return JS_NewFloat64(ctx, timestamp.asDouble()); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_move(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsMakePath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 2)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("move expects 2 arguments")); - } - - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); - se->checkContext(QStringLiteral("File.move()"), dubiousContexts); - - const QString sourceFile = context->argument(0).toString(); - const QString targetFile = context->argument(1).toString(); - const bool overwrite = context->argumentCount() > 2 ? context->argument(2).toBool() : true; - - if (Q_UNLIKELY(QFileInfo(sourceFile).isDir())) - return context->throwError(QStringLiteral("Could not move '%1' to '%2': " - "Source file path is a directory.") - .arg(sourceFile, targetFile)); - - if (Q_UNLIKELY(QFileInfo(targetFile).isDir())) { - return context->throwError(QStringLiteral("Could not move '%1' to '%2': " - "Destination file path is a directory.") - .arg(sourceFile, targetFile)); - } - - QFile f(targetFile); - if (overwrite && f.exists() && !f.remove()) - return context->throwError(QStringLiteral("Could not move '%1' to '%2': %3") - .arg(sourceFile, targetFile, f.errorString())); - - if (QFile::exists(targetFile)) - return context->throwError(QStringLiteral("Could not move '%1' to '%2': " - "Destination file exists.") - .arg(sourceFile, targetFile)); + try { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{DubiousContext(EvalContext::PropertyEvaluation)}; + se->checkContext(QStringLiteral("File.makePath()"), dubiousContexts); + const auto path = getArgument<QString>(ctx, "File.makePath", argc, argv); + return JS_NewBool(ctx, QDir::root().mkpath(path)); + } catch (const QString &error) { return throwError(ctx, error); } +} - QFile f2(sourceFile); - if (Q_UNLIKELY(!f2.rename(targetFile))) - return context->throwError(QStringLiteral("Could not move '%1' to '%2': %3") - .arg(sourceFile, targetFile, f2.errorString())); - return true; +JSValue File::jsMove(JSContext *ctx, JSValue, int argc, JSValue *argv) +{ + try { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{DubiousContext(EvalContext::PropertyEvaluation)}; + se->checkContext(QStringLiteral("File.move()"), dubiousContexts); + + const auto args = getArguments<QString, QString>(ctx, "File.move", argc, argv); + const QString sourceFile = std::get<0>(args); + const QString targetFile = std::get<1>(args); + const auto overwrite = argc > 2 ? fromArg<bool>(ctx, "File.move", 3, argv[2]) : true; + + if (Q_UNLIKELY(QFileInfo(sourceFile).isDir())) { + throw QStringLiteral("Could not move '%1' to '%2': " + "Source file path is a directory.").arg(sourceFile, targetFile); + } + if (Q_UNLIKELY(QFileInfo(targetFile).isDir())) { + throw QStringLiteral("Could not move '%1' to '%2': " + "Destination file path is a directory.") + .arg(sourceFile, targetFile); + } + + QFile f(targetFile); + if (overwrite && f.exists() && !f.remove()) { + throw QStringLiteral("Could not move '%1' to '%2': %3") + .arg(sourceFile, targetFile, f.errorString()); + } + if (QFile::exists(targetFile)) { + throw QStringLiteral("Could not move '%1' to '%2': " + "Destination file exists.").arg(sourceFile, targetFile); + } + + QFile f2(sourceFile); + if (Q_UNLIKELY(!f2.rename(targetFile))) { + throw QStringLiteral("Could not move '%1' to '%2': %3") + .arg(sourceFile, targetFile, f2.errorString()); + } + return JS_TRUE; + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue File::js_canonicalFilePath(QScriptContext *context, QScriptEngine *engine) +JSValue File::jsCanonicalFilePath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("canonicalFilePath expects 1 argument")); - } - return QFileInfo(context->argument(0).toString()).canonicalFilePath(); + try { + const auto path = getArgument<QString>(ctx, "File.canonicalFilePath", argc, argv); + return makeJsString(ctx, QFileInfo(path).canonicalFilePath()); + } catch (const QString &error) { return throwError(ctx, error); } } } // namespace Internal } // namespace qbs -void initializeJsExtensionFile(QScriptValue extensionObject) +void initializeJsExtensionFile(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue fileObj = engine->newQMetaObject(&File::staticMetaObject, - engine->newFunction(&File::js_ctor)); - fileObj.setProperty(QStringLiteral("copy"), engine->newFunction(File::js_copy)); - fileObj.setProperty(QStringLiteral("exists"), engine->newFunction(File::js_exists)); - fileObj.setProperty(QStringLiteral("directoryEntries"), - engine->newFunction(File::js_directoryEntries)); - fileObj.setProperty(QStringLiteral("lastModified"), engine->newFunction(File::js_lastModified)); - fileObj.setProperty(QStringLiteral("makePath"), engine->newFunction(File::js_makePath)); - fileObj.setProperty(QStringLiteral("move"), engine->newFunction(File::js_move)); - fileObj.setProperty(QStringLiteral("remove"), engine->newFunction(File::js_remove)); - fileObj.setProperty(QStringLiteral("canonicalFilePath"), - engine->newFunction(File::js_canonicalFilePath)); - extensionObject.setProperty(QStringLiteral("File"), fileObj); + qbs::Internal::File::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::File *) - -#include "file.moc" diff --git a/src/lib/corelib/jsextensions/fileinfoextension.cpp b/src/lib/corelib/jsextensions/fileinfoextension.cpp index 0f2570ed0..cfc45ca40 100644 --- a/src/lib/corelib/jsextensions/fileinfoextension.cpp +++ b/src/lib/corelib/jsextensions/fileinfoextension.cpp @@ -37,6 +37,8 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/fileinfo.h> @@ -45,278 +47,268 @@ #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> - #include <regex> namespace qbs { namespace Internal { -class FileInfoExtension : public QObject, QScriptable +// removes duplicate separators from the path +static QString uniqueSeparators(QString path) +{ + const auto it = std::unique(path.begin(), path.end(), [](QChar c1, QChar c2) { + return c1 == c2 && c1 == QLatin1Char('/'); + }); + path.resize(int(it - path.begin())); + return path; +} + +class FileInfoExtension : public JsExtension<FileInfoExtension> { - Q_OBJECT public: - static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_path(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_fileName(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_baseName(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_suffix(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_completeSuffix(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_canonicalPath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_cleanPath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_completeBaseName(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_relativePath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_resolvePath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_isAbsolutePath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_toWindowsSeparators(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_fromWindowsSeparators(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_toNativeSeparators(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_fromNativeSeparators(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_joinPaths(QScriptContext *context, QScriptEngine *engine); + static const char *name() { return "FileInfo"; } + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static JSValue jsPath(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsFileName(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsBaseName(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsSuffix(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsCompleteSuffix(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsCanonicalPath(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsCleanPath(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsCompleteBaseName(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsRelativePath(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsResolvePath(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsIsAbsolutePath(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsToWindowsSeparators(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsFromWindowsSeparators(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsToNativeSeparators(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsFromNativeSeparators(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv); + static JSValue jsJoinPaths(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue jsPathListSeparator(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsPathSeparator(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsExecutableSuffix(JSContext *ctx, JSValueConst, int, JSValueConst *); }; -QScriptValue FileInfoExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +void FileInfoExtension::setupStaticMethods(JSContext *ctx, JSValue classObj) { - Q_UNUSED(engine); - return context->throwError(Tr::tr("'FileInfo' cannot be instantiated.")); + setupMethod(ctx, classObj, StringConstants::fileInfoPath(), &FileInfoExtension::jsPath, 1); + setupMethod(ctx, classObj, StringConstants::fileInfoFileName(), + &FileInfoExtension::jsFileName, 1); + setupMethod(ctx, classObj, StringConstants::baseNameProperty(), + &FileInfoExtension::jsBaseName, 1); + setupMethod(ctx, classObj, "suffix", &FileInfoExtension::jsSuffix, 1); + setupMethod(ctx, classObj, "completeSuffix", &FileInfoExtension::jsCompleteSuffix, 1); + setupMethod(ctx, classObj, "canonicalPath", &FileInfoExtension::jsCanonicalPath, 1); + setupMethod(ctx, classObj, "cleanPath", &FileInfoExtension::jsCleanPath, 1); + setupMethod(ctx, classObj, StringConstants::completeBaseNameProperty(), + &FileInfoExtension::jsCompleteBaseName, 1); + setupMethod(ctx, classObj, "relativePath", &FileInfoExtension::jsRelativePath, 1); + setupMethod(ctx, classObj, "resolvePath", &FileInfoExtension::jsResolvePath, 1); + setupMethod(ctx, classObj, "isAbsolutePath", &FileInfoExtension::jsIsAbsolutePath, 1); + setupMethod(ctx, classObj, "toWindowsSeparators", + &FileInfoExtension::jsToWindowsSeparators, 1); + setupMethod(ctx, classObj, "fromWindowsSeparators", + &FileInfoExtension::jsFromWindowsSeparators, 1); + setupMethod(ctx, classObj, "toNativeSeparators", + &FileInfoExtension::jsToNativeSeparators, 1); + setupMethod(ctx, classObj, "fromNativeSeparators", + &FileInfoExtension::jsFromNativeSeparators, 1); + setupMethod(ctx, classObj, "joinPaths", &FileInfoExtension::jsJoinPaths, 0); + setupMethod(ctx, classObj, "pathListSeparator", + &FileInfoExtension::jsPathListSeparator, 0); + setupMethod(ctx, classObj, "pathSeparator", &FileInfoExtension::jsPathSeparator, 0); + setupMethod(ctx, classObj, "executableSuffix", &FileInfoExtension::jsExecutableSuffix, 0); } -QScriptValue FileInfoExtension::js_path(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsPath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("path expects 1 argument")); - } - HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); - if (context->argumentCount() > 1) { - hostOs = context->argument(1).toVariant().toStringList().contains(QLatin1String("windows")) - ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; - } - return FileInfo::path(context->argument(0).toString(), hostOs); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.path", argc, argv); + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (argc > 1) { + const auto osList = fromArg<QStringList>(ctx, "FileInfo.path", 2, argv[1]); + hostOs = osList.contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return makeJsString(ctx, FileInfo::path(filePath, hostOs)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_fileName(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsFileName(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("fileName expects 1 argument")); - } - return FileInfo::fileName(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.fileName", argc, argv); + return makeJsString(ctx, FileInfo::fileName(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_baseName(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsBaseName(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("baseName expects 1 argument")); - } - return FileInfo::baseName(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.baseName", argc, argv); + return makeJsString(ctx, FileInfo::baseName(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_suffix(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsSuffix(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("suffix expects 1 argument")); - } - return FileInfo::suffix(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.suffix", argc, argv); + return makeJsString(ctx, FileInfo::suffix(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_completeSuffix(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsCompleteSuffix(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("completeSuffix expects 1 argument")); - } - return FileInfo::completeSuffix(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.completeSuffix", argc, argv); + return makeJsString(ctx, FileInfo::completeSuffix(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_canonicalPath(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsCanonicalPath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("canonicalPath expects 1 argument")); - } - return QFileInfo(context->argument(0).toString()).canonicalFilePath(); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.canonicalPath", argc, argv); + return makeJsString(ctx, QFileInfo(filePath).canonicalFilePath()); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_cleanPath(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsCleanPath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("cleanPath expects 1 argument")); - } - return QDir::cleanPath(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.cleanPath", argc, argv); + return makeJsString(ctx, QDir::cleanPath(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_completeBaseName(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsCompleteBaseName(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("completeBaseName expects 1 argument")); - } - return FileInfo::completeBaseName(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.completeBaseName", argc, argv); + return makeJsString(ctx, FileInfo::completeBaseName(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_relativePath(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsRelativePath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("relativePath expects 2 arguments")); - } - const QString baseDir = context->argument(0).toString(); - const QString filePath = context->argument(1).toString(); - if (!FileInfo::isAbsolute(baseDir)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("FileInfo.relativePath() expects an absolute path as " - "its first argument, but it is '%1'.").arg(baseDir)); - } - if (!FileInfo::isAbsolute(filePath)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("FileInfo.relativePath() expects an absolute path as " - "its second argument, but it is '%1'.").arg(filePath)); - } - return QDir(baseDir).relativeFilePath(filePath); + try { + const auto args = getArguments<QString, QString>(ctx, "File.relativePath", argc, argv); + const QString baseDir = std::get<0>(args); + const QString filePath = std::get<1>(args); + if (!FileInfo::isAbsolute(baseDir)) { + throw Tr::tr("FileInfo.relativePath() expects an absolute path as " + "its first argument, but it is '%1'.").arg(baseDir); + } + if (!FileInfo::isAbsolute(filePath)) { + throw Tr::tr("FileInfo.relativePath() expects an absolute path as " + "its second argument, but it is '%1'.").arg(filePath); + } + return makeJsString(ctx, QDir(baseDir).relativeFilePath(filePath)); + + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_resolvePath(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsResolvePath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("resolvePath expects 2 arguments")); - } - const QString base = context->argument(0).toString(); - const QString rel = context->argument(1).toString(); - return FileInfo::resolvePath(base, rel); + try { + const auto args = getArguments<QString, QString>(ctx, "File.resolvePath", argc, argv); + const QString base = std::get<0>(args); + const QString rel = std::get<1>(args); + return makeJsString(ctx, FileInfo::resolvePath(base, rel)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_isAbsolutePath(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsIsAbsolutePath(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("isAbsolutePath expects 1 argument")); - } - HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); - if (context->argumentCount() > 1) { - hostOs = context->argument(1).toVariant().toStringList().contains(QLatin1String("windows")) - ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; - } - return FileInfo::isAbsolute(context->argument(0).toString(), hostOs); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.isAbsolutePath", argc, argv); + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (argc > 1) { + const auto osList = fromArg<QStringList>(ctx, "FileInfo.isAbsolutePath", 2, argv[1]); + hostOs = osList.contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return JS_NewBool(ctx, FileInfo::isAbsolute(filePath, hostOs)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_toWindowsSeparators(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsToWindowsSeparators(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("toWindowsSeparators expects 1 argument")); - } - return context->argument(0).toString().replace(QLatin1Char('/'), QLatin1Char('\\')); + try { + auto filePath = getArgument<QString>(ctx, "FileInfo.toWindowsSeparators", argc, argv); + return makeJsString(ctx, filePath.replace(QLatin1Char('/'), QLatin1Char('\\'))); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_fromWindowsSeparators(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsFromWindowsSeparators(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("fromWindowsSeparators expects 1 argument")); - } - return context->argument(0).toString().replace(QLatin1Char('\\'), QLatin1Char('/')); + try { + auto filePath = getArgument<QString>(ctx, "FileInfo.fromWindowsSeparators", argc, argv); + return makeJsString(ctx, filePath.replace(QLatin1Char('\\'), QLatin1Char('/'))); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_toNativeSeparators(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsToNativeSeparators(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("toNativeSeparators expects 1 argument")); - } - return QDir::toNativeSeparators(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.toWindowsSeparators", argc, argv); + return makeJsString(ctx, QDir::toNativeSeparators(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_fromNativeSeparators(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsFromNativeSeparators(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("fromNativeSeparators expects 1 argument")); - } - return QDir::fromNativeSeparators(context->argument(0).toString()); + try { + const auto filePath = getArgument<QString>(ctx, "FileInfo.fromWindowsSeparators", + argc, argv); + return makeJsString(ctx, QDir::fromNativeSeparators(filePath)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue FileInfoExtension::js_joinPaths(QScriptContext *context, QScriptEngine *engine) +JSValue FileInfoExtension::jsJoinPaths(JSContext *ctx, JSValue, int argc, JSValue *argv) { - Q_UNUSED(engine); - QStringList paths; - for (int i = 0; i < context->argumentCount(); ++i) { - const QScriptValue value = context->argument(i); - if (!value.isUndefined() && !value.isNull()) { - const QString arg = value.toString(); - if (!arg.isEmpty()) - paths.push_back(arg); + try { + QStringList paths; + for (int i = 0; i < argc; ++i) { + const auto value = fromArg<QString>(ctx, "FileInfo.joinPaths", i + 1, argv[i]); + if (!value.isEmpty()) + paths.push_back(value); } - } - return engine->toScriptValue(QString::fromStdString( - std::regex_replace(paths.join(QLatin1Char('/')).toStdString(), - std::regex("/{2,}"), std::string("/")))); + return makeJsString(ctx, uniqueSeparators(paths.join(QLatin1Char('/')))); + } catch (const QString &error) { return throwError(ctx, error); } } -} // namespace Internal -} // namespace qbs +JSValue FileInfoExtension::jsPathListSeparator(JSContext *ctx, JSValue, int, JSValue *) +{ + return makeJsString(ctx, QString(HostOsInfo::pathListSeparator())); +} + +JSValue FileInfoExtension::jsPathSeparator(JSContext *ctx, JSValue, int, JSValue *) +{ + return makeJsString(ctx, QString(HostOsInfo::pathSeparator())); +} -void initializeJsExtensionFileInfo(QScriptValue extensionObject) +JSValue FileInfoExtension::jsExecutableSuffix(JSContext *ctx, JSValue, int, JSValue *) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue fileInfoObj = engine->newQMetaObject(&FileInfoExtension::staticMetaObject, - engine->newFunction(&FileInfoExtension::js_ctor)); - fileInfoObj.setProperty(StringConstants::fileInfoPath(), - engine->newFunction(FileInfoExtension::js_path)); - fileInfoObj.setProperty(StringConstants::fileInfoFileName(), - engine->newFunction(FileInfoExtension::js_fileName)); - fileInfoObj.setProperty(StringConstants::baseNameProperty(), - engine->newFunction(FileInfoExtension::js_baseName)); - fileInfoObj.setProperty(QStringLiteral("suffix"), - engine->newFunction(FileInfoExtension::js_suffix)); - fileInfoObj.setProperty(QStringLiteral("completeSuffix"), - engine->newFunction(FileInfoExtension::js_completeSuffix)); - fileInfoObj.setProperty(QStringLiteral("canonicalPath"), - engine->newFunction(FileInfoExtension::js_canonicalPath)); - fileInfoObj.setProperty(QStringLiteral("cleanPath"), - engine->newFunction(FileInfoExtension::js_cleanPath)); - fileInfoObj.setProperty(StringConstants::completeBaseNameProperty(), - engine->newFunction(FileInfoExtension::js_completeBaseName)); - fileInfoObj.setProperty(QStringLiteral("relativePath"), - engine->newFunction(FileInfoExtension::js_relativePath)); - fileInfoObj.setProperty(QStringLiteral("resolvePath"), - engine->newFunction(FileInfoExtension::js_resolvePath)); - fileInfoObj.setProperty(QStringLiteral("isAbsolutePath"), - engine->newFunction(FileInfoExtension::js_isAbsolutePath)); - fileInfoObj.setProperty(QStringLiteral("toWindowsSeparators"), - engine->newFunction(FileInfoExtension::js_toWindowsSeparators)); - fileInfoObj.setProperty(QStringLiteral("fromWindowsSeparators"), - engine->newFunction(FileInfoExtension::js_fromWindowsSeparators)); - fileInfoObj.setProperty(QStringLiteral("toNativeSeparators"), - engine->newFunction(FileInfoExtension::js_toNativeSeparators)); - fileInfoObj.setProperty(QStringLiteral("fromNativeSeparators"), - engine->newFunction(FileInfoExtension::js_fromNativeSeparators)); - fileInfoObj.setProperty(QStringLiteral("joinPaths"), - engine->newFunction(FileInfoExtension::js_joinPaths)); - extensionObject.setProperty(QStringLiteral("FileInfo"), fileInfoObj); + static QString executableSuffix = HostOsInfo::isWindowsHost() ? + QLatin1String(QBS_HOST_EXE_SUFFIX) : QString(); + return makeJsString(ctx, executableSuffix); } -Q_DECLARE_METATYPE(qbs::Internal::FileInfoExtension *) +} // namespace Internal +} // namespace qbs -#include "fileinfoextension.moc" +void initializeJsExtensionFileInfo(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) +{ + qbs::Internal::FileInfoExtension::registerClass(engine, extensionObject); +} diff --git a/src/lib/corelib/jsextensions/host.cpp b/src/lib/corelib/jsextensions/host.cpp new file mode 100644 index 000000000..24d40fe6a --- /dev/null +++ b/src/lib/corelib/jsextensions/host.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2022 Raphaël Cotty <raphael.cotty@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "jsextension.h" + +#include <language/scriptengine.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> + +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> + +namespace qbs { +namespace Internal { + +class Host : public JsExtension<Host> +{ +public: + static const char *name() { return "Host"; } + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static JSValue jsArchitecture(JSContext *ctx, JSValueConst, int, JSValueConst *) { + return makeJsString(ctx, HostOsInfo::hostOSArchitecture()); + } + static JSValue jsOs(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsPlatform(JSContext *ctx, JSValueConst, int, JSValueConst *) { + return makeJsString(ctx, HostOsInfo::hostOSIdentifier()); + } + static JSValue jsOsVersion(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsOsBuildVersion(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsOsVersionParts(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsOsVersionMajor(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsOsVersionMinor(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsOsVersionPatch(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue jsNullDevice(JSContext *ctx, JSValueConst, int, JSValueConst *); +}; + +static QStringList osList() +{ + QStringList list; + for (const auto &s : HostOsInfo::canonicalOSIdentifiers(HostOsInfo::hostOSIdentifier())) + list.push_back(s); + return list; +} + +void Host::setupStaticMethods(JSContext *ctx, JSValue classObj) +{ + setupMethod(ctx, classObj, "architecture", &Host::jsArchitecture, 0); + setupMethod(ctx, classObj, "os", &Host::jsOs, 0); + setupMethod(ctx, classObj, "platform", &Host::jsPlatform, 0); + setupMethod(ctx, classObj, "osVersion", &Host::jsOsVersion, 0); + setupMethod(ctx, classObj, "osBuildVersion", &Host::jsOsBuildVersion, 0); + setupMethod(ctx, classObj, "osVersionParts", &Host::jsOsVersionParts, 0); + setupMethod(ctx, classObj, "osVersionMajor", &Host::jsOsVersionMajor, 0); + setupMethod(ctx, classObj, "osVersionMinor", &Host::jsOsVersionMinor, 0); + setupMethod(ctx, classObj, "osVersionPatch", &Host::jsOsVersionPatch, 0); + setupMethod(ctx, classObj, "nullDevice", &Host::jsNullDevice, 0); +} + +JSValue Host::jsOs(JSContext *ctx, JSValue, int, JSValue *) +{ + static QStringList host = osList(); + return makeJsStringList(ctx, host); +} + +JSValue Host::jsOsVersion(JSContext *ctx, JSValue, int, JSValue *) +{ + static QString osVersion = HostOsInfo::hostOsVersion().toString(); + return osVersion.isNull() ? JS_UNDEFINED : makeJsString(ctx, osVersion); +} + +JSValue Host::jsOsBuildVersion(JSContext *ctx, JSValue, int, JSValue *) +{ + static QString osBuildVersion = HostOsInfo::hostOsBuildVersion(); + return osBuildVersion.isNull() ? JS_UNDEFINED : makeJsString(ctx, osBuildVersion); +} + +JSValue Host::jsOsVersionParts(JSContext *ctx, JSValue, int, JSValue *) +{ + static QStringList osVersionParts = HostOsInfo::hostOsVersion().toString().split( + QStringLiteral(".")); + return makeJsStringList(ctx, osVersionParts); +} + +JSValue Host::jsOsVersionMajor(JSContext *ctx, JSValue, int, JSValue *) +{ + static int osVersionMajor = HostOsInfo::hostOsVersion().majorVersion(); + return JS_NewInt32(ctx, osVersionMajor); +} + +JSValue Host::jsOsVersionMinor(JSContext *ctx, JSValue, int, JSValue *) +{ + static int osVersionMinor = HostOsInfo::hostOsVersion().minorVersion(); + return JS_NewInt32(ctx, osVersionMinor); +} + +JSValue Host::jsOsVersionPatch(JSContext *ctx, JSValue, int, JSValue *) +{ + static int osVersionPatch = HostOsInfo::hostOsVersion().patchLevel(); + return JS_NewInt32(ctx, osVersionPatch); +} + +JSValue Host::jsNullDevice(JSContext *ctx, JSValue, int, JSValue *) +{ + static QString nullDevice = HostOsInfo::isWindowsHost() ? QStringLiteral("NUL") : + QStringLiteral("/dev/null"); + return makeJsString(ctx, nullDevice); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionHost(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) +{ + qbs::Internal::Host::registerClass(engine, extensionObject); +} diff --git a/src/lib/corelib/jsextensions/jsextension.h b/src/lib/corelib/jsextensions/jsextension.h new file mode 100644 index 000000000..d34fb2b83 --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextension.h @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <language/scriptengine.h> +#include <logging/translator.h> + +#include <optional> +#include <tuple> +#include <utility> + +namespace qbs::Internal { + +template<typename ...T> struct PackHelper {}; +template<typename T> struct FromArgHelper; +template <typename T> struct FunctionTrait; + +#define DECLARE_ENUM(ctx, obj, enumVal) \ + JS_SetPropertyStr((ctx), (obj), #enumVal, JS_NewInt32((ctx), (enumVal))) + +template<class Derived> class JsExtension +{ +#define DEFINE_JS_FORWARDER(name, func, text) \ + static JSValue name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) \ + { \ + return jsForward(func, text, ctx, this_val, argc, argv); \ + } + +#define DEFINE_JS_FORWARDER_QUAL(klass, name, func, text) \ + static JSValue name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) \ + { \ + return klass::jsForward(func, text, ctx, this_val, argc, argv); \ + } + +public: + virtual ~JsExtension() = default; + + template<typename ...Args > static JSValue createObject(JSContext *ctx, Args... args) + { + ScopedJsValue obj(ctx, JS_NewObjectClass(ctx, classId(ctx))); + JS_SetOpaque(obj, new Derived(ctx, args...)); + Derived::setupMethods(ctx, obj); + return obj.release(); + } + + static JSValue createObjectDirect(JSContext *ctx, void *obj) + { + JSValue jsObj = JS_NewObjectClass(ctx, classId(ctx)); + attachPointerTo(jsObj, obj); + Derived::setupMethods(ctx, jsObj); + return jsObj; + } + + static Derived *fromJsObject(JSContext *ctx, JSValue obj) + { + return attachedPointer<Derived>(obj, classId(ctx)); + } + + static void registerClass(ScriptEngine *engine, JSValue extensionObject) + { + if (const JSValue cachedValue = engine->getInternalExtension(Derived::name()); + !JS_IsUndefined(cachedValue)) { + JS_SetPropertyStr(engine->context(), extensionObject, Derived::name(), cachedValue); + return; + } + engine->registerClass(Derived::name(), &Derived::ctor, &finalizer, extensionObject); + const ScopedJsValue classObj( + engine->context(), + JS_GetPropertyStr(engine->context(), extensionObject, Derived::name())); + Derived::setupStaticMethods(engine->context(), classObj); + Derived::declareEnums(engine->context(), classObj); + } + + // obj is either class (for "static" methods) or class instance + template<typename Func> static void setupMethod(JSContext *ctx, JSValue obj, const char *name, + Func func, int argc) + { + JS_DefinePropertyValueStr(ctx, obj, name, JS_NewCFunction(ctx, func, name, argc), 0); + } + + template<typename Func> static void setupMethod(JSContext *ctx, JSValue obj, + const QString &name, Func func, int argc) + { + setupMethod(ctx, obj, name.toUtf8().constData(), func, argc); + } + + template<typename T> static T fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) + { + return FromArgHelper<T>::fromArg(ctx, funcName, pos, v); + } + + template <typename Tuple, std::size_t... I> + static void assignToTuple(Tuple &tuple, std::index_sequence<I...>, JSContext *ctx, const char *funcName, JSValueConst *argv) { + Q_UNUSED(ctx) + Q_UNUSED(funcName) + Q_UNUSED(argv) + ((std::get<I>(tuple) = fromArg<std::tuple_element_t<I, Tuple>>(ctx, funcName, I + 1, argv[I])), ...); + } + + template<typename ...Args> static std::tuple<Args...> getArguments(JSContext *ctx, + const char *funcName, int argc, JSValueConst *argv) + { + constexpr int expectedArgc = sizeof ...(Args); + if (argc < expectedArgc) { + throw Tr::tr("%1 requires %d arguments.") + .arg(QLatin1String(funcName)).arg(expectedArgc); + } + std::tuple<std::remove_const_t<std::remove_reference_t<Args>>...> values; + assignToTuple(values, std::make_index_sequence<std::tuple_size<decltype(values)>::value>(), + ctx, funcName, argv); + return values; + } + template<typename ...Args> static std::tuple<Args...> getArgumentsHelper(PackHelper<Args...>, JSContext *ctx, + const char *funcName, int argc, JSValueConst *argv) + { + return getArguments<Args...>(ctx, funcName, argc, argv); + } + + template<typename T> static T getArgument(JSContext *ctx, const char *funcName, + int argc, JSValueConst *argv) + { + return std::get<0>(getArguments<T>(ctx, funcName, argc, argv)); + } + + template <typename Func, typename Tuple, std::size_t... I> + static void jsForwardHelperVoid(Derived *obj, Func func, const Tuple &tuple, std::index_sequence<I...>) { + (obj->*func)(std::get<I>(tuple)...); + } + template <typename Func, typename Ret, typename Tuple, std::size_t... I> + static Ret jsForwardHelperRet(Derived *obj, Func &func, Tuple const& tuple, std::index_sequence<I...>) { + return (obj->*func)(std::get<I>(tuple)...); + } + + static JSValue toJsValue(JSContext *ctx, const QString &s) { return makeJsString(ctx, s); } + static JSValue toJsValue(JSContext *ctx, bool v) { return JS_NewBool(ctx, v); } + static JSValue toJsValue(JSContext *ctx, qint32 v) { return JS_NewInt32(ctx, v); } + static JSValue toJsValue(JSContext *ctx, qint64 v) { return JS_NewInt64(ctx, v); } + static JSValue toJsValue(JSContext *ctx, const QVariant &v) { return makeJsVariant(ctx, v); } + static JSValue toJsValue(JSContext *ctx, const QByteArray &data) + { + const JSValue array = JS_NewArray(ctx); + for (int i = 0; i < data.size(); ++i) { + JS_SetPropertyUint32(ctx, array, i, + JS_NewUint32(ctx, static_cast<unsigned char>(data.at(i)))); + } + return array; + } + static JSValue toJsValue(JSContext *ctx, const QVariantMap &m) + { + return makeJsVariantMap(ctx, m); + } + template<typename T> static JSValue toJsValue(JSContext *ctx, const std::optional<T> &v) + { + if (!v) + return JS_UNDEFINED; + return makeJsString(ctx, *v); + } + + template<typename Func> + static JSValue jsForward(Func func, const char *name, JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) + { + try { + using Ret = typename FunctionTrait<Func>::Ret; + using Args = typename FunctionTrait<Func>::Args; + const auto args = getArgumentsHelper(Args(), ctx, name, argc, argv); + const auto obj = fromJsObject(ctx, this_val); + if constexpr (std::is_same_v<Ret, void>) { + jsForwardHelperVoid(obj, func, args, std::make_index_sequence<std::tuple_size<decltype(args)>::value>()); + return JS_UNDEFINED; + } else { + return toJsValue(ctx, jsForwardHelperRet<Func, Ret>(obj, func, args, std::make_index_sequence<std::tuple_size_v<decltype(args)>>())); + } + } catch (const QString &error) { + return throwError(ctx, error); + } + } + + static void setupMethods(JSContext *, JSValue) {} + static void setupStaticMethods(JSContext *, JSValue) {} + static void declareEnums(JSContext *, JSValue) {} + +private: + static void finalizer(JSRuntime *rt, JSValue val) + { + delete attachedPointer<Derived>(val, classId(rt)); + } + + static JSClassID classId(JSContext *ctx) + { + return ScriptEngine::engineForContext(ctx)->getClassId(Derived::name()); + } + + static JSClassID classId(JSRuntime *rt) + { + return ScriptEngine::engineForRuntime(rt)->getClassId(Derived::name()); + } + + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int) + { + return throwError(ctx, Tr::tr("'%1' cannot be instantiated.") + .arg(QLatin1String(Derived::name()))); + } +}; + +template<> struct FromArgHelper<bool> { + static bool fromArg(JSContext *ctx, const char *, int, JSValue v) { + return JS_ToBool(ctx, v); + } +}; +template<> struct FromArgHelper<qint32> { + static qint32 fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + int32_t val; + if (JS_ToInt32(ctx, &val, v) < 0) { + throw Tr::tr("%1 requires an integer as argument %2") + .arg(QLatin1String(funcName)).arg(pos); + } + return val; + } +}; +template<> struct FromArgHelper<qint64> { + static qint64 fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + int64_t val; + if (JS_ToInt64(ctx, &val, v) < 0) { + throw Tr::tr("%1 requires an integer as argument %2") + .arg(QLatin1String(funcName)).arg(pos); + } + return val; + } +}; +template<> struct FromArgHelper<QByteArray> { + static QByteArray fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + const auto throwError = [&] { + throw Tr::tr("%1 requires an array of bytes as argument %2") + .arg(QLatin1String(funcName)).arg(pos); + }; + if (!JS_IsArray(ctx, v)) + throwError(); + QByteArray data; + data.resize(getJsIntProperty(ctx, v, QLatin1String("length"))); + for (int i = 0; i < data.size(); ++i) { + const JSValue jsNumber = JS_GetPropertyUint32(ctx, v, i); + if (JS_VALUE_GET_TAG(jsNumber) != JS_TAG_INT) + throwError(); + uint32_t n; + JS_ToUint32(ctx, &n, jsNumber); + if (n > 0xff) + throwError(); + data[i] = n; + } + return data; + } +}; +template<> struct FromArgHelper<QString> { + static QString fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + if (JS_IsUndefined(v)) + return {}; + const ScopedJsValue stringified(ctx, JS_ToString(ctx, v)); + if (!JS_IsString(stringified)) { + throw Tr::tr("%1 requires a string as argument %2") + .arg(QLatin1String(funcName)).arg(pos); + } + return getJsString(ctx, stringified); + } +}; +template<> struct FromArgHelper<QStringList> { + static QStringList fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + if (!JS_IsArray(ctx, v)) { + throw Tr::tr("%1 requires an array of strings as argument %2") + .arg(QLatin1String(funcName)).arg(pos); + } + return getJsStringList(ctx, v); + } +}; +template<> struct FromArgHelper<QVariant> { + static QVariant fromArg(JSContext *ctx, const char *, int, JSValue v) { + return getJsVariant(ctx, v); + } +}; +template<class C> struct FromArgHelper<C *> { + static C *fromArg(JSContext *ctx, const char *funcName, int pos, JSValue v) { + C * const obj = JsExtension<C>::fromJsObject(ctx, v); + if (!obj) { + throw Tr::tr("Argument %2 has wrong type in call to %1") + .arg(QLatin1String(funcName)).arg(pos); + } + return obj; + } +}; + +template <typename R, typename C, typename... A> +struct FunctionTrait<R(C::*)(A... )> { + using Ret = R; + using Args = PackHelper<std::remove_reference_t<A>...>; +}; +template <typename R, typename C, typename... A> +struct FunctionTrait<R(C::*)(A... ) const> { + using Ret = R; + using Args = PackHelper<std::remove_reference_t<A>...>; +}; + +} // namespace qbs::Internal diff --git a/src/lib/corelib/jsextensions/jsextensions.cpp b/src/lib/corelib/jsextensions/jsextensions.cpp index e5fbd3de8..66d7ebbd6 100644 --- a/src/lib/corelib/jsextensions/jsextensions.cpp +++ b/src/lib/corelib/jsextensions/jsextensions.cpp @@ -39,17 +39,19 @@ #include "jsextensions.h" +#include <language/scriptengine.h> +#include <tools/scripttools.h> + #include <QtCore/qmap.h> -#include <QtScript/qscriptengine.h> #include <utility> -using InitializerMap = QMap<QString, void (*)(QScriptValue)>; +using InitializerMap = QMap<QString, void (*)(qbs::Internal::ScriptEngine *, JSValue)>; static InitializerMap setupMap() { #define INITIALIZER_NAME(name) initializeJsExtension##name #define ADD_JS_EXTENSION(name) \ - void INITIALIZER_NAME(name)(QScriptValue); \ + void INITIALIZER_NAME(name)(qbs::Internal::ScriptEngine *, JSValue); \ map.insert(QStringLiteral(#name), &INITIALIZER_NAME(name)) InitializerMap map; @@ -57,6 +59,8 @@ static InitializerMap setupMap() ADD_JS_EXTENSION(Environment); ADD_JS_EXTENSION(File); ADD_JS_EXTENSION(FileInfo); + ADD_JS_EXTENSION(Host); + ADD_JS_EXTENSION(PkgConfig); ADD_JS_EXTENSION(Process); ADD_JS_EXTENSION(PropertyList); ADD_JS_EXTENSION(TemporaryDir); @@ -75,20 +79,21 @@ static InitializerMap &initializers() return theMap; } -void JsExtensions::setupExtensions(const QStringList &names, QScriptValue scope) +void JsExtensions::setupExtensions(ScriptEngine *engine, const QStringList &names, + const JSValue &scope) { for (const QString &name : names) - initializers().value(name)(scope); + initializers().value(name)(engine, scope); } -QScriptValue JsExtensions::loadExtension(QScriptEngine *engine, const QString &name) +JSValue JsExtensions::loadExtension(ScriptEngine *engine, const QString &name) { if (!hasExtension(name)) return {}; - QScriptValue extensionObj = engine->newObject(); - initializers().value(name)(extensionObj); - return extensionObj.property(name); + ScopedJsValue extensionObj(engine->context(), engine->newObject()); + initializers().value(name)(engine, extensionObj); + return getJsProperty(engine->context(), extensionObj, name); } bool JsExtensions::hasExtension(const QString &name) diff --git a/src/lib/corelib/jsextensions/jsextensions.h b/src/lib/corelib/jsextensions/jsextensions.h index 8977dbe8a..2602679b5 100644 --- a/src/lib/corelib/jsextensions/jsextensions.h +++ b/src/lib/corelib/jsextensions/jsextensions.h @@ -40,22 +40,20 @@ #ifndef QBS_JSEXTENSIONS_H #define QBS_JSEXTENSIONS_H -#include <QtCore/qhash.h> -#include <QtCore/qstringlist.h> +#include <quickjs.h> -QT_BEGIN_NAMESPACE -class QScriptEngine; -class QScriptValue; -QT_END_NAMESPACE +#include <QtCore/qstringlist.h> namespace qbs { namespace Internal { +class ScriptEngine; class JsExtensions { public: - static void setupExtensions(const QStringList &names, QScriptValue scope); - static QScriptValue loadExtension(QScriptEngine *engine, const QString &name); + static void setupExtensions(ScriptEngine *engine, const QStringList &names, + const JSValue &scope); + static JSValue loadExtension(ScriptEngine *engine, const QString &name); static bool hasExtension(const QString &name); static QStringList extensionNames(); }; diff --git a/src/lib/corelib/jsextensions/jsextensions.pri b/src/lib/corelib/jsextensions/jsextensions.pri deleted file mode 100644 index 2bffc9914..000000000 --- a/src/lib/corelib/jsextensions/jsextensions.pri +++ /dev/null @@ -1,26 +0,0 @@ -QT += xml - -HEADERS += \ - $$PWD/moduleproperties.h \ - $$PWD/jsextensions.h - -SOURCES += \ - $$PWD/environmentextension.cpp \ - $$PWD/file.cpp \ - $$PWD/fileinfoextension.cpp \ - $$PWD/temporarydir.cpp \ - $$PWD/textfile.cpp \ - $$PWD/binaryfile.cpp \ - $$PWD/process.cpp \ - $$PWD/moduleproperties.cpp \ - $$PWD/domxml.cpp \ - $$PWD/jsextensions.cpp \ - $$PWD/utilitiesextension.cpp - -darwin { - HEADERS += $$PWD/propertylistutils.h - SOURCES += $$PWD/propertylist.mm $$PWD/propertylistutils.mm - LIBS += -framework Foundation -} else { - SOURCES += $$PWD/propertylist.cpp -} diff --git a/src/lib/corelib/jsextensions/moduleproperties.cpp b/src/lib/corelib/jsextensions/moduleproperties.cpp index c0fd8ca84..2c73f2c53 100644 --- a/src/lib/corelib/jsextensions/moduleproperties.cpp +++ b/src/lib/corelib/jsextensions/moduleproperties.cpp @@ -41,8 +41,8 @@ #include <buildgraph/artifact.h> #include <buildgraph/artifactsscriptvalue.h> +#include <buildgraph/buildgraph.h> #include <buildgraph/dependencyparametersscriptvalue.h> -#include <buildgraph/scriptclasspropertyiterator.h> #include <language/language.h> #include <language/propertymapinternal.h> #include <language/qualifiedid.h> @@ -53,28 +53,21 @@ #include <tools/qttools.h> #include <tools/stlutils.h> #include <tools/stringconstants.h> - -#include <QtScript/qscriptclass.h> +#include <utility> namespace qbs { namespace Internal { -QScriptValue getDataForModuleScriptValue(QScriptEngine *engine, const ResolvedProduct *product, - const Artifact *artifact, const ResolvedModule *module) +JSValue createDataForModuleScriptValue(ScriptEngine *engine, const Artifact *artifact) { - QScriptValue data = engine->newObject(); - data.setProperty(ModuleNameKey, module->name); - QVariant v; - v.setValue<quintptr>(reinterpret_cast<quintptr>(product)); - data.setProperty(ProductPtrKey, engine->newVariant(v)); - v.setValue<quintptr>(reinterpret_cast<quintptr>(artifact)); - data.setProperty(ArtifactPtrKey, engine->newVariant(v)); + JSValue data = JS_NewObjectClass(engine->context(), engine->dataWithPtrClass()); + attachPointerTo(data, artifact); return data; } -static QScriptValue getModuleProperty(const ResolvedProduct *product, const Artifact *artifact, - ScriptEngine *engine, const QString &moduleName, - const QString &propertyName, bool *isPresent = nullptr) +static JSValue getModuleProperty(const ResolvedProduct *product, const Artifact *artifact, + ScriptEngine *engine, const QString &moduleName, + const QString &propertyName, bool *isPresent = nullptr) { const PropertyMapConstPtr &properties = artifact ? artifact->properties : product->moduleProperties; @@ -84,7 +77,7 @@ static QScriptValue getModuleProperty(const ResolvedProduct *product, const Arti if (!value.isValid()) { value = properties->moduleProperty(moduleName, propertyName, isPresent); - // Cache the variant value. We must not cache the QScriptValue here, because it's a + // Cache the variant value. We must not cache the script value here, because it's a // reference and the user might change the actual object. if (engine->isPropertyCacheEnabled()) engine->addToPropertyCache(moduleName, propertyName, properties, value); @@ -102,111 +95,101 @@ static QScriptValue getModuleProperty(const ResolvedProduct *product, const Arti return engine->toScriptValue(value); } -class ModulePropertyScriptClass : public QScriptClass -{ -public: - ModulePropertyScriptClass(QScriptEngine *engine) - : QScriptClass(engine) - { - } - -private: - QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, - QueryFlags flags, uint *id) override - { - Q_UNUSED(flags); - Q_UNUSED(id); - - if (name == StringConstants::dependenciesProperty() - || name == StringConstants::artifactsProperty()) { - // The prototype is not backed by a QScriptClass. - m_result = object.prototype().property(name); - return HandlesReadAccess; - } - - if (name == StringConstants::parametersProperty()) { - m_result = object.data().property(DependencyParametersKey); - return HandlesReadAccess; - } +struct ModuleData { + JSValue dependencyParameters = JS_UNDEFINED; + const Artifact *artifact = nullptr; +}; - setup(object); - QBS_ASSERT(m_product, return {}); - bool isPresent; - m_result = getModuleProperty(m_product, m_artifact, static_cast<ScriptEngine *>(engine()), - m_moduleName, name, &isPresent); +ModuleData getModuleData(JSContext *ctx, JSValue obj) +{ + const ScopedJsValue jsData(ctx, getJsProperty(ctx, obj, + StringConstants::dataPropertyInternal())); + ModuleData data; + data.dependencyParameters = JS_GetPropertyUint32(ctx, jsData, DependencyParametersKey); + data.artifact = attachedPointer<Artifact>( + jsData, ScriptEngine::engineForContext(ctx)->dataWithPtrClass()); + return data; +} - // It is important that we reject unknown property names. Otherwise QtScript will forward - // *everything* to us, including built-in stuff like the hasOwnProperty function. - return isPresent ? HandlesReadAccess : QueryFlags(); +static int getModulePropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj) +{ + const auto engine = ScriptEngine::engineForContext(ctx); + const ModuleData data = getModuleData(ctx, obj); + const auto module = attachedPointer<ResolvedModule>(obj, engine->modulePropertyScriptClass()); + QBS_ASSERT(module, return -1); + + const PropertyMapInternal *propertyMap; + QStringList additionalProperties; + if (data.artifact) { + propertyMap = data.artifact->properties.get(); + } else { + propertyMap = module->product->moduleProperties.get(); + if (JS_IsObject(data.dependencyParameters)) + additionalProperties.push_back(StringConstants::parametersProperty()); } + JS_FreeValue(ctx, data.dependencyParameters); + getPropertyNames(ctx, ptab, plen, propertyMap->value().value(module->name).toMap(), + additionalProperties, engine->baseModuleScriptValue(module)); + return 0; +} - QScriptValue property(const QScriptValue &, const QScriptString &, uint) override - { - return m_result; +static int getModuleProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, + JSAtom prop) +{ + if (desc) { + desc->getter = desc->setter = desc->value = JS_UNDEFINED; + desc->flags = JS_PROP_ENUMERABLE; } - - QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override - { - setup(object); - QBS_ASSERT(m_artifact || m_product, return nullptr); - const PropertyMapInternal *propertyMap; - std::vector<QString> additionalProperties({StringConstants::artifactsProperty(), - StringConstants::dependenciesProperty()}); - if (m_artifact) { - propertyMap = m_artifact->properties.get(); - } else { - propertyMap = m_product->moduleProperties.get(); - if (object.data().property(DependencyParametersKey).isValid()) - additionalProperties.push_back(StringConstants::parametersProperty()); - } - return new ScriptClassPropertyIterator(object, - propertyMap->value().value(m_moduleName).toMap(), - additionalProperties); + const auto engine = ScriptEngine::engineForContext(ctx); + const QString name = getJsString(ctx, prop); + const ModuleData data = getModuleData(ctx, obj); + const auto module = attachedPointer<ResolvedModule>(obj, engine->modulePropertyScriptClass()); + QBS_ASSERT(module, return -1); + + ScopedJsValue parametersMgr(ctx, data.dependencyParameters); + if (name == StringConstants::parametersProperty()) { + if (desc) + desc->value = parametersMgr.release(); + return 1; } - void setup(const QScriptValue &object) - { - if (m_lastObjectId != object.objectId()) { - m_lastObjectId = object.objectId(); - const QScriptValue data = object.data(); - QBS_ASSERT(data.isValid(), return); - m_moduleName = data.property(ModuleNameKey).toString(); - m_product = reinterpret_cast<const ResolvedProduct *>( - data.property(ProductPtrKey).toVariant().value<quintptr>()); - m_artifact = reinterpret_cast<const Artifact *>( - data.property(ArtifactPtrKey).toVariant().value<quintptr>()); - } + bool isPresent; + JSValue value = getModuleProperty( + module->product, data.artifact, ScriptEngine::engineForContext(ctx), + module->name, name, &isPresent); + if (isPresent) { + if (desc) + desc->value = value; + return 1; } - qint64 m_lastObjectId = 0; - QString m_moduleName; - const ResolvedProduct *m_product = nullptr; - const Artifact *m_artifact = nullptr; - QScriptValue m_result; -}; - -static QString ptrKey() { return QStringLiteral("__internalPtr"); } -static QString typeKey() { return QStringLiteral("__type"); } -static QString artifactType() { return QStringLiteral("artifact"); } + ScopedJsValue v(ctx, JS_GetProperty(ctx, engine->baseModuleScriptValue(module), prop)); + const int ret = JS_IsUndefined(v) ? 0 : 1; + if (desc) + desc->value = v.release(); + return ret; +} -static QScriptValue js_moduleDependencies(QScriptContext *, ScriptEngine *engine, - const ResolvedModule *module) +static JSValue js_moduleDependencies(JSContext *ctx, JSValueConst this_val, int , JSValueConst *) { - QScriptValue result = engine->newArray(); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const auto module = attachedPointer<ResolvedModule>(this_val, engine->dataWithPtrClass()); + JSValue result = JS_NewArray(engine->context()); quint32 idx = 0; - for (const QString &depName : qAsConst(module->moduleDependencies)) { - for (const ResolvedModuleConstPtr &dep : module->product->modules) { + for (const QString &depName : std::as_const(module->moduleDependencies)) { + for (const auto &dep : module->product->modules) { if (dep->name != depName) continue; - QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass()); - obj.setPrototype(engine->moduleScriptValuePrototype(dep.get())); - QScriptValue data = getDataForModuleScriptValue(engine, module->product, nullptr, - dep.get()); + JSValue obj = JS_NewObjectClass(ctx, engine->modulePropertyScriptClass()); + attachPointerTo(obj, dep.get()); + JSValue data = createDataForModuleScriptValue(engine, nullptr); const QVariantMap ¶ms = module->product->moduleParameters.value(dep); - data.setProperty(DependencyParametersKey, dependencyParametersValue( - module->product->uniqueName(), dep->name, params, engine)); - obj.setData(data); - result.setProperty(idx++, obj); + JS_SetPropertyUint32(ctx, data, DependencyParametersKey, + dependencyParametersValue(module->product->uniqueName(), + dep->name, params, engine)); + defineJsProperty(ctx, obj, StringConstants::dataPropertyInternal(), data); + JS_SetPropertyUint32(ctx, result, idx++, obj); break; } } @@ -214,35 +197,84 @@ static QScriptValue js_moduleDependencies(QScriptContext *, ScriptEngine *engine return result; } -static QScriptValue setupModuleScriptValue(ScriptEngine *engine, - const ResolvedModule *module) +template<class ProductOrArtifact> +static JSValue moduleProperty(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + if (Q_UNLIKELY(argc < 2)) + return throwError(ctx, Tr::tr("Function moduleProperty() expects 2 arguments")); + + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const ResolvedProduct *product = nullptr; + const Artifact *artifact = nullptr; + if constexpr (std::is_same_v<ProductOrArtifact, ResolvedProduct>) { + product = attachedPointer<ResolvedProduct>(this_val, engine->productPropertyScriptClass()); + } else { + artifact = attachedPointer<Artifact>(this_val, engine->dataWithPtrClass()); + product = artifact->product; + } + + const QString moduleName = getJsString(ctx, argv[0]); + const QString propertyName = getJsString(ctx, argv[1]); + return getModuleProperty(product, artifact, engine, moduleName, propertyName); +} + + +template<class ProductOrArtifact> +static JSValue js_moduleProperty(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + try { + return moduleProperty<ProductOrArtifact>(ctx, this_val, argc, argv); + } catch (const ErrorInfo &e) { + return throwError(ctx, e.toString()); + } +} + +template<class ProductOrArtifact> +static void initModuleProperties(ScriptEngine *engine, JSValue objectWithProperties) +{ + JSContext * const ctx = engine->context(); + JSValue func = JS_NewCFunction(ctx, js_moduleProperty<ProductOrArtifact>, "moduleProperty", 2); + setJsProperty(ctx, objectWithProperties, QStringLiteral("moduleProperty"), func); +} + +static JSValue setupBaseModuleScriptValue(ScriptEngine *engine, const ResolvedModule *module) { - QScriptValue &moduleScriptValue = engine->moduleScriptValuePrototype(module); - if (moduleScriptValue.isValid()) + JSValue &moduleScriptValue = engine->baseModuleScriptValue(module); + if (JS_IsObject(moduleScriptValue)) return moduleScriptValue; - moduleScriptValue = engine->newObject(); - QScriptValue depfunc = engine->newFunction<const ResolvedModule *>(&js_moduleDependencies, - module); - moduleScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); - QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForModule, module); - moduleScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc, - QScriptValue::ReadOnly | QScriptValue::Undeletable - | QScriptValue::PropertyGetter); + const ScopedJsValue proto(engine->context(), JS_NewObject(engine->context())); + moduleScriptValue = JS_NewObjectProtoClass(engine->context(), proto, + engine->dataWithPtrClass()); + attachPointerTo(moduleScriptValue, module); + QByteArray name = StringConstants::dependenciesProperty().toUtf8(); + const ScopedJsValue depfunc( + engine->context(), + JS_NewCFunction(engine->context(), js_moduleDependencies, name.constData(), 0)); + const ScopedJsAtom depAtom(engine->context(), name); + JS_DefineProperty(engine->context(), moduleScriptValue, depAtom, + JS_UNDEFINED, depfunc, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + name = StringConstants::artifactsProperty().toUtf8(); + const ScopedJsValue artifactsFunc( + engine->context(), + JS_NewCFunction(engine->context(), &artifactsScriptValueForModule, + name.constData(), 0)); + const ScopedJsAtom artifactsAtom(engine->context(), name); + JS_DefineProperty(engine->context(), moduleScriptValue, artifactsAtom, + JS_UNDEFINED, artifactsFunc, JS_UNDEFINED, + JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); return moduleScriptValue; } -void ModuleProperties::init(QScriptValue productObject, +void ModuleProperties::init(ScriptEngine *engine, JSValue productObject, const ResolvedProduct *product) { - init(productObject, product, StringConstants::productValue()); - setupModules(productObject, product, nullptr); + initModuleProperties<ResolvedProduct>(engine, productObject); + setupModules(engine, productObject, product, nullptr); } -void ModuleProperties::init(QScriptValue artifactObject, const Artifact *artifact) +void ModuleProperties::init(ScriptEngine *engine, JSValue artifactObject, const Artifact *artifact) { - init(artifactObject, artifact, artifactType()); + initModuleProperties<Artifact>(engine, artifactObject); const auto product = artifact->product; const QVariantMap productProperties { {StringConstants::buildDirectoryProperty(), product->buildDirectory()}, @@ -252,106 +284,51 @@ void ModuleProperties::init(QScriptValue artifactObject, const Artifact *artifac {StringConstants::targetNameProperty(), product->targetName}, {StringConstants::typeProperty(), sorted(product->fileTags.toStringList())} }; - QScriptEngine * const engine = artifactObject.engine(); - artifactObject.setProperty(StringConstants::productVar(), - engine->toScriptValue(productProperties)); - setupModules(artifactObject, artifact->product.get(), artifact); + setJsProperty(engine->context(), artifactObject, StringConstants::productVar(), + engine->toScriptValue(productProperties)); + setupModules(engine, artifactObject, artifact->product.get(), artifact); } -void ModuleProperties::setModuleScriptValue(QScriptValue targetObject, - const QScriptValue &moduleObject, const QString &moduleName) +void ModuleProperties::setModuleScriptValue(ScriptEngine *engine, JSValue targetObject, + const JSValue &moduleObject, const QString &moduleName) { - auto const e = static_cast<ScriptEngine *>(targetObject.engine()); const QualifiedId name = QualifiedId::fromString(moduleName); - QScriptValue obj = targetObject; + JSValue obj = targetObject; for (int i = 0; i < name.size() - 1; ++i) { - QScriptValue tmp = obj.property(name.at(i)); - if (!tmp.isObject()) - tmp = e->newObject(); - obj.setProperty(name.at(i), tmp); + JSValue tmp = getJsProperty(engine->context(), obj, name.at(i)); + if (!JS_IsObject(tmp)) { + tmp = engine->newObject(); + setJsProperty(engine->context(), obj, name.at(i), tmp); + } else { + JS_FreeValue(engine->context(), tmp); + } obj = tmp; } - obj.setProperty(name.last(), moduleObject); - if (moduleName.size() > 1) - targetObject.setProperty(moduleName, moduleObject); -} - -void ModuleProperties::init(QScriptValue objectWithProperties, const void *ptr, - const QString &type) -{ - QScriptEngine * const engine = objectWithProperties.engine(); - objectWithProperties.setProperty(QStringLiteral("moduleProperty"), - engine->newFunction(ModuleProperties::js_moduleProperty, 2)); - objectWithProperties.setProperty(ptrKey(), engine->toScriptValue(quintptr(ptr))); - objectWithProperties.setProperty(typeKey(), type); + setJsProperty(engine->context(), obj, name.last(), moduleObject); + if (name.size() > 1) { + setJsProperty(engine->context(), targetObject, moduleName, + JS_DupValue(engine->context(), moduleObject)); + } } -void ModuleProperties::setupModules(QScriptValue &object, const ResolvedProduct *product, - const Artifact *artifact) +void ModuleProperties::setupModules(ScriptEngine *engine, JSValue &object, + const ResolvedProduct *product, const Artifact *artifact) { - const auto engine = static_cast<ScriptEngine *>(object.engine()); - QScriptClass *modulePropertyScriptClass = engine->modulePropertyScriptClass(); - if (!modulePropertyScriptClass) { - modulePropertyScriptClass = new ModulePropertyScriptClass(engine); + JSClassID modulePropertyScriptClass = engine->modulePropertyScriptClass(); + if (modulePropertyScriptClass == 0) { + modulePropertyScriptClass = engine->registerClass("ModulePropertyScriptClass", nullptr, + nullptr, JS_UNDEFINED, &getModulePropertyNames, &getModuleProperty); engine->setModulePropertyScriptClass(modulePropertyScriptClass); } for (const auto &module : product->modules) { - QScriptValue moduleObjectPrototype = setupModuleScriptValue(engine, module.get()); - QScriptValue moduleObject = engine->newObject(modulePropertyScriptClass); - moduleObject.setPrototype(moduleObjectPrototype); - moduleObject.setData(getDataForModuleScriptValue(engine, product, artifact, module.get())); - setModuleScriptValue(object, moduleObject, module->name); - } -} - -QScriptValue ModuleProperties::js_moduleProperty(QScriptContext *context, QScriptEngine *engine) -{ - try { - return moduleProperty(context, engine); - } catch (const ErrorInfo &e) { - return context->throwError(e.toString()); + setupBaseModuleScriptValue(engine, module.get()); + JSValue moduleObject = JS_NewObjectClass(engine->context(), modulePropertyScriptClass); + attachPointerTo(moduleObject, module.get()); + defineJsProperty(engine->context(), moduleObject, StringConstants::dataPropertyInternal(), + createDataForModuleScriptValue(engine, artifact)); + setModuleScriptValue(engine, object, moduleObject, module->name); } } -QScriptValue ModuleProperties::moduleProperty(QScriptContext *context, QScriptEngine *engine) -{ - if (Q_UNLIKELY(context->argumentCount() < 2)) { - return context->throwError(QScriptContext::SyntaxError, - Tr::tr("Function moduleProperty() expects 2 arguments")); - } - - const QScriptValue objectWithProperties = context->thisObject(); - const QScriptValue typeScriptValue = objectWithProperties.property(typeKey()); - if (Q_UNLIKELY(!typeScriptValue.isString())) { - return context->throwError(QScriptContext::TypeError, - QStringLiteral("Internal error: __type not set up")); - } - const QScriptValue ptrScriptValue = objectWithProperties.property(ptrKey()); - if (Q_UNLIKELY(!ptrScriptValue.isNumber())) { - return context->throwError(QScriptContext::TypeError, - QStringLiteral("Internal error: __internalPtr not set up")); - } - - const void *ptr = reinterpret_cast<const void *>(qscriptvalue_cast<quintptr>(ptrScriptValue)); - const ResolvedProduct *product = nullptr; - const Artifact *artifact = nullptr; - if (typeScriptValue.toString() == StringConstants::productValue()) { - QBS_ASSERT(ptr, return {}); - product = static_cast<const ResolvedProduct *>(ptr); - } else if (typeScriptValue.toString() == artifactType()) { - QBS_ASSERT(ptr, return {}); - artifact = static_cast<const Artifact *>(ptr); - product = artifact->product.get(); - } else { - return context->throwError(QScriptContext::TypeError, - QStringLiteral("Internal error: invalid type")); - } - - const auto qbsEngine = static_cast<ScriptEngine *>(engine); - const QString moduleName = context->argument(0).toString(); - const QString propertyName = context->argument(1).toString(); - return getModuleProperty(product, artifact, qbsEngine, moduleName, propertyName); -} - } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/jsextensions/moduleproperties.h b/src/lib/corelib/jsextensions/moduleproperties.h index 3fe4fbfd0..6272ee8a2 100644 --- a/src/lib/corelib/jsextensions/moduleproperties.h +++ b/src/lib/corelib/jsextensions/moduleproperties.h @@ -43,41 +43,32 @@ #include <buildgraph/forward_decls.h> #include <language/forward_decls.h> -#include <QtScript/qscriptcontext.h> -#include <QtScript/qscriptvalue.h> +#include <quickjs.h> namespace qbs { namespace Internal { - class ScriptEngine; enum ModulePropertiesScriptValueCommonPropertyKeys : quint32 { ModuleNameKey, - ProductPtrKey, - ArtifactPtrKey, DependencyParametersKey, }; -QScriptValue getDataForModuleScriptValue(QScriptEngine *engine, const ResolvedProduct *product, - const Artifact *artifact, const ResolvedModule *module); +JSValue createDataForModuleScriptValue(ScriptEngine *engine, + const Artifact *artifact); class ModuleProperties { public: - static void init(QScriptValue productObject, const ResolvedProduct *product); - static void init(QScriptValue artifactObject, const Artifact *artifact); - static void setModuleScriptValue(QScriptValue targetObject, const QScriptValue &moduleObject, - const QString &moduleName); + static void init(ScriptEngine *engine, JSValue productObject, const ResolvedProduct *product); + static void init(ScriptEngine *engine, JSValue artifactObject, const Artifact *artifact); + static void setModuleScriptValue(ScriptEngine *engine, JSValue targetObject, + const JSValue &moduleObject, const QString &moduleName); private: - static void init(QScriptValue objectWithProperties, const void *ptr, const QString &type); - static void setupModules(QScriptValue &object, const ResolvedProduct *product, + static void setupModules(ScriptEngine *engine, JSValue &object, const ResolvedProduct *product, const Artifact *artifact); - - static QScriptValue js_moduleProperty(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue moduleProperty(QScriptContext *context, QScriptEngine *engine); }; } // namespace Internal diff --git a/src/lib/corelib/jsextensions/pkgconfigjs.cpp b/src/lib/corelib/jsextensions/pkgconfigjs.cpp new file mode 100644 index 000000000..817a3dfb9 --- /dev/null +++ b/src/lib/corelib/jsextensions/pkgconfigjs.cpp @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "pkgconfigjs.h" + +#include <language/scriptengine.h> +#include <tools/version.h> + +#include <QtCore/QProcessEnvironment> + +#include <stdexcept> + +namespace qbs { +namespace Internal { + +namespace { + +template<typename C, typename F> QVariantList convert(const C &c, F &&f) +{ + QVariantList result; + result.reserve(int(c.size())); + std::transform(c.begin(), c.end(), std::back_inserter(result), f); + return result; +} + +QVariantMap variablesMapToMap(const PcPackage::VariablesMap &variables) +{ + QVariantMap result; + for (const auto &item: variables) + result.insert(QString::fromStdString(item.first), QString::fromStdString(item.second)); + return result; +} + +QVariantMap packageToMap(const PcPackage &package) +{ + QVariantMap result; + result[QStringLiteral("isBroken")] = false; + result[QStringLiteral("filePath")] = QString::fromStdString(package.filePath); + result[QStringLiteral("baseFileName")] = QString::fromStdString(package.baseFileName); + result[QStringLiteral("name")] = QString::fromStdString(package.name); + result[QStringLiteral("version")] = QString::fromStdString(package.version); + result[QStringLiteral("description")] = QString::fromStdString(package.description); + result[QStringLiteral("url")] = QString::fromStdString(package.url); + + const auto flagToMap = [](const PcPackage::Flag &flag) + { + QVariantMap result; + const auto value = QString::fromStdString(flag.value); + result[QStringLiteral("type")] = QVariant::fromValue(qint32(flag.type)); + result[QStringLiteral("value")] = value; + return result; + }; + + const auto requiredVersionToMap = [](const PcPackage::RequiredVersion &version) + { + using Type = PcPackage::RequiredVersion::ComparisonType; + QVariantMap result; + result[QStringLiteral("name")] = QString::fromStdString(version.name); + const auto versionString = QString::fromStdString(version.version); + const auto qbsVersion = Version::fromString(QString::fromStdString(version.version)); + const auto nextQbsVersion = Version( + qbsVersion.majorVersion(), + qbsVersion.minorVersion(), + qbsVersion.patchLevel() + 1); + switch (version.comparison) { + case Type::LessThan: + result[QStringLiteral("versionBelow")] = versionString; + break; + case Type::GreaterThan: + result[QStringLiteral("versionAtLeast")] = nextQbsVersion.toString(); + break; + case Type::LessThanEqual: + result[QStringLiteral("versionBelow")] = nextQbsVersion.toString(); + break; + case Type::GreaterThanEqual: + result[QStringLiteral("versionAtLeast")] = versionString; + break; + case Type::Equal: + result[QStringLiteral("version")] = versionString; + break; + case Type::NotEqual: + result[QStringLiteral("versionBelow")] = versionString; + result[QStringLiteral("versionAtLeast")] = nextQbsVersion.toString(); + break; + case Type::AlwaysMatch: + break; + } + return result; + }; + + result[QStringLiteral("libs")] = convert(package.libs, flagToMap); + result[QStringLiteral("libsPrivate")] = convert(package.libsPrivate, flagToMap); + result[QStringLiteral("cflags")] = convert(package.cflags, flagToMap); + result[QStringLiteral("requires")] = convert(package.requiresPublic, requiredVersionToMap); + result[QStringLiteral("requiresPrivate")] = + convert(package.requiresPrivate, requiredVersionToMap); + result[QStringLiteral("conflicts")] = convert(package.conflicts, requiredVersionToMap); + result[QStringLiteral("variables")] = variablesMapToMap(package.variables); + + return result; +}; + +QVariantMap brokenPackageToMap(const PcBrokenPackage &package) +{ + QVariantMap result; + result[QStringLiteral("isBroken")] = true; + result[QStringLiteral("filePath")] = QString::fromStdString(package.filePath); + result[QStringLiteral("baseFileName")] = QString::fromStdString(package.baseFileName); + result[QStringLiteral("errorText")] = QString::fromStdString(package.errorText); + return result; +} + +QVariantMap packageVariantToMap(const PcPackageVariant &package) +{ + return package.visit([](const auto &value) { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, PcPackage>) + return packageToMap(value); + else + return brokenPackageToMap(value); + }); +} + +PcPackage::VariablesMap envToVariablesMap(const QProcessEnvironment &env) +{ + PcPackage::VariablesMap result; + const auto keys = env.keys(); + for (const auto &key : keys) + result[key.toStdString()] = env.value(key).toStdString(); + return result; +} + +PcPackage::VariablesMap variablesFromQVariantMap(const QVariantMap &map) +{ + PcPackage::VariablesMap result; + for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) + result[it.key().toStdString()] = it.value().toString().toStdString(); + return result; +} + +std::vector<std::string> stringListToStdVector(const QStringList &list) +{ + return transformed<std::vector<std::string>>(list, [](const auto &s) { + return s.toStdString(); }); +} + +} // namespace + +void PkgConfigJs::declareEnums(JSContext *ctx, JSValue classObj) +{ + DECLARE_ENUM(ctx, classObj, LibraryName); + DECLARE_ENUM(ctx, classObj, LibraryPath); + DECLARE_ENUM(ctx, classObj, StaticLibraryName); + DECLARE_ENUM(ctx, classObj, Framework); + DECLARE_ENUM(ctx, classObj, FrameworkPath); + DECLARE_ENUM(ctx, classObj, LinkerFlag); + DECLARE_ENUM(ctx, classObj, IncludePath); + DECLARE_ENUM(ctx, classObj, SystemIncludePath); + DECLARE_ENUM(ctx, classObj, Define); + DECLARE_ENUM(ctx, classObj, CompilerFlag); +} + +JSValue PkgConfigJs::ctor(JSContext *ctx, JSValue, JSValue, int argc, JSValue *argv, int) +{ + try { + QVariantMap options; + if (argc > 0) + options = fromArg<QVariant>(ctx, "PkgConfig constructor", 1, argv[0]).toMap(); + JSValue obj = createObject(ctx, options); + return obj; + } catch (const PcException &e) { + return throwError(ctx, QString::fromUtf8(e.what())); + } catch (const QString &error) { + return throwError(ctx, error); + } +} + +PkgConfigJs::PkgConfigJs(JSContext *ctx, const QVariantMap &options) : + m_pkgConfig(std::make_unique<PkgConfig>( + convertOptions(ScriptEngine::engineForContext(ctx)->environment(), options))) +{ + for (const auto &package : m_pkgConfig->packages()) { + m_packages.insert( + QString::fromStdString(package.getBaseFileName()), packageVariantToMap(package)); + } +} + +PkgConfig::Options PkgConfigJs::convertOptions(const QProcessEnvironment &env, const QVariantMap &map) +{ + PkgConfig::Options result; + result.libDirs = + stringListToStdVector(map.value(QStringLiteral("libDirs")).toStringList()); + result.extraPaths = + stringListToStdVector(map.value(QStringLiteral("extraPaths")).toStringList()); + result.sysroot = map.value(QStringLiteral("sysroot")).toString().toStdString(); + result.topBuildDir = map.value(QStringLiteral("topBuildDir")).toString().toStdString(); + result.allowSystemLibraryPaths = + map.value(QStringLiteral("allowSystemLibraryPaths"), false).toBool(); + const auto systemLibraryPaths = map.value(QStringLiteral("systemLibraryPaths")).toStringList(); + result.systemLibraryPaths.reserve(systemLibraryPaths.size()); + std::transform( + systemLibraryPaths.begin(), + systemLibraryPaths.end(), + std::back_inserter(result.systemLibraryPaths), + [](const QString &str){ return str.toStdString(); }); + result.disableUninstalled = map.value(QStringLiteral("disableUninstalled"), true).toBool(); + result.staticMode = map.value(QStringLiteral("staticMode"), false).toBool(); + result.definePrefix = map.value(QStringLiteral("definePrefix"), false).toBool(); + result.globalVariables = + variablesFromQVariantMap(map.value(QStringLiteral("globalVariables")).toMap()); + result.systemVariables = envToVariablesMap(env); + + return result; +} + +void PkgConfigJs::setupMethods(JSContext *ctx, JSValue obj) +{ + setupMethod(ctx, obj, "packages", &PkgConfigJs::jsPackages, 0); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionPkgConfig(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) +{ + qbs::Internal::PkgConfigJs::registerClass(engine, extensionObject); +} diff --git a/src/lib/corelib/jsextensions/pkgconfigjs.h b/src/lib/corelib/jsextensions/pkgconfigjs.h new file mode 100644 index 000000000..7e84545a0 --- /dev/null +++ b/src/lib/corelib/jsextensions/pkgconfigjs.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "jsextension.h" + +#include "tools/qbs_export.h" +#include <tools/stlutils.h> + +#include <pkgconfig.h> + +#include <QtCore/qvariant.h> + +#include <memory> + +class QProcessEnvironment; + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT PkgConfigJs : public JsExtension<PkgConfigJs> +{ +public: + enum FlagType { + LibraryName = toUnderlying(PcPackage::Flag::Type::LibraryName), + LibraryPath = toUnderlying(PcPackage::Flag::Type::LibraryPath), + StaticLibraryName = toUnderlying(PcPackage::Flag::Type::StaticLibraryName), + Framework = toUnderlying(PcPackage::Flag::Type::Framework), + FrameworkPath = toUnderlying(PcPackage::Flag::Type::FrameworkPath), + LinkerFlag = toUnderlying(PcPackage::Flag::Type::LinkerFlag), + IncludePath = toUnderlying(PcPackage::Flag::Type::IncludePath), + SystemIncludePath = toUnderlying(PcPackage::Flag::Type::SystemIncludePath), + Define = toUnderlying(PcPackage::Flag::Type::Define), + CompilerFlag = toUnderlying(PcPackage::Flag::Type::CompilerFlag), + }; + + enum class ComparisonType { + LessThan, + GreaterThan, + LessThanEqual, + GreaterThanEqual, + Equal, + NotEqual, + AlwaysMatch + }; + + static const char *name() { return "PkgConfig"; } + static void declareEnums(JSContext *ctx, JSValue classObj); + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int); + + explicit PkgConfigJs(JSContext *ctx, const QVariantMap &options = {}); + + DEFINE_JS_FORWARDER(jsPackages, &PkgConfigJs::packages, "PkgConfig.packages") + QVariantMap packages() const { return m_packages; } + + // also used in tests + static PkgConfig::Options convertOptions(const QProcessEnvironment &env, const QVariantMap &map); + + static void setupMethods(JSContext *ctx, JSValue obj); + +private: + std::unique_ptr<PkgConfig> m_pkgConfig; + QVariantMap m_packages; +}; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/process.cpp b/src/lib/corelib/jsextensions/process.cpp index 064297a32..f0febb364 100644 --- a/src/lib/corelib/jsextensions/process.cpp +++ b/src/lib/corelib/jsextensions/process.cpp @@ -37,6 +37,8 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/executablefinder.h> @@ -44,163 +46,182 @@ #include <tools/shellutils.h> #include <tools/stringconstants.h> -#include <QtCore/qobject.h> #include <QtCore/qprocess.h> -#include <QtCore/qtextcodec.h> #include <QtCore/qtextstream.h> #include <QtCore/qvariant.h> - -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QtCore5Compat/qtextcodec.h> +#else +#include <QtCore/qtextcodec.h> +#endif namespace qbs { namespace Internal { -class Process : public QObject, public QScriptable, public ResourceAcquiringScriptObject +class Process : public JsExtension<Process> { - Q_OBJECT + friend class JsExtension<Process>; public: - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - Process(QScriptContext *context); - ~Process() override; - - Q_INVOKABLE QString getEnv(const QString &name); - Q_INVOKABLE void setEnv(const QString &name, const QString &value); - Q_INVOKABLE void setCodec(const QString &codec); + static const char *name() { return "Process"; } + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int); + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static void setupMethods(JSContext *ctx, JSValue obj); + Process(JSContext *context); + + DEFINE_JS_FORWARDER(jsGetEnv, &Process::getEnv, "Process.getEnv") + DEFINE_JS_FORWARDER(jsSetEnv, &Process::setEnv, "Process.setEnv") + DEFINE_JS_FORWARDER(jsSetCodec, &Process::setCodec, "Process.setCodec") + DEFINE_JS_FORWARDER(jsWorkingDir, &Process::workingDirectory, "Process.workingDirectory") + DEFINE_JS_FORWARDER(jsSetWorkingDir, &Process::setWorkingDirectory, + "Process.setWorkingDirectory") + DEFINE_JS_FORWARDER(jsStart, &Process::start, "Process.start") + DEFINE_JS_FORWARDER(jsClose, &Process::close, "Process.close") + DEFINE_JS_FORWARDER(jsTerminate, &Process::terminate, "Process.terminate") + DEFINE_JS_FORWARDER(jsKill, &Process::kill, "Process.kill") + DEFINE_JS_FORWARDER(jsReadLine, &Process::readLine, "Process.readLine") + DEFINE_JS_FORWARDER(jsAtEnd, &Process::atEnd, "Process.atEnd") + DEFINE_JS_FORWARDER(jsReadStdOut, &Process::readStdOut, "Process.readStdOut") + DEFINE_JS_FORWARDER(jsReadStdErr, &Process::readStdErr, "Process.readStdErr") + DEFINE_JS_FORWARDER(jsCloseWriteChannel, &Process::closeWriteChannel, + "Process.closeWriteChannel") + DEFINE_JS_FORWARDER(jsWrite, &Process::write, "Process.write") + DEFINE_JS_FORWARDER(jsWriteLine, &Process::writeLine, "Process.writeLine") + DEFINE_JS_FORWARDER(jsExitCode, &Process::exitCode, "Process.exitCode") + + static JSValue jsExec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + try { + const auto args = getArguments<QString, QStringList>(ctx, "Process.exec", argc, argv); + bool throwOnError = false; + if (argc > 2) + throwOnError = fromArg<bool>(ctx, "Process.exec", 3, argv[2]); + return JS_NewInt32(ctx, fromJsObject(ctx, this_val) + ->exec(std::get<0>(args), std::get<1>(args), throwOnError)); + } catch (const QString &error) { return throwError(ctx, error); } + } + static JSValue jsWaitForFinished(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) + { + try { + int msecs = 30000; + if (argc > 0) + msecs = getArgument<int>(ctx, "Process.waitForFinished", argc, argv); + return JS_NewBool(ctx, fromJsObject(ctx, this_val)->waitForFinished(msecs)); + } catch (const QString &error) { return throwError(ctx, error); } + } + QString getEnv(const QString &name) const { return m_environment.value(name); } + void setEnv(const QString &name, const QString &value) { m_environment.insert(name, value); } + void setCodec(const QString &codec); - Q_INVOKABLE QString workingDirectory(); - Q_INVOKABLE void setWorkingDirectory(const QString &dir); + QString workingDirectory() const { return m_workingDirectory; } + void setWorkingDirectory(const QString &dir) { m_workingDirectory = 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(); + bool start(const QString &program, const QStringList &arguments); + int exec(const QString &program, const QStringList &arguments, bool throwOnError); + void close(); + bool waitForFinished(int msecs); + void terminate() { m_qProcess->terminate(); } + void kill() { m_qProcess->kill(); } - Q_INVOKABLE QString readLine(); - Q_INVOKABLE bool atEnd() const; - Q_INVOKABLE QString readStdOut(); - Q_INVOKABLE QString readStdErr(); + QString readLine(); + bool atEnd() const { return m_qProcess->atEnd(); } + QString readStdOut() { return m_codec->toUnicode(m_qProcess->readAllStandardOutput()); } + QString readStdErr() { return m_codec->toUnicode(m_qProcess->readAllStandardError()); } - Q_INVOKABLE void closeWriteChannel(); + void closeWriteChannel() { m_qProcess->closeWriteChannel(); } - Q_INVOKABLE void write(const QString &str); - Q_INVOKABLE void writeLine(const QString &str); + void write(const QString &str) { m_qProcess->write(m_codec->fromUnicode(str)); } + void writeLine(const QString &str); - Q_INVOKABLE int exitCode() const; + int exitCode() const { return m_qProcess->exitCode(); } - static QScriptValue js_shellQuote(QScriptContext *context, QScriptEngine *engine); + static JSValue jsShellQuote(JSContext *ctx, JSValue, int argc, JSValue *argv); private: QString findExecutable(const QString &filePath) const; - // ResourceAcquiringScriptObject implementation - void releaseResources() override; - - QProcess *m_qProcess; + std::unique_ptr<QProcess> m_qProcess; QProcessEnvironment m_environment; QString m_workingDirectory; - QTextStream *m_textStream; + QTextCodec *m_codec = nullptr; }; -QScriptValue Process::ctor(QScriptContext *context, QScriptEngine *engine) +JSValue Process::ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int) { - Process *t; - switch (context->argumentCount()) { - case 0: - t = new Process(context); - break; - default: - return context->throwError(QStringLiteral("Process()")); - } + try { + JSValue obj = createObject(ctx); - const auto se = static_cast<ScriptEngine *>(engine); - se->addResourceAcquiringScriptObject(t); - const DubiousContextList dubiousContexts ({ + Process * const process = fromJsObject(ctx, obj); + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{ DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("qbs.Process"), dubiousContexts); - - QScriptValue obj = engine->newQObject(t, QScriptEngine::QtOwnership); - - // Get environment - QVariant v = engine->property(StringConstants::qbsProcEnvVarInternal()); - if (v.isNull()) { - // The build environment is not initialized yet. - // This can happen if one uses Process on the RHS of a binding like Group.name. - t->m_environment = static_cast<ScriptEngine *>(engine)->environment(); - } else { - t->m_environment - = QProcessEnvironment(*reinterpret_cast<QProcessEnvironment*>(v.value<void*>())); - } - se->setUsesIo(); - - 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); + }; + se->checkContext(QStringLiteral("qbs.Process"), dubiousContexts); + + // Get environment + QVariant v = se->property(StringConstants::qbsProcEnvVarInternal()); + if (v.isNull()) { + // The build environment is not initialized yet. + // This can happen if one uses Process on the RHS of a binding like Group.name. + process->m_environment = se->environment(); + } else { + process->m_environment + = QProcessEnvironment(*reinterpret_cast<QProcessEnvironment*>(v.value<void*>())); + } + se->setUsesIo(); + return obj; + } catch (const QString &error) { return throwError(ctx, error); } } -void Process::setEnv(const QString &name, const QString &value) +void Process::setupStaticMethods(JSContext *ctx, JSValue classObj) { - Q_ASSERT(thisObject().engine() == engine()); - m_environment.insert(name, value); + setupMethod(ctx, classObj, "shellQuote", &Process::jsShellQuote, 3); } -QString Process::workingDirectory() +void Process::setupMethods(JSContext *ctx, JSValue obj) { - Q_ASSERT(thisObject().engine() == engine()); - return m_workingDirectory; + setupMethod(ctx, obj, "getEnv", &jsGetEnv, 1); + setupMethod(ctx, obj, "setEnv", &jsSetEnv, 2); + setupMethod(ctx, obj, "setCodec", &jsSetCodec, 1); + setupMethod(ctx, obj, "workingDirectory", &jsWorkingDir, 0); + setupMethod(ctx, obj, "setWorkingDirectory", &jsSetWorkingDir, 1); + setupMethod(ctx, obj, "start", &jsStart, 2); + setupMethod(ctx, obj, "exec", &jsExec, 3); + setupMethod(ctx, obj, "close", &jsClose, 0); + setupMethod(ctx, obj, "waitForFinished", &jsWaitForFinished, 1); + setupMethod(ctx, obj, "terminate", &jsTerminate, 0); + setupMethod(ctx, obj, "kill", &jsKill, 0); + setupMethod(ctx, obj, "readLine", &jsReadLine, 0); + setupMethod(ctx, obj, "atEnd", &jsAtEnd, 0); + setupMethod(ctx, obj, "readStdOut", &jsReadStdOut, 0); + setupMethod(ctx, obj, "readStdErr", &jsReadStdErr, 0); + setupMethod(ctx, obj, "closeWriteChannel", &jsCloseWriteChannel, 0); + setupMethod(ctx, obj, "write", &jsWrite, 1); + setupMethod(ctx, obj, "writeLine", &jsWriteLine, 1); + setupMethod(ctx, obj, "exitCode", &jsExitCode, 0); } -void Process::setWorkingDirectory(const QString &dir) +Process::Process(JSContext *) { - Q_ASSERT(thisObject().engine() == engine()); - m_workingDirectory = dir; + m_qProcess = std::make_unique<QProcess>(); + m_codec = QTextCodec::codecForName("UTF-8"); } 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(findExecutable(program), arguments); + m_qProcess->start(findExecutable(program), arguments, QIODevice::ReadWrite | QIODevice::Text); return m_qProcess->waitForStarted(); } int Process::exec(const QString &program, const QStringList &arguments, bool throwOnError) { - Q_ASSERT(thisObject().engine() == engine()); - if (!start(findExecutable(program), arguments)) { - if (throwOnError) { - context()->throwError(Tr::tr("Error running '%1': %2") - .arg(program, m_qProcess->errorString())); - } + if (throwOnError) + throw Tr::tr("Error running '%1': %2").arg(program, m_qProcess->errorString()); return -1; } m_qProcess->closeWriteChannel(); @@ -208,20 +229,20 @@ int Process::exec(const QString &program, const QStringList &arguments, bool thr if (throwOnError) { if (m_qProcess->error() != QProcess::UnknownError && m_qProcess->error() != QProcess::Crashed) { - context()->throwError(Tr::tr("Error running '%1': %2") - .arg(program, m_qProcess->errorString())); + throw Tr::tr("Error running '%1': %2").arg(program, m_qProcess->errorString()); } else if (m_qProcess->exitStatus() == QProcess::CrashExit || m_qProcess->exitCode() != 0) { QString errorMessage = m_qProcess->error() == QProcess::Crashed ? Tr::tr("Error running '%1': %2").arg(program, m_qProcess->errorString()) - : Tr::tr("Process '%1' finished with exit code %2.").arg(program).arg( - m_qProcess->exitCode()); + : Tr::tr("Process '%1 %2' finished with exit code %3.") + .arg(program, arguments.join(QStringLiteral(" "))) + .arg(m_qProcess->exitCode()); const QString stdOut = readStdOut(); if (!stdOut.isEmpty()) errorMessage.append(Tr::tr(" The standard output was:\n")).append(stdOut); const QString stdErr = readStdErr(); if (!stdErr.isEmpty()) errorMessage.append(Tr::tr(" The standard error output was:\n")).append(stdErr); - context()->throwError(errorMessage); + throw errorMessage; } } if (m_qProcess->error() != QProcess::UnknownError) @@ -233,67 +254,29 @@ void Process::close() { if (!m_qProcess) return; - Q_ASSERT(thisObject().engine() == engine()); - delete m_textStream; - m_textStream = nullptr; - delete m_qProcess; - m_qProcess = nullptr; + m_qProcess.reset(); } 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)); + const auto newCodec = QTextCodec::codecForName(qPrintable(codec)); + if (newCodec) + m_codec = newCodec; } QString Process::readLine() { - return m_textStream->readLine(); -} - -bool Process::atEnd() const -{ - return m_textStream->atEnd(); -} - -QString Process::readStdOut() -{ - return m_textStream->readAll(); -} - -QString Process::readStdErr() -{ - return m_textStream->codec()->toUnicode(m_qProcess->readAllStandardError()); -} - -void Process::closeWriteChannel() -{ - m_textStream->flush(); - m_qProcess->closeWriteChannel(); -} - -int Process::exitCode() const -{ - return m_qProcess->exitCode(); + auto result = m_codec->toUnicode(m_qProcess->readLine()); + if (!result.isEmpty() && result.back() == QLatin1Char('\n')) + result.chop(1); + return result; } QString Process::findExecutable(const QString &filePath) const @@ -302,53 +285,30 @@ QString Process::findExecutable(const QString &filePath) const return exeFinder.findExecutable(filePath, m_workingDirectory); } -void Process::releaseResources() -{ - close(); - deleteLater(); -} - -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'; + m_qProcess->write(m_codec->fromUnicode(str)); + m_qProcess->putChar('\n'); } -QScriptValue Process::js_shellQuote(QScriptContext *context, QScriptEngine *engine) +JSValue Process::jsShellQuote(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() < 2)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("shellQuote expects at least 2 arguments")); - } - const QString program = context->argument(0).toString(); - const QStringList args = context->argument(1).toVariant().toStringList(); - HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); - if (context->argumentCount() > 2) { - hostOs = context->argument(2).toVariant().toStringList().contains(QLatin1String("windows")) - ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; - } - return engine->toScriptValue(shellQuote(program, args, hostOs)); + try { + const auto args = getArguments<QString, QStringList>(ctx, "Process.shellQuote", argc, argv); + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (argc > 2) { + const auto osList = fromArg<QStringList>(ctx, "Process.shellQuote", 3, argv[2]); + hostOs = osList.contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return makeJsString(ctx, shellQuote(std::get<0>(args), std::get<1>(args), hostOs)); + } catch (const QString &error) { return throwError(ctx, error); } } } // namespace Internal } // namespace qbs -void initializeJsExtensionProcess(QScriptValue extensionObject) +void initializeJsExtensionProcess(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue obj = engine->newQMetaObject(&Process::staticMetaObject, engine->newFunction(&Process::ctor)); - extensionObject.setProperty(QStringLiteral("Process"), obj); - obj.setProperty(QStringLiteral("shellQuote"), engine->newFunction(Process::js_shellQuote, 3)); + qbs::Internal::Process::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::Process *) - -#include "process.moc" diff --git a/src/lib/corelib/jsextensions/propertylist.cpp b/src/lib/corelib/jsextensions/propertylist.cpp index 197d5e99e..914701203 100644 --- a/src/lib/corelib/jsextensions/propertylist.cpp +++ b/src/lib/corelib/jsextensions/propertylist.cpp @@ -38,11 +38,11 @@ ** ****************************************************************************/ -#include <QtScript/qscriptengine.h> +#include <language/scriptengine.h> -void initializeJsExtensionPropertyList(QScriptValue extensionObject) +void initializeJsExtensionPropertyList(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - QScriptEngine *engine = extensionObject.engine(); - QScriptValue obj = engine->newObject(); // provide a fake object - extensionObject.setProperty(QStringLiteral("PropertyList"), obj); + JSValue obj = engine->newObject(); // provide a fake object + qbs::Internal::setJsProperty(engine->context(), extensionObject, + QStringLiteral("PropertyList"), obj); } diff --git a/src/lib/corelib/jsextensions/propertylist.h b/src/lib/corelib/jsextensions/propertylist.h deleted file mode 100644 index adb90f786..000000000 --- a/src/lib/corelib/jsextensions/propertylist.h +++ /dev/null @@ -1,115 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2015 Petroules Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QBS_PROPERTYLIST_H -#define QBS_PROPERTYLIST_H - -#include <QtCore/qglobal.h> - -// ### remove when qbs requires qbs 1.6 to build itself -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) && defined(__APPLE__) && !defined(Q_OS_MAC) -#define Q_OS_MAC -#endif - -#ifndef Q_OS_MAC - -#include <QtScript/qscriptengine.h> - -namespace qbs { -namespace Internal { - -// provide a fake initializer for other platforms -void initializeJsExtensionPropertyList(QScriptValue extensionObject) -{ - // provide a fake object - QScriptEngine *engine = extensionObject.engine(); - extensionObject.setProperty(QLatin1String("PropertyList"), engine->newObject()); -} - -} // namespace Internal -} // namespace qbs - -#else // Q_OS_MAC - -#include <QtCore/qobject.h> -#include <QtCore/qstring.h> -#include <QtCore/qvariant.h> - -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptvalue.h> - -namespace qbs { -namespace Internal { - -void initializeJsExtensionPropertyList(QScriptValue extensionObject); - -class PropertyListPrivate; - -class PropertyList : public QObject, public QScriptable -{ - Q_OBJECT -public: - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - PropertyList(QScriptContext *context); - ~PropertyList(); - Q_INVOKABLE bool isEmpty() const; - Q_INVOKABLE void clear(); - Q_INVOKABLE void readFromObject(const QScriptValue &value); - Q_INVOKABLE void readFromString(const QString &input); - Q_INVOKABLE void readFromFile(const QString &filePath); - Q_INVOKABLE void readFromData(const QByteArray &data); - Q_INVOKABLE void writeToFile(const QString &filePath, const QString &plistFormat); - Q_INVOKABLE QScriptValue format() const; - Q_INVOKABLE QScriptValue toObject() const; - Q_INVOKABLE QString toString(const QString &plistFormat) const; - Q_INVOKABLE QString toXMLString() const; - Q_INVOKABLE QString toJSON(const QString &style = QString()) const; -private: - PropertyListPrivate *d; -}; - -} // namespace Internal -} // namespace qbs - -Q_DECLARE_METATYPE(qbs::Internal::PropertyList *) - -#endif // Q_OS_MAC - -#endif // QBS_PROPERTYLIST_H diff --git a/src/lib/corelib/jsextensions/propertylist.mm b/src/lib/corelib/jsextensions/propertylist_darwin.mm index 6ac9d56c9..97e8f6209 100644 --- a/src/lib/corelib/jsextensions/propertylist.mm +++ b/src/lib/corelib/jsextensions/propertylist_darwin.mm @@ -38,118 +38,122 @@ ** ****************************************************************************/ +#include "jsextension.h" +#include "propertylistutils.h" + #include <language/scriptengine.h> +#include <logging/translator.h> #include <tools/hostosinfo.h> #include <QtCore/qfile.h> -#include <QtCore/qobject.h> #include <QtCore/qstring.h> #include <QtCore/qtextstream.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> - -// Same values as CoreFoundation and Foundation APIs -enum { - QPropertyListOpenStepFormat = 1, - QPropertyListXMLFormat_v1_0 = 100, - QPropertyListBinaryFormat_v1_0 = 200, - QPropertyListJSONFormat = 1000 // If this conflicts someday, just change it :) -}; - namespace qbs { namespace Internal { -class PropertyListPrivate; - -class PropertyList : public QObject, public QScriptable +class PropertyList : public JsExtension<PropertyList> { - Q_OBJECT public: - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - PropertyList(QScriptContext *context); - ~PropertyList() override; - Q_INVOKABLE bool isEmpty() const; - Q_INVOKABLE void clear(); - Q_INVOKABLE void readFromObject(const QScriptValue &value); - Q_INVOKABLE void readFromString(const QString &input); - Q_INVOKABLE void readFromFile(const QString &filePath); - Q_INVOKABLE void readFromData(const QByteArray &data); - Q_INVOKABLE void writeToFile(const QString &filePath, const QString &plistFormat); - Q_INVOKABLE QScriptValue format() const; - Q_INVOKABLE QScriptValue toObject() const; - Q_INVOKABLE QString toString(const QString &plistFormat) const; - Q_INVOKABLE QString toXMLString() const; - Q_INVOKABLE QString toJSON(const QString &style = QString()) const; + static const char *name() { return "PropertyList"; } + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int); + PropertyList(JSContext *) {} + static void setupMethods(JSContext *ctx, JSValue obj); + + DEFINE_JS_FORWARDER(jsIsEmpty, &PropertyList::isEmpty, "PropertyList.isEmpty"); + DEFINE_JS_FORWARDER(jsClear, &PropertyList::clear, "PropertyList.clear"); + DEFINE_JS_FORWARDER(jsReadFromObject, &PropertyList::readFromObject, + "PropertyList.readFromObject"); + DEFINE_JS_FORWARDER(jsReadFromString, &PropertyList::readFromString, + "PropertyList.readFromString"); + DEFINE_JS_FORWARDER(jsReadFromFile, &PropertyList::readFromFile, "PropertyList.readFromFile"); + DEFINE_JS_FORWARDER(jsReadFromData, &PropertyList::readFromData, "PropertyList.readFromData"); + DEFINE_JS_FORWARDER(jsWriteToFile, &PropertyList::writeToFile, "PropertyList.writeToFile"); + DEFINE_JS_FORWARDER(jsFormat, &PropertyList::format, "PropertyList.format"); + DEFINE_JS_FORWARDER(jsToObject, &PropertyList::toObject, "PropertyList.toObject"); + DEFINE_JS_FORWARDER(jsToString, &PropertyList::toString, "PropertyList.toString"); + DEFINE_JS_FORWARDER(jsToXmlString, &PropertyList::toXMLString, "PropertyList.toXMLString"); + + static JSValue jsToJson(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + try { + QString style; + if (argc > 0) + style = fromArg<QString>(ctx, "Process.exec", 1, argv[0]); + return toJsValue(ctx, fromJsObject(ctx, this_val)->toJSON(style)); + } catch (const QString &error) { return throwError(ctx, error); } + } + private: - PropertyListPrivate *d; -}; + bool isEmpty() const { return m_propertyListObject.isNull(); } + void clear(); + void readFromObject(const QVariant &value); + void readFromString(const QString &input); + void readFromFile(const QString &filePath); + void readFromData(const QByteArray &data); + void writeToFile(const QString &filePath, const QString &plistFormat); + std::optional<QString> format() const; + QVariant toObject() const { + return m_propertyListObject.isNull() ? QVariantMap() : m_propertyListObject; + } + QString toString(const QString &plistFormat) const; + QString toXMLString() const; + QString toJSON(const QString &style = QString()) const; -class PropertyListPrivate -{ -public: - PropertyListPrivate(); + QByteArray writeToData(const QString &format) const; - QVariant propertyListObject; - int propertyListFormat; + QVariant m_propertyListObject; + int m_propertyListFormat = 0; +}; - void readFromData(QScriptContext *context, QByteArray data); - QByteArray writeToData(QScriptContext *context, const QString &format); +// Same values as CoreFoundation and Foundation APIs +enum { + QPropertyListOpenStepFormat = 1, + QPropertyListXMLFormat_v1_0 = 100, + QPropertyListBinaryFormat_v1_0 = 200, + QPropertyListJSONFormat = 1000 // If this conflicts someday, just change it :) }; -QScriptValue PropertyList::ctor(QScriptContext *context, QScriptEngine *engine) +JSValue PropertyList::ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int) { - auto const se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ + try { + JSValue obj = createObject(ctx); + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{ DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("qbs.PropertyList"), dubiousContexts); - - auto p = new PropertyList(context); - QScriptValue obj = engine->newQObject(p, QScriptEngine::ScriptOwnership); - return obj; -} - -PropertyListPrivate::PropertyListPrivate() - : propertyListObject(), propertyListFormat(0) -{ -} - -PropertyList::~PropertyList() -{ - delete d; -} - -PropertyList::PropertyList(QScriptContext *context) -: d(new PropertyListPrivate) -{ - Q_UNUSED(context); - Q_ASSERT(thisObject().engine() == engine()); + }; + se->checkContext(QStringLiteral("qbs.PropertyList"), dubiousContexts); + return obj; + } catch (const QString &error) { return throwError(ctx, error); } } -bool PropertyList::isEmpty() const +void PropertyList::setupMethods(JSContext *ctx, JSValue obj) { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - return p->d->propertyListObject.isNull(); + setupMethod(ctx, obj, "isEmpty", &jsIsEmpty, 0); + setupMethod(ctx, obj, "clear", &jsClear, 0); + setupMethod(ctx, obj, "readFromObject", &jsReadFromObject, 1); + setupMethod(ctx, obj, "readFromString", &jsReadFromString, 1); + setupMethod(ctx, obj, "readFromFile", &jsReadFromFile, 1); + setupMethod(ctx, obj, "readFromData", &jsReadFromData, 1); + setupMethod(ctx, obj, "writeToFile", &jsWriteToFile, 1); + setupMethod(ctx, obj, "format", &jsFormat, 0); + setupMethod(ctx, obj, "toObject", &jsToObject, 0); + setupMethod(ctx, obj, "toString", &jsToString, 1); + setupMethod(ctx, obj, "toXMLString", &jsToXmlString, 1); + setupMethod(ctx, obj, "toJSON", &jsToJson, 1); } void PropertyList::clear() { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - p->d->propertyListObject = QVariant(); - p->d->propertyListFormat = 0; + m_propertyListObject = QVariant(); + m_propertyListFormat = 0; } -void PropertyList::readFromObject(const QScriptValue &value) +void PropertyList::readFromObject(const QVariant &value) { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - p->d->propertyListObject = value.toVariant(); - p->d->propertyListFormat = 0; // wasn't deserialized from any external format + m_propertyListObject = value; + m_propertyListFormat = 0; // wasn't deserialized from any external format } void PropertyList::readFromString(const QString &input) @@ -159,49 +163,68 @@ void PropertyList::readFromString(const QString &input) void PropertyList::readFromFile(const QString &filePath) { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { const QByteArray data = file.readAll(); if (file.error() == QFile::NoError) { - p->d->readFromData(p->context(), data); + readFromData(data); return; } } - - p->context()->throwError(QStringLiteral("%1: %2").arg(filePath).arg(file.errorString())); + throw QStringLiteral("%1: %2").arg(filePath).arg(file.errorString()); } void PropertyList::readFromData(const QByteArray &data) { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - p->d->readFromData(p->context(), data); + @autoreleasepool { + NSPropertyListFormat format; + int internalFormat = 0; + NSString *errorString = nil; + id plist = [NSPropertyListSerialization propertyListWithData:data.toNSData() + options:0 + format:&format error:nil]; + if (plist) { + internalFormat = format; + } else { + NSError *error = nil; + plist = [NSJSONSerialization JSONObjectWithData:data.toNSData() + options:0 + error:&error]; + if (Q_UNLIKELY(!plist)) { + errorString = [error localizedDescription]; + } else { + internalFormat = QPropertyListJSONFormat; + } + } + + if (Q_UNLIKELY(!plist)) + throw QString::fromNSString(errorString); + QVariant obj = QPropertyListUtils::fromPropertyList(plist); + if (!obj.isNull()) { + m_propertyListObject = obj; + m_propertyListFormat = internalFormat; + } else { + throw Tr::tr("error converting property list"); + } + } } void PropertyList::writeToFile(const QString &filePath, const QString &plistFormat) { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - QFile file(filePath); - QByteArray data = p->d->writeToData(p->context(), plistFormat); + QByteArray data = writeToData(plistFormat); if (Q_LIKELY(!data.isEmpty())) { if (file.open(QIODevice::WriteOnly) && file.write(data) == data.size()) { return; } } - p->context()->throwError(QStringLiteral("%1: %2").arg(filePath).arg(file.errorString())); + throw QStringLiteral("%1: %2").arg(filePath).arg(file.errorString()); } -QScriptValue PropertyList::format() const +std::optional<QString> PropertyList::format() const { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - switch (p->d->propertyListFormat) + switch (m_propertyListFormat) { case QPropertyListOpenStepFormat: return QStringLiteral("openstep"); @@ -212,32 +235,20 @@ QScriptValue PropertyList::format() const case QPropertyListJSONFormat: return QStringLiteral("json"); default: - return p->engine()->undefinedValue(); + return {}; } } -QScriptValue PropertyList::toObject() const -{ - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - return p->engine()->toScriptValue(p->d->propertyListObject); -} - QString PropertyList::toString(const QString &plistFormat) const { - Q_ASSERT(thisObject().engine() == engine()); - auto p = qscriptvalue_cast<PropertyList*>(thisObject()); - if (plistFormat == QLatin1String("binary1")) { - p->context()->throwError(QStringLiteral("Property list object cannot be converted to a " - "string in the binary1 format; this format can only " - "be written directly to a file")); - return {}; + throw Tr::tr("Property list object cannot be converted to a " + "string in the binary1 format; this format can only " + "be written directly to a file"); } if (!isEmpty()) - return QString::fromUtf8(p->d->writeToData(p->context(), plistFormat)); - + return QString::fromUtf8(writeToData(plistFormat)); return {}; } @@ -255,63 +266,16 @@ QString PropertyList::toJSON(const QString &style) const return toString(format); } -} // namespace Internal -} // namespace qbs - -#include "propertylistutils.h" - -namespace qbs { -namespace Internal { - -void PropertyListPrivate::readFromData(QScriptContext *context, QByteArray data) -{ - @autoreleasepool { - NSPropertyListFormat format; - int internalFormat = 0; - NSString *errorString = nil; - id plist = [NSPropertyListSerialization propertyListWithData:data.toNSData() - options:0 - format:&format error:nil]; - if (plist) { - internalFormat = format; - } else { - NSError *error = nil; - plist = [NSJSONSerialization JSONObjectWithData:data.toNSData() - options:0 - error:&error]; - if (Q_UNLIKELY(!plist)) { - errorString = [error localizedDescription]; - } else { - internalFormat = QPropertyListJSONFormat; - } - } - - if (Q_UNLIKELY(!plist)) { - context->throwError(QString::fromNSString(errorString)); - } else { - QVariant obj = QPropertyListUtils::fromPropertyList(plist); - if (!obj.isNull()) { - propertyListObject = obj; - propertyListFormat = internalFormat; - } else { - context->throwError(QStringLiteral("error converting property list")); - } - } - } -} - -QByteArray PropertyListPrivate::writeToData(QScriptContext *context, const QString &format) +QByteArray PropertyList::writeToData(const QString &format) const { @autoreleasepool { NSError *error = nil; NSString *errorString = nil; NSData *data = nil; - id obj = QPropertyListUtils::toPropertyList(propertyListObject); - if (!obj) { - context->throwError(QStringLiteral("error converting property list")); - return QByteArray(); - } + id obj = QPropertyListUtils::toPropertyList(m_propertyListObject); + if (!obj) + throw Tr::tr("error converting property list"); if (format == QLatin1String("json") || format == QLatin1String("json-pretty") || format == QLatin1String("json-compact")) { @@ -348,9 +312,8 @@ QByteArray PropertyListPrivate::writeToData(QScriptContext *context, const QStri @"format", format.toUtf8().constData()]; } - if (Q_UNLIKELY(!data)) { - context->throwError(QString::fromNSString(errorString)); - } + if (Q_UNLIKELY(!data)) + throw QString::fromNSString(errorString); return QByteArray::fromNSData(data); } @@ -359,15 +322,7 @@ QByteArray PropertyListPrivate::writeToData(QScriptContext *context, const QStri } // namespace Internal } // namespace qbs -void initializeJsExtensionPropertyList(QScriptValue extensionObject) +void initializeJsExtensionPropertyList(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue obj = engine->newQMetaObject(&PropertyList::staticMetaObject, - engine->newFunction(&PropertyList::ctor)); - extensionObject.setProperty(QStringLiteral("PropertyList"), obj); + qbs::Internal::PropertyList::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::PropertyList *) - -#include "propertylist.moc" diff --git a/src/lib/corelib/jsextensions/propertylistutils.mm b/src/lib/corelib/jsextensions/propertylistutils.mm index b8ae1b8e0..704b1a8ce 100644 --- a/src/lib/corelib/jsextensions/propertylistutils.mm +++ b/src/lib/corelib/jsextensions/propertylistutils.mm @@ -127,33 +127,33 @@ static NSArray *toArray(const QVariantList &list); static id toObject(const QVariant &variant) { - if (variant.type() == QVariant::Hash) { + if (variant.userType() == QMetaType::QVariantHash) { return toDictionary(qHashToMap(variant.toHash())); - } else if (variant.type() == QVariant::Map) { + } else if (variant.userType() == QMetaType::QVariantMap) { return toDictionary(variant.toMap()); - } else if (variant.type() == QVariant::List) { + } else if (variant.userType() == QMetaType::QVariantList) { return toArray(variant.toList()); - } else if (variant.type() == QVariant::String) { + } else if (variant.userType() == QMetaType::QString) { return variant.toString().toNSString(); - } else if (variant.type() == QVariant::ByteArray) { + } else if (variant.userType() == QMetaType::QByteArray) { return variant.toByteArray().toNSData(); - } else if (variant.type() == QVariant::Date || - variant.type() == QVariant::DateTime) { + } else if (variant.userType() == QMetaType::QDate || + variant.userType() == QMetaType::QDateTime) { return variant.toDateTime().toNSDate(); - } else if (variant.type() == QVariant::Bool) { + } else if (variant.userType() == QMetaType::Bool) { return variant.toBool() ? [NSNumber numberWithBool:YES] : [NSNumber numberWithBool:NO]; - } else if (variant.type() == QVariant::Char || - variant.type() == QVariant::Int) { + } else if (variant.userType() == QMetaType::Char || + variant.userType() == QMetaType::Int) { return [NSNumber numberWithInt:variant.toInt()]; - } else if (variant.type() == QVariant::UInt) { + } else if (variant.userType() == QMetaType::UInt) { return [NSNumber numberWithUnsignedInt:variant.toUInt()]; - } else if (variant.type() == QVariant::LongLong) { + } else if (variant.userType() == QMetaType::LongLong) { return [NSNumber numberWithLongLong:variant.toLongLong()]; - } else if (variant.type() == QVariant::ULongLong) { + } else if (variant.userType() == QMetaType::ULongLong) { return [NSNumber numberWithUnsignedLongLong:variant.toULongLong()]; - } else if (variant.type() == QVariant::Double) { + } else if (variant.userType() == QMetaType::Double) { return [NSNumber numberWithDouble:variant.toDouble()]; } else { return [NSNull null]; diff --git a/src/lib/corelib/jsextensions/temporarydir.cpp b/src/lib/corelib/jsextensions/temporarydir.cpp index 470d21d20..209c058f2 100644 --- a/src/lib/corelib/jsextensions/temporarydir.cpp +++ b/src/lib/corelib/jsextensions/temporarydir.cpp @@ -38,17 +38,14 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qobject.h> #include <QtCore/qtemporarydir.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { @@ -60,63 +57,49 @@ static bool tempDirIsCanonical() return false; } -class TemporaryDir : public QObject, public QScriptable +class TemporaryDir : public JsExtension<TemporaryDir> { - Q_OBJECT + friend class JsExtension<TemporaryDir>; public: - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - TemporaryDir(QScriptContext *context); - Q_INVOKABLE bool isValid() const; - Q_INVOKABLE QString path() const; - Q_INVOKABLE bool remove(); + static const char *name() { return "TemporaryDir"; } + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, int, JSValueConst *, int) + { + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts{DubiousContext(EvalContext::PropertyEvaluation, + DubiousContext::SuggestMoving)}; + se->checkContext(QStringLiteral("qbs.TemporaryDir"), dubiousContexts); + return createObject(ctx); + } + static void setupMethods(JSContext *ctx, JSValue obj) + { + setupMethod(ctx, obj, "isValid", &jsIsValid, 0); + setupMethod(ctx, obj, "path", &jsPath, 0); + setupMethod(ctx, obj, "remove", &jsRemove, 0); + } + private: - QTemporaryDir dir; -}; + TemporaryDir(JSContext *) { m_dir.setAutoRemove(false); } -QScriptValue TemporaryDir::ctor(QScriptContext *context, QScriptEngine *engine) -{ - const auto se = static_cast<ScriptEngine *>(engine); - const DubiousContextList dubiousContexts({ - DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("qbs.TemporaryDir"), dubiousContexts); + DEFINE_JS_FORWARDER(jsIsValid, &TemporaryDir::isValid, "TemporaryDir.isValid") + DEFINE_JS_FORWARDER(jsPath, &TemporaryDir::path, "TemporaryDir.path") + DEFINE_JS_FORWARDER(jsRemove, &TemporaryDir::remove, "TemporaryDir.remove") - const auto t = new TemporaryDir(context); - QScriptValue obj = engine->newQObject(t, QScriptEngine::ScriptOwnership); - return obj; -} + bool isValid() const { return m_dir.isValid(); } -TemporaryDir::TemporaryDir(QScriptContext *context) -{ - Q_UNUSED(context); - dir.setAutoRemove(false); -} + QString path() const + { + return tempDirIsCanonical() ? m_dir.path() : QFileInfo(m_dir.path()).canonicalFilePath(); + } -bool TemporaryDir::isValid() const -{ - return dir.isValid(); -} - -QString TemporaryDir::path() const -{ - return tempDirIsCanonical() ? dir.path() : QFileInfo(dir.path()).canonicalFilePath(); -} + bool remove() { return m_dir.remove(); } -bool TemporaryDir::remove() -{ - return dir.remove(); -} + QTemporaryDir m_dir; +}; } // namespace Internal } // namespace qbs -void initializeJsExtensionTemporaryDir(QScriptValue extensionObject) +void initializeJsExtensionTemporaryDir(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue obj = engine->newQMetaObject(&TemporaryDir::staticMetaObject, - engine->newFunction(&TemporaryDir::ctor)); - extensionObject.setProperty(QStringLiteral("TemporaryDir"), obj); + qbs::Internal::TemporaryDir::registerClass(engine, extensionObject); } - -#include "temporarydir.moc" diff --git a/src/lib/corelib/jsextensions/textfile.cpp b/src/lib/corelib/jsextensions/textfile.cpp index 7c67f9019..c9f0edec9 100644 --- a/src/lib/corelib/jsextensions/textfile.cpp +++ b/src/lib/corelib/jsextensions/textfile.cpp @@ -37,27 +37,29 @@ ** ****************************************************************************/ +#include "jsextension.h" + #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/hostosinfo.h> #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qobject.h> #include <QtCore/qtextstream.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QtCore5Compat/qtextcodec.h> +#else +#include <QtCore/qtextcodec.h> +#endif namespace qbs { namespace Internal { -class TextFile : public QObject, public QScriptable, public ResourceAcquiringScriptObject +class TextFile : public JsExtension<TextFile> { - Q_OBJECT - Q_ENUMS(OpenMode) + friend class JsExtension<TextFile>; public: enum OpenMode { @@ -67,83 +69,93 @@ public: Append = 4 }; - static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); - ~TextFile() override; - - Q_INVOKABLE void close(); - Q_INVOKABLE QString filePath(); - 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); + static const char *name() { return "TextFile"; } + static void declareEnums(JSContext *ctx, JSValue classObj); + static JSValue ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int); + static void setupMethods(JSContext *ctx, JSValue obj); private: - TextFile(QScriptContext *context, const QString &filePath, OpenMode mode = ReadOnly, - const QString &codec = QLatin1String("UTF8")); - - bool checkForClosed() const; - - // ResourceAcquiringScriptObject implementation - void releaseResources() override; - - QFile *m_file; - QTextStream *m_stream; + DEFINE_JS_FORWARDER(jsClose, &TextFile::close, "TextFile.close") + DEFINE_JS_FORWARDER(jsFilePath, &TextFile::filePath, "TextFile.filePath") + DEFINE_JS_FORWARDER(jsSetCodec, &TextFile::setCodec, "TextFile.setCodec") + DEFINE_JS_FORWARDER(jsReadLine, &TextFile::readLine, "TextFile.readLine") + DEFINE_JS_FORWARDER(jsReadAll, &TextFile::readAll, "TextFile.readAll") + DEFINE_JS_FORWARDER(jsAtEof, &TextFile::atEof, "TextFile.atEof") + DEFINE_JS_FORWARDER(jsTruncate, &TextFile::truncate, "TextFile.truncate") + DEFINE_JS_FORWARDER(jsWrite, &TextFile::write, "TextFile.write") + DEFINE_JS_FORWARDER(jsWriteLine, &TextFile::writeLine, "TextFile.writeLine") + + void close(); + QString filePath(); + void setCodec(const QString &codec); + QString readLine(); + QString readAll(); + bool atEof() const; + void truncate(); + void write(const QString &str); + void writeLine(const QString &str); + + TextFile(JSContext *, const QString &filePath, OpenMode mode, const QString &codec); + + void checkForClosed() const; + + std::unique_ptr<QFile> m_file; + QTextCodec *m_codec = nullptr; }; -QScriptValue TextFile::ctor(QScriptContext *context, QScriptEngine *engine) +void TextFile::declareEnums(JSContext *ctx, JSValue classObj) { - TextFile *t; - switch (context->argumentCount()) { - case 0: - return context->throwError(Tr::tr("TextFile constructor needs path of file to be opened.")); - 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(Tr::tr("TextFile constructor takes at most three parameters.")); - } + DECLARE_ENUM(ctx, classObj, ReadOnly); + DECLARE_ENUM(ctx, classObj, WriteOnly); + DECLARE_ENUM(ctx, classObj, ReadWrite); + DECLARE_ENUM(ctx, classObj, Append); +} - const auto se = static_cast<ScriptEngine *>(engine); - se->addResourceAcquiringScriptObject(t); - const DubiousContextList dubiousContexts({ +JSValue TextFile::ctor(JSContext *ctx, JSValueConst, JSValueConst, + int argc, JSValueConst *argv, int) +{ + try { + const auto filePath = getArgument<QString>(ctx, "TextFile constructor", argc, argv); + OpenMode mode = ReadOnly; + QString codec = QLatin1String("UTF-8"); + if (argc > 1) { + mode = static_cast<OpenMode> + (fromArg<qint32>(ctx, "TextFile constructor", 2, argv[1])); + } + if (argc > 2) { + codec = fromArg<QString>(ctx, "TextFile constructor", 3, argv[2]); + } + ScopedJsValue obj(ctx, createObject(ctx, filePath, mode, codec)); + + const auto se = ScriptEngine::engineForContext(ctx); + const DubiousContextList dubiousContexts { DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) - }); - se->checkContext(QStringLiteral("qbs.TextFile"), dubiousContexts); - se->setUsesIo(); - - return engine->newQObject(t, QScriptEngine::QtOwnership); + }; + se->checkContext(QStringLiteral("qbs.TextFile"), dubiousContexts); + se->setUsesIo(); + return obj.release(); + } catch (const QString &error) { return throwError(ctx, error); } } -TextFile::~TextFile() -{ - delete m_stream; - delete m_file; +void TextFile::setupMethods(JSContext *ctx, JSValue obj) +{ + setupMethod(ctx, obj, "close", &jsClose, 0); + setupMethod(ctx, obj, "filePath", &jsFilePath, 0); + setupMethod(ctx, obj, "atEof", &jsAtEof, 0); + setupMethod(ctx, obj, "setCodec", &jsSetCodec, 1); + setupMethod(ctx, obj, "readLine", &jsReadLine, 0); + setupMethod(ctx, obj, "readAll", &jsReadAll, 0); + setupMethod(ctx, obj, "truncate", &jsTruncate, 0); + setupMethod(ctx, obj, "write", &jsWrite, 1); + setupMethod(ctx, obj, "writeLine", &jsWriteLine, 1); } -TextFile::TextFile(QScriptContext *context, const QString &filePath, OpenMode mode, - const QString &codec) +TextFile::TextFile(JSContext *, const QString &filePath, OpenMode mode, const QString &codec) { - Q_UNUSED(codec) - Q_ASSERT(thisObject().engine() == engine()); - - m_file = new QFile(filePath); - m_stream = new QTextStream(m_file); + auto file = std::make_unique<QFile>(filePath); + const auto newCodec = QTextCodec::codecForName(qPrintable(codec)); + m_codec = newCodec ? newCodec : QTextCodec::codecForName("UTF-8"); QIODevice::OpenMode m = QIODevice::NotOpen; if (mode & ReadOnly) m |= QIODevice::ReadOnly; @@ -151,113 +163,83 @@ TextFile::TextFile(QScriptContext *context, const QString &filePath, OpenMode mo m |= QIODevice::WriteOnly; if (mode & Append) m |= QIODevice::Append; - if (Q_UNLIKELY(!m_file->open(m))) { - context->throwError(Tr::tr("Unable to open file '%1': %2") - .arg(filePath, m_file->errorString())); - delete m_file; - m_file = nullptr; - } + m |= QIODevice::Text; + if (Q_UNLIKELY(!file->open(m))) + throw Tr::tr("Unable to open file '%1': %2").arg(filePath, file->errorString()); + m_file = std::move(file); } void TextFile::close() { - if (checkForClosed()) - return; - delete m_stream; - m_stream = nullptr; + checkForClosed(); m_file->close(); - delete m_file; - m_file = nullptr; + m_file.reset(); } QString TextFile::filePath() { - if (checkForClosed()) - return {}; + checkForClosed(); return QFileInfo(*m_file).absoluteFilePath(); } void TextFile::setCodec(const QString &codec) { - if (checkForClosed()) - return; - m_stream->setCodec(qPrintable(codec)); + checkForClosed(); + const auto newCodec = QTextCodec::codecForName(qPrintable(codec)); + if (newCodec) + m_codec = newCodec; } QString TextFile::readLine() { - if (checkForClosed()) - return {}; - return m_stream->readLine(); + checkForClosed(); + auto result = m_codec->toUnicode(m_file->readLine()); + if (!result.isEmpty() && result.back() == QLatin1Char('\n')) + result.chop(1); + return result; } QString TextFile::readAll() { - if (checkForClosed()) - return {}; - return m_stream->readAll(); + checkForClosed(); + return m_codec->toUnicode(m_file->readAll()); } bool TextFile::atEof() const { - if (checkForClosed()) - return true; - return m_stream->atEnd(); + checkForClosed(); + return m_file->atEnd(); } void TextFile::truncate() { - if (checkForClosed()) - return; + checkForClosed(); m_file->resize(0); - m_stream->reset(); } void TextFile::write(const QString &str) { - if (checkForClosed()) - return; - (*m_stream) << str; + checkForClosed(); + m_file->write(m_codec->fromUnicode(str)); } void TextFile::writeLine(const QString &str) { - if (checkForClosed()) - return; - (*m_stream) << str; - if (HostOsInfo::isWindowsHost()) - (*m_stream) << '\r'; - (*m_stream) << '\n'; + checkForClosed(); + m_file->write(m_codec->fromUnicode(str)); + m_file->putChar('\n'); } -bool TextFile::checkForClosed() const +void TextFile::checkForClosed() const { - if (m_file) - return false; - QScriptContext *ctx = context(); - if (ctx) - ctx->throwError(Tr::tr("Access to TextFile object that was already closed.")); - return true; -} - -void TextFile::releaseResources() -{ - close(); - deleteLater(); + if (!m_file) + throw Tr::tr("Access to TextFile object that was already closed."); } } // namespace Internal } // namespace qbs -void initializeJsExtensionTextFile(QScriptValue extensionObject) +void initializeJsExtensionTextFile(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) { - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue obj = engine->newQMetaObject(&TextFile::staticMetaObject, - engine->newFunction(&TextFile::ctor)); - extensionObject.setProperty(QStringLiteral("TextFile"), obj); + qbs::Internal::TextFile::registerClass(engine, extensionObject); } - -Q_DECLARE_METATYPE(qbs::Internal::TextFile *) - -#include "textfile.moc" diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp index b425bb4a2..cdcee59fa 100644 --- a/src/lib/corelib/jsextensions/utilitiesextension.cpp +++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp @@ -37,12 +37,15 @@ ** ****************************************************************************/ +#include "jsextension.h" +#include "jsextensions.h" + #include <api/languageinfo.h> -#include <jsextensions/jsextensions.h> #include <language/scriptengine.h> #include <logging/translator.h> #include <tools/architectures.h> #include <tools/hostosinfo.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <tools/toolchains.h> #include <tools/version.h> @@ -72,6 +75,7 @@ struct fat_arch_64 { #ifdef Q_OS_WIN +#include <tools/clangclinfo.h> #include <tools/msvcinfo.h> #include <tools/vsenvironmentdetector.h> #endif @@ -82,120 +86,152 @@ struct fat_arch_64 { #include <QtCore/qfile.h> #include <QtCore/qlibrary.h> -#include <QtScript/qscriptable.h> -#include <QtScript/qscriptengine.h> - namespace qbs { namespace Internal { -class UtilitiesExtension : public QObject, QScriptable +class DummyLogSink : public ILogSink { + void doPrintMessage(LoggerLevel, const QString &, const QString &) override { } +}; + +class UtilitiesExtension : public JsExtension<UtilitiesExtension> { - Q_OBJECT public: - static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_canonicalPlatform(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_canonicalTargetArchitecture(QScriptContext *context, - QScriptEngine *engine); - static QScriptValue js_canonicalToolchain(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_cStringQuote(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_getHash(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_getNativeSetting(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_kernelVersion(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_nativeSettingGroups(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_rfc1034identifier(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue js_smimeMessageContent(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_certificateInfo(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_signingIdentities(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_msvcCompilerInfo(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_clangClCompilerInfo(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue js_versionCompare(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue js_qmlTypeInfo(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_builtinExtensionNames(QScriptContext *context, QScriptEngine *engine); - static QScriptValue js_isSharedLibrary(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue js_getArchitecturesFromBinary(QScriptContext *context, - QScriptEngine *engine); + static const char *name() { return "Utilities"; } + static void setupStaticMethods(JSContext *ctx, JSValue classObj); + static JSValue js_canonicalArchitecture(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_canonicalPlatform(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_canonicalTargetArchitecture(JSContext *ctx, JSValueConst, + int, JSValueConst *); + static JSValue js_canonicalToolchain(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_cStringQuote(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_getHash(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_getNativeSetting(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_kernelVersion(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_nativeSettingGroups(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_rfc1034identifier(JSContext *ctx, JSValueConst, int, JSValueConst *); + + static JSValue js_smimeMessageContent(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_certificateInfo(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_signingIdentities(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_msvcCompilerInfo(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_clangClCompilerInfo(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + static JSValue js_installedMSVCs(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_installedClangCls(JSContext *ctx, JSValueConst, int, JSValueConst *); + + static JSValue js_versionCompare(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + + static JSValue js_qmlTypeInfo(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_builtinExtensionNames(JSContext *ctx, JSValueConst, int, JSValueConst *); + static JSValue js_isSharedLibrary(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv); + + static JSValue js_getArchitecturesFromBinary(JSContext *ctx, JSValueConst, int, JSValueConst *); }; -QScriptValue UtilitiesExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_canonicalPlatform(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - Q_UNUSED(engine); - return context->throwError(Tr::tr("'Utilities' cannot be instantiated.")); + try { + const auto platform = getArgument<QString>(ctx, "Utilities.canonicalPlatform", argc, argv); + if (platform.isEmpty()) + return makeJsStringList(ctx, {}); + std::vector<QString> platforms = HostOsInfo::canonicalOSIdentifiers(platform); + return makeJsStringList(ctx, QStringList(std::move_iterator(platforms.begin()), + std::move_iterator(platforms.end()))); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue UtilitiesExtension::js_canonicalPlatform(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_canonicalTargetArchitecture(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - const QScriptValue value = context->argument(0); - if (value.isUndefined() || value.isNull()) - return value; - - if (context->argumentCount() == 1 && value.isString()) { - return engine->toScriptValue([&value] { - QStringList list; - for (const auto &s : HostOsInfo::canonicalOSIdentifiers(value.toString().toStdString())) - list.push_back(QString::fromStdString(s)); - return list; - }()); + try { + const auto arch = getArgument<QString>(ctx, "Utilities.canonicalTargetArchitecture", + argc, argv); + QString endianness, vendor, system, abi; + if (argc > 1) + endianness = fromArg<QString>(ctx, "Utilities.canonicalTargetArchitecture", 2, argv[1]); + if (argc > 2) + vendor = fromArg<QString>(ctx, "Utilities.canonicalTargetArchitecture", 3, argv[2]); + if (argc > 3) + system = fromArg<QString>(ctx, "Utilities.canonicalTargetArchitecture", 4, argv[3]); + if (argc > 4) + abi = fromArg<QString>(ctx, "Utilities.canonicalTargetArchitecture", 5, argv[4]); + return makeJsString(ctx, + canonicalTargetArchitecture(arch, endianness, vendor, system, abi)); + } catch (const QString &error) { + return throwError(ctx, error); } - - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("canonicalPlatform expects one argument of type string")); } -QScriptValue UtilitiesExtension::js_canonicalTargetArchitecture(QScriptContext *context, - QScriptEngine *engine) +void UtilitiesExtension::setupStaticMethods(JSContext *ctx, JSValue classObj) { - const QScriptValue arch = context->argument(0); - if (arch.isUndefined() || arch.isNull()) - return arch; - - QScriptValue endianness = context->argument(1); - if (endianness.isUndefined() || endianness.isNull()) - endianness = QString(); - const QScriptValue vendor = context->argument(2); - const QScriptValue system = context->argument(3); - const QScriptValue abi = context->argument(4); - - if (!arch.isString() || !endianness.isString() - || !vendor.isString() || !system.isString() || !abi.isString()) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("canonicalTargetArchitecture expects 1 to 5 arguments of type string")); - - return engine->toScriptValue(canonicalTargetArchitecture(arch.toString(), endianness.toString(), - vendor.toString(), - system.toString(), abi.toString())); + setupMethod(ctx, classObj, "canonicalArchitecture", + &UtilitiesExtension::js_canonicalArchitecture, 1); + setupMethod(ctx, classObj, "canonicalPlatform", + &UtilitiesExtension::js_canonicalPlatform, 1); + setupMethod(ctx, classObj, "canonicalTargetArchitecture", + &UtilitiesExtension::js_canonicalTargetArchitecture, 1); + setupMethod(ctx, classObj, "canonicalToolchain", + &UtilitiesExtension::js_canonicalToolchain, 1); + setupMethod(ctx, classObj, "cStringQuote", &UtilitiesExtension::js_cStringQuote, 1); + setupMethod(ctx, classObj, "getHash", &UtilitiesExtension::js_getHash, 1); + setupMethod(ctx, classObj, "getNativeSetting", + &UtilitiesExtension::js_getNativeSetting, 3); + setupMethod(ctx, classObj, "kernelVersion", &UtilitiesExtension::js_kernelVersion, 0); + setupMethod(ctx, classObj, "nativeSettingGroups", + &UtilitiesExtension::js_nativeSettingGroups, 1); + setupMethod(ctx, classObj, "rfc1034Identifier", + &UtilitiesExtension::js_rfc1034identifier, 1); + setupMethod(ctx, classObj, "smimeMessageContent", + &UtilitiesExtension::js_smimeMessageContent, 1); + setupMethod(ctx, classObj, "certificateInfo", &UtilitiesExtension::js_certificateInfo, 1); + setupMethod(ctx, classObj, "signingIdentities", + &UtilitiesExtension::js_signingIdentities, 1); + setupMethod(ctx, classObj, "msvcCompilerInfo", + &UtilitiesExtension::js_msvcCompilerInfo, 1); + setupMethod(ctx, classObj, "clangClCompilerInfo", + &UtilitiesExtension::js_clangClCompilerInfo, 1); + setupMethod(ctx, classObj, "installedMSVCs", &UtilitiesExtension::js_installedMSVCs, 1); + setupMethod(ctx, classObj, "installedClangCls", + &UtilitiesExtension::js_installedClangCls, 1); + setupMethod(ctx, classObj, "versionCompare", &UtilitiesExtension::js_versionCompare, 2); + setupMethod(ctx, classObj, "qmlTypeInfo", &UtilitiesExtension::js_qmlTypeInfo, 0); + setupMethod(ctx, classObj, "builtinExtensionNames", + &UtilitiesExtension::js_builtinExtensionNames, 0); + setupMethod(ctx, classObj, "isSharedLibrary", &UtilitiesExtension::js_isSharedLibrary, 1); + setupMethod(ctx, classObj, "getArchitecturesFromBinary", + &UtilitiesExtension::js_getArchitecturesFromBinary, 1); } -QScriptValue UtilitiesExtension::js_canonicalArchitecture(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_canonicalArchitecture(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - const QScriptValue value = context->argument(0); - if (value.isUndefined() || value.isNull()) - return value; - - if (context->argumentCount() == 1 && value.isString()) - return engine->toScriptValue(canonicalArchitecture(value.toString())); - - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("canonicalArchitecture expects one argument of type string")); + try { + const auto arch = getArgument<QString>(ctx, "Utilities.canonicalArchitecture", argc, argv); + if (arch.isEmpty()) + return makeJsString(ctx, {}); + return makeJsString(ctx, canonicalArchitecture(arch)); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue UtilitiesExtension::js_canonicalToolchain(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_canonicalToolchain(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - QStringList toolchain; - for (int i = 0; i < context->argumentCount(); ++i) - toolchain << context->argument(i).toString(); - return engine->toScriptValue(canonicalToolchain(toolchain)); + try { + QStringList toolchain; + for (int i = 0; i < argc; ++i) + toolchain << fromArg<QString>(ctx, "Utilities.canonicalToolchain", i + 1, argv[i]); + return makeJsStringList(ctx, canonicalToolchain(toolchain)); + } catch (const QString &error) { + return throwError(ctx, error); + } } // copied from src/corelib/tools/qtools_p.h -Q_DECL_CONSTEXPR inline char toHexUpper(uint value) Q_DECL_NOTHROW +Q_DECL_CONSTEXPR inline uchar toHexUpper(uint value) Q_DECL_NOTHROW { return "0123456789ABCDEF"[value & 0xF]; } @@ -323,74 +359,85 @@ static inline QString escapedString(const Char *begin, int length, bool isUnicod return out; } -QScriptValue UtilitiesExtension::js_cStringQuote(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_cStringQuote(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("cStringQuote expects 1 argument")); + try { + const auto str = getArgument<QString>(ctx, "Utilities.cStringQuote", argc, argv); + return makeJsString(ctx, escapedString( + reinterpret_cast<const ushort *>(str.constData()), str.size())); + } catch (const QString &error) { + return throwError(ctx, error); } - QString value = context->argument(0).toString(); - return engine->toScriptValue(escapedString(reinterpret_cast<const ushort *>(value.constData()), value.size())); } -QScriptValue UtilitiesExtension::js_getHash(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_getHash(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() < 1)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("getHash expects 1 argument")); + try { + const QByteArray input = getArgument<QString>(ctx, "Utilities.getHash", argc, argv) + .toLatin1(); + const QByteArray hash = QCryptographicHash::hash(input, QCryptographicHash::Sha1) + .toHex().left(16); + return makeJsString(ctx, QString::fromLatin1(hash)); + } catch (const QString &error) { + return throwError(ctx, error); } - const QByteArray input = context->argument(0).toString().toLatin1(); - const QByteArray hash - = QCryptographicHash::hash(input, QCryptographicHash::Sha1).toHex().left(16); - return engine->toScriptValue(QString::fromLatin1(hash)); } -QScriptValue UtilitiesExtension::js_getNativeSetting(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_getNativeSetting(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() < 1 || context->argumentCount() > 3)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("getNativeSetting expects between 1 and 3 arguments")); - } + try { + const auto path = getArgument<QString>(ctx, "Utilities.getNativeSetting", argc, argv); - QString key = context->argumentCount() > 1 ? context->argument(1).toString() : QString(); + QString key; + if (argc > 1) + key = fromArg<QString>(ctx, "Utilities.getNativeSetting", 2, argv[1]); - // We'll let empty string represent the default registry value - if (HostOsInfo::isWindowsHost() && key.isEmpty()) - key = StringConstants::dot(); + // QSettings will ignore the default value if an empty key is passed. + if (key.isEmpty()) { + key = HostOsInfo::isWindowsHost() ? StringConstants::dot() // default registry value + : QLatin1String("__dummy"); + } - QVariant defaultValue = context->argumentCount() > 2 ? context->argument(2).toVariant() : QVariant(); + QVariant defaultValue; + if (argc > 2) + defaultValue = fromArg<QVariant>(ctx, "Utilities.getNativeSetting", 3, argv[2]); - QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); - QVariant value = settings.value(key, defaultValue); - return value.isNull() ? engine->undefinedValue() : engine->toScriptValue(value); + const QSettings settings(path, QSettings::NativeFormat); + const QVariant v = settings.value(key, defaultValue); + return v.isNull() ? JS_UNDEFINED : makeJsVariant(ctx, v); + } catch (const QString &error) { + return throwError(ctx, error); + } } -QScriptValue UtilitiesExtension::js_kernelVersion(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_kernelVersion(JSContext *ctx, JSValueConst, int, JSValueConst *) { - Q_UNUSED(context); - return engine->toScriptValue(QSysInfo::kernelVersion()); + return makeJsString(ctx, QSysInfo::kernelVersion()); } -QScriptValue UtilitiesExtension::js_nativeSettingGroups(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_nativeSettingGroups(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() != 1)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("nativeSettingGroups expects 1 argument")); + try { + const auto path = getArgument<QString>(ctx, "Utilities.nativeSettingGroups", argc, argv); + QSettings settings(path, QSettings::NativeFormat); + return makeJsStringList(ctx, settings.childGroups()); + } catch (const QString &error) { + return throwError(ctx, error); } - - QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); - return engine->toScriptValue(settings.childGroups()); } -QScriptValue UtilitiesExtension::js_rfc1034identifier(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_rfc1034identifier(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("rfc1034Identifier expects 1 argument")); - const QString identifier = context->argument(0).toString(); - return engine->toScriptValue(HostOsInfo::rfc1034Identifier(identifier)); + try { + const auto identifier = getArgument<QString>(ctx, "Utilities.rfc1034identifier", argc, argv); + return makeJsString(ctx, HostOsInfo::rfc1034Identifier(identifier)); + } catch (const QString &error) { + return throwError(ctx, error); + } } /** @@ -403,67 +450,84 @@ QScriptValue UtilitiesExtension::js_rfc1034identifier(QScriptContext *context, * \note A provisioning profile is an S/MIME message whose contents are an XML property list, * so this method can be used to read such files. */ -QScriptValue UtilitiesExtension::js_smimeMessageContent(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_smimeMessageContent(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { #if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) - Q_UNUSED(engine); - return context->throwError(QScriptContext::UnknownError, - QStringLiteral("smimeMessageContent is not available on this platform")); + Q_UNUSED(argc) + Q_UNUSED(argv) + return throwError(ctx, QStringLiteral("smimeMessageContent is not available on this platform")); #else - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("smimeMessageContent expects 1 argument")); - - const QString filePath = context->argument(0).toString(); - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) - return engine->undefinedValue(); - - QByteArray content = smimeMessageContent(file.readAll()); - if (content.isEmpty()) - return engine->undefinedValue(); - return engine->toScriptValue(content); + try { + const QString filePath = getArgument<QString>(ctx, "Utilities.smimeMessageContent", + argc, argv); + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return JS_UNDEFINED; + + QByteArray content = smimeMessageContent(file.readAll()); + if (content.isEmpty()) + return JS_UNDEFINED; + return toJsValue(ctx, content); + } catch (const QString &error) { + return throwError(ctx, error); + } #endif } -QScriptValue UtilitiesExtension::js_certificateInfo(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_certificateInfo(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { #if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) - Q_UNUSED(engine); - return context->throwError(QScriptContext::UnknownError, - QStringLiteral("certificateInfo is not available on this platform")); + Q_UNUSED(argc) + Q_UNUSED(argv) + return throwError(ctx, QStringLiteral("certificateInfo is not available on this platform")); #else - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("certificateInfo expects 1 argument")); - return engine->toScriptValue(certificateInfo(context->argument(0).toVariant().toByteArray())); + try { + const QVariant arg = getArgument<QVariant>(ctx, "Utilities.certificateInfo", argc, argv); + return toJsValue(ctx, certificateInfo(arg.toByteArray())); + } catch (const QString &error) { + return throwError(ctx, error); + } #endif } // Rough command line equivalent: security find-identity -p codesigning -v -QScriptValue UtilitiesExtension::js_signingIdentities(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_signingIdentities(JSContext *ctx, JSValueConst, int, JSValueConst *) { #if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) - Q_UNUSED(engine); - return context->throwError(QScriptContext::UnknownError, - QStringLiteral("signingIdentities is not available on this platform")); + return throwError(ctx, QStringLiteral("signingIdentities is not available on this platform")); #else - Q_UNUSED(context); - return engine->toScriptValue(identitiesProperties()); + return makeJsVariantMap(ctx, identitiesProperties()); #endif } #ifdef Q_OS_WIN +// Try to detect the cross-architecture from the compiler path; prepend the host architecture +// if it is differ than the target architecture (e.g. something like x64_x86 and so forth). +static QString detectArchitecture(const QString &compilerFilePath, const QString &targetArch) +{ + const auto startIndex = compilerFilePath.lastIndexOf(QLatin1String("Host")); + if (startIndex == -1) + return targetArch; + const auto endIndex = compilerFilePath.indexOf(QLatin1Char('/'), startIndex); + if (endIndex == -1) + return targetArch; + const auto hostArch = compilerFilePath.mid(startIndex + 4, endIndex - startIndex - 4).toLower(); + if (hostArch.isEmpty() || (hostArch == targetArch)) + return targetArch; + return hostArch + QLatin1Char('_') + targetArch; +} + static std::pair<QVariantMap /*result*/, QString /*error*/> msvcCompilerInfoHelper( const QString &compilerFilePath, MSVC::CompilerLanguage language, const QString &vcvarsallPath, - const QString &arch) + const QString &arch, + const QString &sdkVersion) { - MSVC msvc(compilerFilePath, arch); + QString detailedArch = detectArchitecture(compilerFilePath, arch); + MSVC msvc(compilerFilePath, std::move(detailedArch), sdkVersion); VsEnvironmentDetector envdetector(vcvarsallPath); if (!envdetector.start(&msvc)) return { {}, QStringLiteral("Detecting the MSVC build environment failed: ") @@ -471,7 +535,8 @@ static std::pair<QVariantMap /*result*/, QString /*error*/> msvcCompilerInfoHelp try { QVariantMap envMap; - for (const QString &key : msvc.environment.keys()) + const auto keys = msvc.environment.keys(); + for (const QString &key : keys) envMap.insert(key, msvc.environment.value(key)); return { @@ -487,108 +552,164 @@ static std::pair<QVariantMap /*result*/, QString /*error*/> msvcCompilerInfoHelp } #endif -QScriptValue UtilitiesExtension::js_msvcCompilerInfo(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_msvcCompilerInfo(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { #ifndef Q_OS_WIN - Q_UNUSED(engine); - return context->throwError(QScriptContext::UnknownError, - QStringLiteral("msvcCompilerInfo is not available on this platform")); + Q_UNUSED(argc) + Q_UNUSED(argv) + return throwError(ctx, QStringLiteral("msvcCompilerInfo is not available on this platform")); #else - if (Q_UNLIKELY(context->argumentCount() < 2)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("msvcCompilerInfo expects 2 arguments")); + try { + const auto args = getArguments<QString, QString, QString>(ctx, "Utilities.msvcCompilerInfo", argc, argv); + const QString compilerFilePath = std::get<0>(args); + const QString compilerLanguage = std::get<1>(args); + const QString sdkVersion = std::get<2>(args); + MSVC::CompilerLanguage language; + if (compilerLanguage == QStringLiteral("c")) + language = MSVC::CLanguage; + else if (compilerLanguage == StringConstants::cppLang()) + language = MSVC::CPlusPlusLanguage; + else + throw Tr::tr("Utilities.msvcCompilerInfo expects \"c\" or \"cpp\" as its second argument"); + const auto result = msvcCompilerInfoHelper( + compilerFilePath, + language, + {}, + MSVC::architectureFromClPath(compilerFilePath), + sdkVersion); + if (result.first.isEmpty()) + throw result.second; + return toJsValue(ctx, result.first); + } catch (const QString &error) { + return throwError(ctx, error); + } +#endif +} - const QString compilerFilePath = context->argument(0).toString(); - const QString compilerLanguage = context->argument(1).toString(); +JSValue UtilitiesExtension::js_clangClCompilerInfo(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) +{ +#ifndef Q_OS_WIN + Q_UNUSED(argc) + Q_UNUSED(argv) + return throwError(ctx, QStringLiteral("clangClCompilerInfo is not available on this platform")); +#else + try { + const auto args = getArguments<QString, QString, QString, QString, QString>(ctx, "Utilities.clangClCompilerInfo", argc, argv); + const QString compilerFilePath = std::get<0>(args); + + // architecture cannot be empty as vcvarsall.bat requires at least 1 arg, so fallback + // to host architecture if none is present + QString arch = std::get<1>(args); + if (arch.isEmpty()) + arch = HostOsInfo::hostOSArchitecture(); + + const QString vcvarsallPath = std::get<2>(args); + const QString compilerLanguage = std::get<3>(args); + const QString sdkVersion = std::get<4>(args); MSVC::CompilerLanguage language; if (compilerLanguage == QStringLiteral("c")) language = MSVC::CLanguage; else if (compilerLanguage == StringConstants::cppLang()) language = MSVC::CPlusPlusLanguage; else - return context->throwError(QScriptContext::TypeError, - QStringLiteral("msvcCompilerInfo expects \"c\" or \"cpp\" as its second argument")); + throw Tr::tr("Utilities.clangClCompilerInfo expects \"c\" or \"cpp\" as its fourth argument"); const auto result = msvcCompilerInfoHelper( - compilerFilePath, language, {}, MSVC::architectureFromClPath(compilerFilePath)); + compilerFilePath, language, vcvarsallPath, arch, sdkVersion); if (result.first.isEmpty()) - return context->throwError(QScriptContext::UnknownError, result.second); - return engine->toScriptValue(result.first); + throw result.second; + return toJsValue(ctx, result.first); + } catch (const QString &error) { + return throwError(ctx, error); + } #endif } -QScriptValue UtilitiesExtension::js_clangClCompilerInfo(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_installedMSVCs(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { #ifndef Q_OS_WIN - Q_UNUSED(engine); - return context->throwError(QScriptContext::UnknownError, - QStringLiteral("clangClCompilerInfo is not available on this platform")); + return throwError(ctx, QStringLiteral("Utilities.installedMSVCs is not available on this platform")); + Q_UNUSED(argc) + Q_UNUSED(argv) #else - if (Q_UNLIKELY(context->argumentCount() < 4)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("clangClCompilerInfo expects 4 arguments")); - - const QString compilerFilePath = context->argument(0).toString(); - QString arch = context->argument(1).toString(); - QString vcvarsallPath = context->argument(2).toString(); - const QString compilerLanguage = context->argumentCount() > 3 - ? context->argument(3).toString() - : QString(); - MSVC::CompilerLanguage language; - if (compilerLanguage == QStringLiteral("c")) - language = MSVC::CLanguage; - else if (compilerLanguage == StringConstants::cppLang()) - language = MSVC::CPlusPlusLanguage; - else - return context->throwError(QScriptContext::TypeError, - QStringLiteral("clangClCompilerInfo expects \"c\" or \"cpp\" as its fourth argument")); - - const auto result = msvcCompilerInfoHelper( - compilerFilePath, language, vcvarsallPath, arch); - if (result.first.isEmpty()) - return context->throwError(QScriptContext::UnknownError, result.second); - return engine->toScriptValue(result.first); + try { + const QString hostArch = HostOsInfo::hostOSArchitecture(); + QString preferredArch = getArgument<QString>(ctx, "Utilities.installedMSVCs", argc, argv); + if (preferredArch.isEmpty()) + preferredArch = hostArch; + + DummyLogSink dummySink; + Logger dummyLogger(&dummySink); + auto msvcs = MSVC::installedCompilers(dummyLogger); + + const auto predicate = [&preferredArch, &hostArch](const MSVC &msvc) + { + auto archPair = MSVC::getHostTargetArchPair(msvc.architecture); + return archPair.first != hostArch || preferredArch != archPair.second; + }; + Internal::removeIf(msvcs, predicate); + QVariantList result; + for (const auto &msvc: msvcs) + result.append(msvc.toVariantMap()); + return makeJsVariantList(ctx, result); + } catch (const QString &error) { + return throwError(ctx, error); + } #endif } -QScriptValue UtilitiesExtension::js_versionCompare(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_installedClangCls(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv) { - if (context->argumentCount() == 2) { - const QScriptValue value1 = context->argument(0); - const QScriptValue value2 = context->argument(1); - if (value1.isString() && value2.isString()) { - const auto a = Version::fromString(value1.toString()); - const auto b = Version::fromString(value2.toString()); - return engine->toScriptValue(compare(a, b)); - } +#ifndef Q_OS_WIN + Q_UNUSED(argc) + Q_UNUSED(argv) + return throwError(ctx, QStringLiteral("Utilities.installedClangCls is not available on this platform")); +#else + try { + const QString path = getArgument<QString>(ctx, "Utilities.installedClangCls", argc, argv); + DummyLogSink dummySink; + Logger dummyLogger(&dummySink); + auto compilers = ClangClInfo::installedCompilers({path}, dummyLogger); + QVariantList result; + for (const auto &compiler: compilers) + result.append(compiler.toVariantMap()); + return makeJsVariantList(ctx, result); + } catch (const QString &error) { + return throwError(ctx, error); } +#endif +} - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("versionCompare expects two arguments of type string")); +JSValue UtilitiesExtension::js_versionCompare(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) +{ + try { + const auto args = getArguments<QString, QString>(ctx, "Utilities.versionCompare", + argc, argv); + const auto a = Version::fromString(std::get<0>(args), true); + const auto b = Version::fromString(std::get<1>(args), true); + return JS_NewInt32(ctx, compare(a, b)); + } catch (const QString &error) { return throwError(ctx, error); } } -QScriptValue UtilitiesExtension::js_qmlTypeInfo(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_qmlTypeInfo(JSContext *ctx, JSValueConst, int, JSValueConst *) { - Q_UNUSED(context); - return engine->toScriptValue(QString::fromStdString(qbs::LanguageInfo::qmlTypeInfo())); + return makeJsString(ctx, QString::fromStdString(qbs::LanguageInfo::qmlTypeInfo())); } -QScriptValue UtilitiesExtension::js_builtinExtensionNames(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_builtinExtensionNames(JSContext *ctx, JSValueConst, + int, JSValueConst *) { - Q_UNUSED(context); - return engine->toScriptValue(JsExtensions::extensionNames()); + return makeJsStringList(ctx, JsExtensions::extensionNames()); } -QScriptValue UtilitiesExtension::js_isSharedLibrary(QScriptContext *context, QScriptEngine *engine) +JSValue UtilitiesExtension::js_isSharedLibrary(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (context->argumentCount() == 1) { - const QScriptValue value = context->argument(0); - if (value.isString()) - return engine->toScriptValue(QLibrary::isLibrary(value.toString())); - } - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("isSharedLibrary expects one argument of type string")); + try { + const auto fileName = getArgument<QString>(ctx, "Utilities.isSharedLibrary", argc, argv); + return JS_NewBool(ctx, QLibrary::isLibrary(fileName)); + } catch (const QString &error) { return throwError(ctx, error); } } #ifdef __APPLE__ @@ -661,7 +782,7 @@ static QStringList detectMachOArchs(QIODevice *device) if (strncmp(ar_header, ARMAG, SARMAG) == 0) { while (!device->atEnd()) { static_assert(sizeof(ar_hdr) == 60, "sizeof(ar_hdr) != 60"); - ar_hdr header; + ar_hdr header{}; if (device->read(reinterpret_cast<char *>(&header), sizeof(ar_hdr)) != sizeof(ar_hdr)) return {}; @@ -711,7 +832,7 @@ static QStringList detectMachOArchs(QIODevice *device) pos = device->pos(); - fat_header fatheader; + fat_header fatheader{}; fatheader.magic = readInt(device, nullptr, false); if (fatheader.magic == FAT_MAGIC || fatheader.magic == FAT_CIGAM || fatheader.magic == FAT_MAGIC_64 || fatheader.magic == FAT_CIGAM_64) { @@ -724,7 +845,7 @@ static QStringList detectMachOArchs(QIODevice *device) QStringList archs; for (uint32_t n = 0; n < fatheader.nfat_arch; ++n) { - fat_arch_64 fatarch; + fat_arch_64 fatarch{}; static_assert(sizeof(fat_arch_64) == 32, "sizeof(fat_arch_64) != 32"); static_assert(sizeof(fat_arch) == 20, "sizeof(fat_arch) != 20"); const qint64 expectedBytes = is64bit ? sizeof(fat_arch_64) : sizeof(fat_arch); @@ -754,7 +875,7 @@ static QStringList detectMachOArchs(QIODevice *device) return {}; bool swap = false; - mach_header header; + mach_header header{}; header.magic = readInt(device, nullptr, swap); switch (header.magic) { case MH_CIGAM: @@ -786,84 +907,32 @@ static QStringList detectMachOArchs(QIODevice *device) } #endif -QScriptValue UtilitiesExtension::js_getArchitecturesFromBinary(QScriptContext *context, - QScriptEngine *engine) +JSValue UtilitiesExtension::js_getArchitecturesFromBinary(JSContext *ctx, JSValueConst, + int argc, JSValueConst *argv) { - if (context->argumentCount() != 1) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("getArchitecturesFromBinary expects exactly one argument")); - } - const QScriptValue arg = context->argument(0); - if (!arg.isString()) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("getArchitecturesFromBinary expects a string argument")); - } - QStringList archs; + try { + const auto path = getArgument<QString>(ctx, "Utilities.getArchitecturesFromBinary", + argc, argv); + QStringList archs; #ifdef __APPLE__ - QFile file(arg.toString()); - if (!file.open(QIODevice::ReadOnly)) { - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("Failed to open file '%1': %2") - .arg(file.fileName(), file.errorString())); - } - archs = detectMachOArchs(&file); + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + throw Tr::tr("In Utilities.getArchitecturesFromBinary: " + "Failed to open file '%1': %2") + .arg(file.fileName(), file.errorString()); + } + archs = detectMachOArchs(&file); +#else + Q_UNUSED(path) #endif // __APPLE__ - return engine->toScriptValue(archs); + return makeJsStringList(ctx, archs); + } catch (const QString &error) { return throwError(ctx, error); } } } // namespace Internal } // namespace qbs -void initializeJsExtensionUtilities(QScriptValue extensionObject) -{ - using namespace qbs::Internal; - QScriptEngine *engine = extensionObject.engine(); - QScriptValue environmentObj = engine->newQMetaObject(&UtilitiesExtension::staticMetaObject, - engine->newFunction(&UtilitiesExtension::js_ctor)); - environmentObj.setProperty(QStringLiteral("canonicalArchitecture"), - engine->newFunction(UtilitiesExtension::js_canonicalArchitecture, 1)); - environmentObj.setProperty(QStringLiteral("canonicalPlatform"), - engine->newFunction(UtilitiesExtension::js_canonicalPlatform, 1)); - environmentObj.setProperty(QStringLiteral("canonicalTargetArchitecture"), - engine->newFunction( - UtilitiesExtension::js_canonicalTargetArchitecture, 4)); - environmentObj.setProperty(QStringLiteral("canonicalToolchain"), - engine->newFunction(UtilitiesExtension::js_canonicalToolchain)); - environmentObj.setProperty(QStringLiteral("cStringQuote"), - engine->newFunction(UtilitiesExtension::js_cStringQuote, 1)); - environmentObj.setProperty(QStringLiteral("getHash"), - engine->newFunction(UtilitiesExtension::js_getHash, 1)); - environmentObj.setProperty(QStringLiteral("getNativeSetting"), - engine->newFunction(UtilitiesExtension::js_getNativeSetting, 3)); - environmentObj.setProperty(QStringLiteral("kernelVersion"), - engine->newFunction(UtilitiesExtension::js_kernelVersion, 0)); - environmentObj.setProperty(QStringLiteral("nativeSettingGroups"), - engine->newFunction(UtilitiesExtension::js_nativeSettingGroups, 1)); - environmentObj.setProperty(QStringLiteral("rfc1034Identifier"), - engine->newFunction(UtilitiesExtension::js_rfc1034identifier, 1)); - environmentObj.setProperty(QStringLiteral("smimeMessageContent"), - engine->newFunction(UtilitiesExtension::js_smimeMessageContent, 1)); - environmentObj.setProperty(QStringLiteral("certificateInfo"), - engine->newFunction(UtilitiesExtension::js_certificateInfo, 1)); - environmentObj.setProperty(QStringLiteral("signingIdentities"), - engine->newFunction(UtilitiesExtension::js_signingIdentities, 0)); - environmentObj.setProperty(QStringLiteral("msvcCompilerInfo"), - engine->newFunction(UtilitiesExtension::js_msvcCompilerInfo, 1)); - environmentObj.setProperty(QStringLiteral("clangClCompilerInfo"), - engine->newFunction(UtilitiesExtension::js_clangClCompilerInfo, 1)); - environmentObj.setProperty(QStringLiteral("versionCompare"), - engine->newFunction(UtilitiesExtension::js_versionCompare, 2)); - environmentObj.setProperty(QStringLiteral("qmlTypeInfo"), - engine->newFunction(UtilitiesExtension::js_qmlTypeInfo, 0)); - environmentObj.setProperty(QStringLiteral("builtinExtensionNames"), - engine->newFunction(UtilitiesExtension::js_builtinExtensionNames, 0)); - environmentObj.setProperty(QStringLiteral("isSharedLibrary"), - engine->newFunction(UtilitiesExtension::js_isSharedLibrary, 1)); - environmentObj.setProperty(QStringLiteral("getArchitecturesFromBinary"), - engine->newFunction(UtilitiesExtension::js_getArchitecturesFromBinary, 1)); - extensionObject.setProperty(QStringLiteral("Utilities"), environmentObj); -} - -Q_DECLARE_METATYPE(qbs::Internal::UtilitiesExtension *) - -#include "utilitiesextension.moc" +void initializeJsExtensionUtilities(qbs::Internal::ScriptEngine *engine, JSValue extensionObject) +{ + qbs::Internal::UtilitiesExtension::registerClass(engine, extensionObject); +} diff --git a/src/lib/corelib/language/artifactproperties.cpp b/src/lib/corelib/language/artifactproperties.cpp index e5b11f4d7..011e58d88 100644 --- a/src/lib/corelib/language/artifactproperties.cpp +++ b/src/lib/corelib/language/artifactproperties.cpp @@ -64,6 +64,7 @@ bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2) { return ap1.fileTagsFilter() == ap2.fileTagsFilter() && ap1.extraFileTags() == ap2.extraFileTags() + && !ap1.propertyMap() == !ap2.propertyMap() && *ap1.propertyMap() == *ap2.propertyMap(); } diff --git a/src/lib/corelib/language/asttools.cpp b/src/lib/corelib/language/asttools.cpp index 617c8b95b..0d0c234c4 100644 --- a/src/lib/corelib/language/asttools.cpp +++ b/src/lib/corelib/language/asttools.cpp @@ -61,13 +61,13 @@ QString textOf(const QString &source, QbsQmlJS::AST::Node *node) if (!node) return {}; return source.mid(node->firstSourceLocation().begin(), - node->lastSourceLocation().end() - node->firstSourceLocation().begin()); + int(node->lastSourceLocation().end() - node->firstSourceLocation().begin())); } -QStringRef textRefOf(const QString &source, QbsQmlJS::AST::Node *node) +QStringView textViewOf(const QString &source, QbsQmlJS::AST::Node *node) { const quint32 firstBegin = node->firstSourceLocation().begin(); - return source.midRef(firstBegin, node->lastSourceLocation().end() - firstBegin); + return QStringView(source).mid(firstBegin, int(node->lastSourceLocation().end() - firstBegin)); } } // namespace Internal diff --git a/src/lib/corelib/language/asttools.h b/src/lib/corelib/language/asttools.h index b4f5c4d98..086d59290 100644 --- a/src/lib/corelib/language/asttools.h +++ b/src/lib/corelib/language/asttools.h @@ -50,7 +50,7 @@ namespace Internal { QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid); CodeLocation toCodeLocation(const QString &filePath, const QbsQmlJS::AST::SourceLocation &location); QString textOf(const QString &source, QbsQmlJS::AST::Node *node); -QStringRef textRefOf(const QString &source, QbsQmlJS::AST::Node *node); +QStringView textViewOf(const QString &source, QbsQmlJS::AST::Node *node); } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index 68355df51..44dc8a326 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -125,7 +125,7 @@ ItemDeclaration BuiltinDeclarations::declarationsForType(ItemType type) const } ItemType BuiltinDeclarations::typeForName(const QString &typeName, - const CodeLocation location) const + const CodeLocation &location) const { const auto it = m_typeMap.constFind(typeName); if (it == m_typeMap.constEnd()) @@ -166,25 +166,28 @@ void BuiltinDeclarations::insert(const ItemDeclaration &decl) static PropertyDeclaration conditionProperty() { - return PropertyDeclaration(StringConstants::conditionProperty(), PropertyDeclaration::Boolean, - StringConstants::trueValue()); + return { + StringConstants::conditionProperty(), + PropertyDeclaration::Boolean, + StringConstants::trueValue()}; } static PropertyDeclaration alwaysRunProperty() { - return PropertyDeclaration(StringConstants::alwaysRunProperty(), PropertyDeclaration::Boolean, - StringConstants::falseValue()); + return { + StringConstants::alwaysRunProperty(), + PropertyDeclaration::Boolean, + StringConstants::falseValue()}; } static PropertyDeclaration nameProperty() { - return PropertyDeclaration(StringConstants::nameProperty(), PropertyDeclaration::String); + return {StringConstants::nameProperty(), PropertyDeclaration::String}; } static PropertyDeclaration buildDirProperty() { - return PropertyDeclaration(StringConstants::buildDirectoryProperty(), - PropertyDeclaration::Path); + return {StringConstants::buildDirectoryProperty(), PropertyDeclaration::Path}; } static PropertyDeclaration prepareScriptProperty() @@ -244,11 +247,11 @@ void BuiltinDeclarations::addDependsItem() PropertyDeclaration::StringList); item << PropertyDeclaration(StringConstants::limitToSubProjectProperty(), PropertyDeclaration::Boolean, StringConstants::falseValue()); - item << PropertyDeclaration(StringConstants::multiplexConfigurationIdsProperty(), - PropertyDeclaration::StringList, QString(), - PropertyDeclaration::ReadOnlyFlag); - item << PropertyDeclaration(StringConstants::enableFallbackProperty(), - PropertyDeclaration::Boolean, StringConstants::trueValue()); + item << PropertyDeclaration( + StringConstants::multiplexConfigurationIdsProperty(), + PropertyDeclaration::StringList, + QString(), + PropertyDeclaration::ReadOnlyFlag); insert(item); } @@ -258,7 +261,6 @@ void BuiltinDeclarations::addExportItem() item << PropertyDeclaration(StringConstants::prefixMappingProperty(), PropertyDeclaration::Variant); auto allowedChildTypes = item.allowedChildTypes(); - allowedChildTypes.insert(ItemType::Parameters); allowedChildTypes.insert(ItemType::Properties); item.setAllowedChildTypes(allowedChildTypes); insert(item); @@ -326,25 +328,28 @@ void BuiltinDeclarations::addModuleProviderItem() ItemDeclaration item(ItemType::ModuleProvider); item << nameProperty() << PropertyDeclaration(QStringLiteral("outputBaseDir"), PropertyDeclaration::String) + << PropertyDeclaration(StringConstants::isEagerProperty(), + PropertyDeclaration::Boolean, + StringConstants::trueValue()) + << PropertyDeclaration(StringConstants::moduleNameProperty(), PropertyDeclaration::String) << PropertyDeclaration(QStringLiteral("relativeSearchPaths"), PropertyDeclaration::StringList); + item.setAllowedChildTypes({ItemType::PropertyOptions, ItemType::Probe}); insert(item); } ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type) { ItemDeclaration item(type); - item.setAllowedChildTypes(ItemDeclaration::TypeNames() - << ItemType::Group - << ItemType::Depends - << ItemType::FileTagger - << ItemType::JobLimit - << ItemType::Rule - << ItemType::Parameter - << ItemType::Probe - << ItemType::PropertyOptions - << ItemType::Scanner); - item << nameProperty(); + item.setAllowedChildTypes({ItemType::Depends, ItemType::FileTagger, ItemType::Group, + ItemType::JobLimit, ItemType::Parameter, ItemType::Parameters, + ItemType::Probe, ItemType::PropertyOptions, + ItemType::Rule, ItemType::Scanner}); + PropertyDeclaration nameDecl = nameProperty(); + PropertyDeclaration::Flags nameFlags = nameDecl.flags(); + nameFlags |= PropertyDeclaration::ReadOnlyFlag; + nameDecl.setFlags(nameFlags); + item << nameDecl; item << conditionProperty(); PropertyDeclaration setupBuildEnvDecl(StringConstants::setupBuildEnvironmentProperty(), PropertyDeclaration::Variant, QString(), @@ -433,6 +438,8 @@ void BuiltinDeclarations::addProductItem() item << PropertyDeclaration(StringConstants::multiplexConfigurationIdProperty(), PropertyDeclaration::String, QString(), PropertyDeclaration::ReadOnlyFlag); + item << PropertyDeclaration(StringConstants::qbsModuleProviders(), + PropertyDeclaration::StringList); insert(item); } @@ -473,6 +480,8 @@ void BuiltinDeclarations::addProjectItem() item << PropertyDeclaration(StringConstants::qbsSearchPathsProperty(), PropertyDeclaration::StringList, QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::qbsModuleProviders(), + PropertyDeclaration::StringList); insert(item); } diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h index 988f9ab81..9d7aee982 100644 --- a/src/lib/corelib/language/builtindeclarations.h +++ b/src/lib/corelib/language/builtindeclarations.h @@ -62,7 +62,7 @@ public: QStringList allTypeNames() const; ItemDeclaration declarationsForType(ItemType type) const; ItemType typeForName(const QString &typeName, - const CodeLocation location = CodeLocation()) const; + const CodeLocation &location = CodeLocation()) const; QString nameForType(ItemType itemType) const; QStringList argumentNamesForScriptFunction(ItemType itemType, const QString &scriptName) const; diff --git a/src/lib/corelib/language/deprecationinfo.h b/src/lib/corelib/language/deprecationinfo.h index 220f99ad3..2f9bfc103 100644 --- a/src/lib/corelib/language/deprecationinfo.h +++ b/src/lib/corelib/language/deprecationinfo.h @@ -39,6 +39,11 @@ #ifndef QBS_DEPRECATIONINFO_H #define QBS_DEPRECATIONINFO_H +#include <api/languageinfo.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/deprecationwarningmode.h> +#include <tools/error.h> #include <tools/version.h> #include <QtCore/qstring.h> @@ -50,15 +55,54 @@ class DeprecationInfo { public: explicit DeprecationInfo(const Version &removalVersion, - const QString &additionalUserInfo = QString()) + QString additionalUserInfo = QString()) : m_removalVersion(removalVersion) - , m_additionalUserInfo(additionalUserInfo) + , m_additionalUserInfo(std::move(additionalUserInfo)) {} DeprecationInfo() = default; bool isValid() const { return m_removalVersion.isValid(); } Version removalVersion() const { return m_removalVersion; } - QString additionalUserInfo() const { return m_additionalUserInfo; } + + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, bool isItem, Logger &logger) const + { + if (!isValid()) + return {}; + const Version qbsVersion = LanguageInfo::qbsVersion(); + if (removalVersion() <= qbsVersion) { + const QString msgTemplate = isItem + ? Tr::tr("The item '%1' can no longer be used. It was removed in Qbs %2.") + : Tr::tr("The property '%1' can no longer be used. It was removed in Qbs %2."); + ErrorInfo error(msgTemplate.arg(name, removalVersion().toString()), loc); + if (!m_additionalUserInfo.isEmpty()) + error.append(m_additionalUserInfo); + return error; + } + const QString msgTemplate = isItem + ? Tr::tr("The item '%1' is deprecated and will be removed in Qbs %2.") + : Tr::tr("The property '%1' is deprecated and will be removed in Qbs %2."); + ErrorInfo error(msgTemplate.arg(name, removalVersion().toString()), loc); + if (!m_additionalUserInfo.isEmpty()) + error.append(m_additionalUserInfo); + switch (mode) { + case DeprecationWarningMode::Error: + return error; + case DeprecationWarningMode::On: + logger.printWarning(error); + break; + case DeprecationWarningMode::BeforeRemoval: { + const Version next(qbsVersion.majorVersion(), qbsVersion.minorVersion() + 1); + if (removalVersion() == next || removalVersion().minorVersion() == 0) + logger.printWarning(error); + break; + } + case DeprecationWarningMode::Off: + break; + } + + return {}; + } private: Version m_removalVersion; diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp index 5ea506d6a..084f2f4a9 100644 --- a/src/lib/corelib/language/evaluator.cpp +++ b/src/lib/corelib/language/evaluator.cpp @@ -39,18 +39,19 @@ #include "evaluator.h" -#include "evaluationdata.h" -#include "evaluatorscriptclass.h" #include "filecontext.h" #include "filetags.h" #include "item.h" #include "scriptengine.h" #include "value.h" +#include <quickjs.h> + #include <buildgraph/buildgraph.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/error.h> +#include <tools/fileinfo.h> #include <tools/scripttools.h> #include <tools/qbsassert.h> #include <tools/qttools.h> @@ -61,27 +62,63 @@ namespace qbs { namespace Internal { +class EvaluationData +{ +public: + Evaluator *evaluator = nullptr; + const Item *item = nullptr; + mutable QHash<QString, JSValue> valueCache; +}; + +static void convertToPropertyType_impl( + ScriptEngine *engine, const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, const Value *value, const CodeLocation &location, JSValue &v); +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj); +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); +static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); + +static bool debugProperties = false; + Evaluator::Evaluator(ScriptEngine *scriptEngine) : m_scriptEngine(scriptEngine) - , m_scriptClass(new EvaluatorScriptClass(scriptEngine)) + , m_scriptClass(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED, + getEvalPropertyNames, getEvalPropertySafe)) { + scriptEngine->registerEvaluator(this); } Evaluator::~Evaluator() { - for (const auto &data : qAsConst(m_scriptValueMap)) - delete attachedPointer<EvaluationData>(data); - delete m_scriptClass; + Set<JSValue> valuesToFree; + for (const auto &data : std::as_const(m_scriptValueMap)) { + const auto evalData = attachedPointer<EvaluationData>(data, m_scriptClass); + valuesToFree << data; + for (const JSValue cachedValue : evalData->valueCache) + JS_FreeValue(m_scriptEngine->context(), cachedValue); + evalData->item->removeObserver(this); + delete evalData; + } + for (const auto &scopes : std::as_const(m_fileContextScopesMap)) { + valuesToFree << scopes.fileScope; + valuesToFree << scopes.importScope; + } + for (const JSValue v : std::as_const(valuesToFree)) { + JS_FreeValue(m_scriptEngine->context(), v); + } + m_scriptEngine->unregisterEvaluator(this); } -QScriptValue Evaluator::property(const Item *item, const QString &name) +JSValue Evaluator::property(const Item *item, const QString &name) { - return scriptValue(item).property(name); + return getJsProperty(m_scriptEngine->context(), scriptValue(item), name); } -QScriptValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) +JSValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v; + JSValue v; evaluateProperty(&v, item, name, propertyWasSet); return v; } @@ -89,16 +126,19 @@ QScriptValue Evaluator::value(const Item *item, const QString &name, bool *prope bool Evaluator::boolValue(const Item *item, const QString &name, bool *propertyWasSet) { - return value(item, name, propertyWasSet).toBool(); + const ScopedJsValue sv(m_scriptEngine->context(), value(item, name, propertyWasSet)); + return JS_ToBool(m_scriptEngine->context(), sv); } int Evaluator::intValue(const Item *item, const QString &name, int defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toInt32(); + qint32 n; + JS_ToInt32(m_scriptEngine->context(), &n, v); + return n; } FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *propertySet) @@ -109,24 +149,27 @@ FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *p QString Evaluator::stringValue(const Item *item, const QString &name, const QString &defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toString(); + QString str = getJsString(m_scriptEngine->context(), v); + JS_FreeValue(m_scriptEngine->context(), v); + return str; } -static QStringList toStringList(const QScriptValue &scriptValue) +static QStringList toStringList(ScriptEngine *engine, const JSValue &scriptValue) { - if (scriptValue.isString()) { - return {scriptValue.toString()}; - } else if (scriptValue.isArray()) { + if (JS_IsString(scriptValue)) + return {getJsString(engine->context(), scriptValue)}; + if (JS_IsArray(engine->context(), scriptValue)) { QStringList lst; int i = 0; - forever { - QScriptValue elem = scriptValue.property(i++); - if (!elem.isValid()) + for (;;) { + JSValue elem = JS_GetPropertyUint32(engine->context(), scriptValue, i++); + if (JS_IsUndefined(elem)) break; - lst.push_back(elem.toString()); + lst.push_back(getJsString(engine->context(), elem)); + JS_FreeValue(engine->context(), elem); } return lst; } @@ -135,11 +178,29 @@ static QStringList toStringList(const QScriptValue &scriptValue) QStringList Evaluator::stringListValue(const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v = property(item, name); - handleEvaluationError(item, name, v); + const auto result = optionalStringListValue(item, name, propertyWasSet); + return result ? *result : QStringList(); +} + +std::optional<QStringList> Evaluator::optionalStringListValue( + const Item *item, const QString &name, bool *propertyWasSet) +{ + const ScopedJsValue v(m_scriptEngine->context(), property(item, name)); + handleEvaluationError(item, name); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - return toStringList(v); + if (JS_IsUndefined(v)) + return std::nullopt; + return toStringList(m_scriptEngine, v); +} + +QVariant Evaluator::variantValue(const Item *item, const QString &name, bool *propertySet) +{ + const ScopedJsValue jsValue(m_scriptEngine->context(), property(item, name)); + handleEvaluationError(item, name); + if (propertySet) + *propertySet = isNonDefaultValue(item, name); + return getJsVariant(m_scriptEngine->context(), jsValue); } bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const @@ -150,15 +211,15 @@ bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const } void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc, - QScriptValue &v) + JSValue &v) { - m_scriptClass->convertToPropertyType(decl, loc, v); + convertToPropertyType_impl(engine(), QString(), nullptr, decl, nullptr, loc, v); } -QScriptValue Evaluator::scriptValue(const Item *item) +JSValue Evaluator::scriptValue(const Item *item) { - QScriptValue &scriptValue = m_scriptValueMap[item]; - if (scriptValue.isObject()) { + JSValue &scriptValue = m_scriptValueMap[item]; + if (JS_IsObject(scriptValue)) { // already initialized return scriptValue; } @@ -166,111 +227,783 @@ QScriptValue Evaluator::scriptValue(const Item *item) const auto edata = new EvaluationData; edata->evaluator = this; edata->item = item; - edata->item->setObserver(this); + edata->item->addObserver(this); - scriptValue = m_scriptEngine->newObject(m_scriptClass); + scriptValue = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass); attachPointerTo(scriptValue, edata); return scriptValue; } -void Evaluator::onItemPropertyChanged(Item *item) -{ - auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item)); - if (data) - data->valueCache.clear(); -} - -void Evaluator::handleEvaluationError(const Item *item, const QString &name, - const QScriptValue &scriptValue) +void Evaluator::handleEvaluationError(const Item *item, const QString &name) { - throwOnEvaluationError(m_scriptEngine, scriptValue, [&item, &name] () { + throwOnEvaluationError(m_scriptEngine, [&item, &name] () { const ValueConstPtr &value = item->property(name); return value ? value->location() : CodeLocation(); }); } -void Evaluator::setPathPropertiesBaseDir(const QString &dirPath) -{ - m_scriptClass->setPathPropertiesBaseDir(dirPath); -} - -void Evaluator::clearPathPropertiesBaseDir() -{ - m_scriptClass->clearPathPropertiesBaseDir(); -} - -bool Evaluator::evaluateProperty(QScriptValue *result, const Item *item, const QString &name, +bool Evaluator::evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet) { *result = property(item, name); - handleEvaluationError(item, name, *result); + ScopedJsValue valMgr(m_scriptEngine->context(), *result); + handleEvaluationError(item, name); + valMgr.release(); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - return result->isValid() && !result->isUndefined(); + return !JS_IsUndefined(*result); } Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConstPtr &file) { FileContextScopes &result = m_fileContextScopesMap[file]; - if (!result.fileScope.isObject()) { + if (!JS_IsObject(result.fileScope)) { if (file->idScope()) result.fileScope = scriptValue(file->idScope()); else result.fileScope = m_scriptEngine->newObject(); - result.fileScope.setProperty(StringConstants::filePathGlobalVar(), file->filePath()); - result.fileScope.setProperty(StringConstants::pathGlobalVar(), file->dirPath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::filePathGlobalVar(), file->filePath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::pathGlobalVar(), file->dirPath()); } - if (!result.importScope.isObject()) { + if (!JS_IsObject(result.importScope)) { try { - result.importScope = m_scriptEngine->newObject(); - setupScriptEngineForFile(m_scriptEngine, file, result.importScope, - ObserveMode::Enabled); + ScopedJsValue importScope(m_scriptEngine->context(), m_scriptEngine->newObject()); + setupScriptEngineForFile(m_scriptEngine, file, importScope, ObserveMode::Enabled); + result.importScope = importScope.release(); } catch (const ErrorInfo &e) { - result.importScope = m_scriptEngine->currentContext()->throwError(e.toString()); + result.importScope = throwError(m_scriptEngine->context(), e.toString()); } } return result; } -void Evaluator::setCachingEnabled(bool enabled) +// This is the only function in this class that can be called from a thread that is not +// the evaluating one. For this reason, we do not clear the cache here, as that would +// incur enourmous synchronization overhead. Instead, we mark the item's cache as invalidated +// and do the actual clearing only at the very few places where the cache is actually accessed. +void Evaluator::invalidateCache(const Item *item) +{ + std::lock_guard lock(m_cacheInvalidationMutex); + m_invalidatedCaches << item; +} + +void Evaluator::clearCache(const Item *item) { - m_scriptClass->setValueCacheEnabled(enabled); + std::lock_guard lock(m_cacheInvalidationMutex); + if (const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item), + m_scriptEngine->dataWithPtrClass())) { + clearCache(*data); + m_invalidatedCaches.remove(data->item); + } } -PropertyDependencies Evaluator::propertyDependencies() const +void Evaluator::clearCacheIfInvalidated(EvaluationData &edata) { - return m_scriptClass->propertyDependencies(); + std::lock_guard lock(m_cacheInvalidationMutex); + if (const auto it = m_invalidatedCaches.find(edata.item); it != m_invalidatedCaches.end()) { + clearCache(edata); + m_invalidatedCaches.erase(it); + } } -void Evaluator::clearPropertyDependencies() +void Evaluator::clearCache(EvaluationData &edata) { - m_scriptClass->clearPropertyDependencies(); + for (const auto value : std::as_const(edata.valueCache)) + JS_FreeValue(m_scriptEngine->context(), value); + edata.valueCache.clear(); } -void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, +void throwOnEvaluationError(ScriptEngine *engine, const std::function<CodeLocation()> &provideFallbackCodeLocation) { - if (Q_LIKELY(!engine->hasErrorOrException(scriptValue))) + if (JsException ex = engine->checkAndClearException(provideFallbackCodeLocation())) + throw ex.toErrorInfo(); +} + +static void makeTypeError(ScriptEngine *engine, const ErrorInfo &error, JSValue &v) +{ + v = throwError(engine->context(), error.toString()); +} + +static void makeTypeError(ScriptEngine *engine, const PropertyDeclaration &decl, + const CodeLocation &location, JSValue &v) +{ + const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") + .arg(decl.name(), decl.typeString()), location); + makeTypeError(engine, error, v); +} + +static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) +{ + const VariantValuePtr v = item->variantProperty + (StringConstants::qbsSourceDirPropertyInternal()); + return v ? v->value().toString() : defaultValue; +} + +static void convertToPropertyType_impl(ScriptEngine *engine, + const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, + const Value *value, const CodeLocation &location, JSValue &v) +{ + JSContext * const ctx = engine->context(); + if (JS_IsUndefined(v) || JS_IsError(ctx, v) || JS_IsException(v)) return; - QString message; - QString filePath; - int line = -1; - const QScriptValue value = scriptValue.isError() ? scriptValue - : engine->uncaughtException(); - if (value.isError()) { - QScriptValue v = value.property(QStringLiteral("message")); - if (v.isString()) - message = v.toString(); - v = value.property(StringConstants::fileNameProperty()); - if (v.isString()) - filePath = v.toString(); - v = value.property(QStringLiteral("lineNumber")); - if (v.isNumber()) - line = v.toInt32(); - throw ErrorInfo(message, CodeLocation(filePath, line, -1, false)); + QString srcDir; + QString actualBaseDir; + const Item * const srcDirItem = value && value->scope() ? value->scope() : item; + if (item && !pathPropertiesBaseDir.isEmpty()) { + const VariantValueConstPtr itemSourceDir + = item->variantProperty(QStringLiteral("sourceDirectory")); + actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; + } + switch (decl.type()) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Boolean: + if (!JS_IsBool(v)) + v = JS_NewBool(ctx, JS_ToBool(ctx, v)); + break; + case PropertyDeclaration::Integer: + if (!JS_IsNumber(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::Path: + { + if (!JS_IsString(v)) { + makeTypeError(engine, decl, location, v); + break; + } + const QString srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir) + : pathPropertiesBaseDir; + if (!srcDir.isEmpty()) { + v = engine->toScriptValue(QDir::cleanPath(FileInfo::resolvePath(srcDir, + getJsString(ctx, v)))); + JS_FreeValue(ctx, v); + } + break; + } + case PropertyDeclaration::String: + if (!JS_IsString(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::PathList: + srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir) + : pathPropertiesBaseDir; + // Fall-through. + case PropertyDeclaration::StringList: + { + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + const quint32 c = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 i = 0; i < c; ++i) { + const ScopedJsValue elem(ctx, JS_GetPropertyUint32(ctx, v, i)); + if (JS_IsUndefined(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (JS_IsNull(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (!JS_IsString(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " + "string type.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (srcDir.isEmpty()) + continue; + const JSValue newElem = engine->toScriptValue( + QDir::cleanPath(FileInfo::resolvePath(srcDir, getJsString(ctx, elem)))); + JS_SetPropertyUint32(ctx, v, i, newElem); + } + break; + } + case PropertyDeclaration::VariantList: + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + break; + } +} + +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValue obj) +{ + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + if (!data) + return -1; + const Item::PropertyMap &map = data->item->properties(); + *plen = map.size(); + if (!map.isEmpty()) { + *ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab)); + JSPropertyEnum *entry = *ptab; + for (auto it = map.cbegin(); it != map.cend(); ++it, ++entry) { + entry->atom = JS_NewAtom(ctx, it.key().toUtf8().constData()); + entry->is_enumerable = 1; + } } else { - message = value.toString(); - throw ErrorInfo(message, provideFallbackCodeLocation()); + *ptab = nullptr; + } + return 0; +} + +class PropertyStackManager +{ +public: + PropertyStackManager(const Item *itemOfProperty, const QString &name, const Value *value, + std::stack<QualifiedId> &requestedProperties, + PropertyDependencies &propertyDependencies) + : m_requestedProperties(requestedProperties) + { + if (value->type() == Value::JSSourceValueType + && (itemOfProperty->type() == ItemType::ModuleInstance + || itemOfProperty->type() == ItemType::Module + || itemOfProperty->type() == ItemType::Export)) { + const VariantValueConstPtr varValue + = itemOfProperty->variantProperty(StringConstants::nameProperty()); + if (!varValue) + return; + m_stackUpdate = true; + const QualifiedId fullPropName + = QualifiedId::fromString(varValue->value().toString()) << name; + if (!requestedProperties.empty()) + propertyDependencies[fullPropName].insert(requestedProperties.top()); + m_requestedProperties.push(fullPropName); + } + } + + ~PropertyStackManager() + { + if (m_stackUpdate) + m_requestedProperties.pop(); + } + +private: + std::stack<QualifiedId> &m_requestedProperties; + bool m_stackUpdate = false; +}; + +class SVConverter : ValueHandler +{ + ScriptEngine * const engine; + const JSValue * const object; + Value * const valuePtr; + const Item * const itemOfProperty; + const QString * const propertyName; + const EvaluationData * const data; + JSValue * const result; + JSValueList scopeChain; + char pushedScopesCount; + +public: + + SVConverter(ScriptEngine *engine, const JSValue *obj, const ValuePtr &v, + const Item *_itemOfProperty, const QString *propertyName, + const EvaluationData *data, JSValue *result) + : engine(engine) + , object(obj) + , valuePtr(v.get()) + , itemOfProperty(_itemOfProperty) + , propertyName(propertyName) + , data(data) + , result(result) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + friend class AutoScopePopper; + + class AutoScopePopper + { + public: + AutoScopePopper(SVConverter *converter) + : m_converter(converter) + { + } + + ~AutoScopePopper() + { + m_converter->popScopes(); + } + + private: + SVConverter *m_converter; + }; + + void setupConvenienceProperty(const QString &conveniencePropertyName, JSValue *extraScope, + const JSValue &scriptValue) + { + if (!JS_IsObject(*extraScope)) + *extraScope = engine->newObject(); + const PropertyDeclaration::Type type + = itemOfProperty->propertyDeclaration(*propertyName).type(); + const bool isArray = type == PropertyDeclaration::StringList + || type == PropertyDeclaration::PathList + || type == PropertyDeclaration::Variant // TODO: Why? + || type == PropertyDeclaration::VariantList; + JSValue valueToSet = JS_DupValue(engine->context(), scriptValue); + if (isArray && JS_IsUndefined(valueToSet)) + valueToSet = engine->newArray(0, JsValueOwner::Caller); + setJsProperty(engine->context(), *extraScope, conveniencePropertyName, valueToSet); + } + + std::pair<JSValue, bool> createExtraScope(const JSSourceValue *value, Item *outerItem, + JSValue *outerScriptValue) + { + std::pair<JSValue, bool> result; + auto &extraScope = result.first; + result.second = true; + if (value->sourceUsesBase()) { + JSValue baseValue = JS_UNDEFINED; + if (value->baseValue()) { + SVConverter converter(engine, object, value->baseValue(), itemOfProperty, + propertyName, data, &baseValue); + converter.start(); + } + setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); + } + if (value->sourceUsesOuter()) { + JSValue v = JS_UNDEFINED; + bool doSetup = false; + if (outerItem) { + v = data->evaluator->property(outerItem, *propertyName); + if (JsException ex = engine->checkAndClearException({})) { + extraScope = engine->throwError(ex.toErrorInfo().toString()); + result.second = false; + return result; + } + doSetup = true; + JS_FreeValue(engine->context(), v); + } else if (outerScriptValue) { + doSetup = true; + v = *outerScriptValue; + } + if (doSetup) + setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); + } + if (value->sourceUsesOriginal()) { + JSValue originalJs = JS_UNDEFINED; + ScopedJsValue originalMgr(engine->context(), JS_UNDEFINED); + if (data->item->propertyDeclaration(*propertyName).isScalar()) { + const Item *item = itemOfProperty; + + if (item->type() != ItemType::ModuleInstance + && item->type() != ItemType::ModuleInstancePlaceholder) { + const QString errorMessage = Tr::tr("The special value 'original' can only " + "be used with module properties."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + if (!value->scope()) { + const QString errorMessage = Tr::tr("The special value 'original' cannot " + "be used on the right-hand side of a property declaration."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + ValuePtr original; + for (const ValuePtr &v : value->candidates()) { + if (!v->scope()) { + original = v; + break; + } + } + + // This can happen when resolving shadow products. The error will be ignored + // in that case. + if (!original) { + const QString errorMessage = Tr::tr("Error setting up 'original'."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + SVConverter converter(engine, object, original, item, propertyName, data, + &originalJs); + converter.start(); + } else { + originalJs = engine->newArray(0, JsValueOwner::Caller); + originalMgr.setValue(originalJs); + } + setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalJs); + } + return result; + } + + void pushScope(const JSValue &scope) + { + if (JS_IsObject(scope)) { + scopeChain << scope; + ++pushedScopesCount; + } + } + + void pushScopeRecursively(const Item *scope) + { + if (scope) { + pushScopeRecursively(scope->scope()); + pushScope(data->evaluator->scriptValue(scope)); + } + } + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scopeChain.pop_back(); + } + + void handle(JSSourceValue *value) override + { + JSValue outerScriptValue = JS_UNDEFINED; + for (const JSSourceValue::Alternative &alternative : value->alternatives()) { + if (alternative.value->sourceUsesOuter() + && !data->item->outerItem() + && JS_IsUndefined(outerScriptValue)) { + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); + if (sver.hasError) { + *result = sver.scriptValue; + return; + } + outerScriptValue = sver.scriptValue; + } + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), + data->item->outerItem(), + &alternative, + value, &outerScriptValue); + if (!sver.tryNextAlternative || sver.hasError) { + *result = sver.scriptValue; + return; + } + } + *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; + } + + struct JSSourceValueEvaluationResult + { + JSValue scriptValue = JS_UNDEFINED; + bool tryNextAlternative = true; + bool hasError = false; + }; + + JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, + const JSSourceValue::Alternative *alternative = nullptr, + JSSourceValue *elseCaseValue = nullptr, JSValue *outerScriptValue = nullptr) + { + JSSourceValueEvaluationResult result; + QBS_ASSERT(!alternative || value == alternative->value.get(), return result); + AutoScopePopper autoScopePopper(this); + auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); + if (!maybeExtraScope.second) { + result.scriptValue = maybeExtraScope.first; + result.hasError = true; + return result; + } + const ScopedJsValue extraScopeMgr(engine->context(), maybeExtraScope.first); + const Evaluator::FileContextScopes fileCtxScopes + = data->evaluator->fileContextScopes(value->file()); + if (JsException ex = engine->checkAndClearException({})) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + result.hasError = true; + return result; + } + pushScope(fileCtxScopes.fileScope); + pushItemScopes(data->item); + if ((itemOfProperty->type() != ItemType::ModuleInstance + && itemOfProperty->type() != ItemType::ModuleInstancePlaceholder) || !value->scope()) { + pushScope(*object); + } + pushScopeRecursively(value->scope()); + pushScope(maybeExtraScope.first); + pushScope(fileCtxScopes.importScope); + if (alternative) { + ScopedJsValue sv(engine->context(), engine->evaluate(JsValueOwner::Caller, + alternative->condition.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException(alternative->condition.location)) { + + // This handles cases like the following: + // Depends { name: "cpp" } + // Properties { + // condition: qbs.targetOS.contains("darwin") + // bundle.isBundle: false + // } + // On non-Darwin platforms, bundle never gets instantiated, and thus bundle.isBundle + // has no scope, so the qbs item in the condition is not found. + // TODO: Ideally, we would never evaluate values in placeholder items, but + // there are currently several contexts where we do that, e.g. Export + // and Group items. Perhaps change that, or try to collect all such + // exceptions and don't try to evaluate other cases. + if (itemOfProperty->type() == ItemType::ModuleInstancePlaceholder) { + result.scriptValue = JS_UNDEFINED; + result.tryNextAlternative = false; + return result; + } + + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + //result.scriptValue = ex.takeValue(); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) { + // The condition is true. Continue evaluating the value. + result.tryNextAlternative = false; + } else { + // The condition is false. Try the next alternative or the else value. + result.tryNextAlternative = true; + return result; + } + sv.reset(engine->evaluate(JsValueOwner::Caller, + alternative->overrideListProperties.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException( + alternative->overrideListProperties.location)) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) + elseCaseValue->setIsExclusiveListValue(); + } + result.scriptValue = engine->evaluate(JsValueOwner::ScriptEngine, + value->sourceCodeForEvaluation(), value->file()->filePath(), value->line(), + scopeChain); + return result; + } + + void handle(ItemValue *value) override + { + *result = data->evaluator->scriptValue(value->item()); + if (JS_IsUninitialized(*result)) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) override + { + *result = engine->toScriptValue(variantValue->value(), variantValue->id()); + engine->takeOwnership(*result); + } +}; + +static void convertToPropertyType(ScriptEngine *engine, const Item *item, + const PropertyDeclaration& decl, const Value *value, JSValue &v) +{ + if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) { + v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237 + return; + } + convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl, + value, value->location(), v); +} + +static QString resultToString(JSContext *ctx, const JSValue &scriptValue) +{ + if (JS_IsUndefined(scriptValue)) + return QLatin1String("undefined"); + if (JS_IsArray(ctx, scriptValue)) + return getJsStringList(ctx, scriptValue).join(QLatin1Char(',')); + if (JS_IsObject(scriptValue)) { + return QStringLiteral("[Object: ") + + QString::number(jsObjectId(scriptValue)) + QLatin1Char(']'); + } + return getJsVariant(ctx, scriptValue).toString(); +} + +static void collectValuesFromNextChain( + ScriptEngine *engine, JSValue obj, const ValuePtr &value, const Item *itemOfProperty, const QString &name, + const EvaluationData *data, JSValue *result) +{ + JSValueList lst; + for (ValuePtr next = value; next; next = next->next()) { + JSValue v = JS_UNDEFINED; + SVConverter svc(engine, &obj, next, itemOfProperty, &name, data, &v); + svc.start(); + if (JsException ex = engine->checkAndClearException({})) { + const ScopedJsValueList l(engine->context(), lst); + *result = engine->throwError(ex.toErrorInfo().toString()); + return; + } + if (JS_IsUndefined(v)) + continue; + const PropertyDeclaration decl = data->item->propertyDeclaration(name); + convertToPropertyType(engine, data->item, decl, next.get(), v); + lst.push_back(JS_DupValue(engine->context(), v)); + if (next->type() == Value::JSSourceValueType + && std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) { + // TODO: Why on earth do we keep the last _2_ elements? + auto keepIt = lst.rbegin(); + for (int i = 0; i < 2 && keepIt != lst.rend(); ++i) + ++keepIt; + for (auto it = lst.begin(); it < keepIt.base(); ++it) + JS_FreeValue(engine->context(), *it); + lst.erase(lst.begin(), keepIt.base()); + break; + } + } + + *result = engine->newArray(int(lst.size()), JsValueOwner::ScriptEngine); + quint32 k = 0; + JSContext * const ctx = engine->context(); + for (const JSValue &v : std::as_const(lst)) { + QBS_ASSERT(!JS_IsError(ctx, v), continue); + if (JS_IsArray(ctx, v)) { + const quint32 vlen = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 j = 0; j < vlen; ++j) + JS_SetPropertyUint32(ctx, *result, k++, JS_GetPropertyUint32(ctx, v, j)); + JS_FreeValue(ctx, v); + } else { + JS_SetPropertyUint32(ctx, *result, k++, v); + } + } + setJsProperty(ctx, *result, StringConstants::lengthProperty(), JS_NewInt32(ctx, k)); +} + +struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; }; +static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item, + const QString &name, EvaluationData *data) +{ + Evaluator * const evaluator = data->evaluator; + const bool isModuleInstance = item->type() == ItemType::ModuleInstance + || item->type() == ItemType::ModuleInstancePlaceholder; + for (; item; item = item->prototype()) { + if (isModuleInstance + && (item->type() == ItemType::Module || item->type() == ItemType::Export)) { + break; // There's no property in a prototype that's not also in the instance. + } + const ValuePtr value = item->ownProperty(name); + if (!value) + continue; + const Item * const itemOfProperty = item; // The item that owns the property. + PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), + evaluator->requestedProperties(), + evaluator->propertyDependencies()); + JSValue result; + if (evaluator->cachingEnabled()) { + data->evaluator->clearCacheIfInvalidated(*data); + const auto result = data->valueCache.constFind(name); + if (result != data->valueCache.constEnd()) { + if (debugProperties) + qDebug() << "[SC] cache hit " << name << ": " + << resultToString(engine->context(), *result); + return {*result, true}; + } + } + + if (value->next()) { + collectValuesFromNextChain(engine, obj, value, itemOfProperty, name, data, &result); + } else { + SVConverter converter(engine, &obj, value, itemOfProperty, &name, data, &result); + converter.start(); + const PropertyDeclaration decl = data->item->propertyDeclaration(name); + convertToPropertyType(engine, data->item, decl, value.get(), result); + } + + if (debugProperties) + qDebug() << "[SC] cache miss " << name << ": " + << resultToString(engine->context(), result); + if (evaluator->cachingEnabled()) { + data->evaluator->clearCacheIfInvalidated(*data); + const auto it = data->valueCache.find(name); + if (it != data->valueCache.end()) { + JS_FreeValue(engine->context(), it.value()); + it.value() = JS_DupValue(engine->context(), result); + } else { + data->valueCache.insert(name, JS_DupValue(engine->context(), result)); + } + } + return {result, true}; + } + return {JS_UNDEFINED, false}; +} + +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop) +{ + if (desc) { + desc->getter = desc->setter = desc->value = JS_UNDEFINED; + desc->flags = JS_PROP_ENUMERABLE; + } + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + const QString name = getJsString(ctx, prop); + if (debugProperties) + qDebug() << "[SC] queryProperty " << jsObjectId(obj) << " " << name; + + if (name == QStringLiteral("parent")) { + if (desc) { + Item * const parent = data->item->parent(); + desc->value = parent + ? JS_DupValue(ctx, data->evaluator->scriptValue(data->item->parent())) + : JS_UNDEFINED; + } + return 1; + } + + if (!data) { + if (debugProperties) + qDebug() << "[SC] queryProperty: no data attached"; + engine->setLastLookupStatus(false); + return -1; + } + + EvalResult result = getEvalProperty(engine, obj, data->item, name, data); + if (!result.found && data->item->parent()) { + if (debugProperties) + qDebug() << "[SC] queryProperty: query parent"; + const Item * const parentItem = data->item->parent(); + result = getEvalProperty(engine, evaluator->scriptValue(parentItem), parentItem, + name, data); + } + if (result.found) { + if (desc) + desc->value = JS_DupValue(ctx, result.v); + engine->setLastLookupStatus(true); + return 1; + } + + if (debugProperties) + qDebug() << "[SC] queryProperty: no such property"; + engine->setLastLookupStatus(false); + return 0; +} + +static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop) +{ + try { + return getEvalProperty(ctx, desc, obj, prop); + } catch (const ErrorInfo &e) { + ScopedJsValue error(ctx, throwError(ctx, e.toString())); + return -1; } } diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h index f8535d0d7..d86e08eb1 100644 --- a/src/lib/corelib/language/evaluator.h +++ b/src/lib/corelib/language/evaluator.h @@ -44,15 +44,18 @@ #include "itemobserver.h" #include "qualifiedid.h" -#include <QtCore/qhash.h> +#include <quickjs.h> -#include <QtScript/qscriptvalue.h> +#include <QtCore/qhash.h> #include <functional> +#include <mutex> +#include <optional> +#include <stack> namespace qbs { namespace Internal { -class EvaluatorScriptClass; +class EvaluationData; class FileTags; class Logger; class PropertyDeclaration; @@ -67,9 +70,10 @@ public: ~Evaluator() override; ScriptEngine *engine() const { return m_scriptEngine; } - QScriptValue property(const Item *item, const QString &name); + JSClassID classId() const { return m_scriptClass; } + JSValue property(const Item *item, const QString &name); - QScriptValue value(const Item *item, const QString &name, bool *propertySet = nullptr); + JSValue value(const Item *item, const QString &name, bool *propertySet = nullptr); bool boolValue(const Item *item, const QString &name, bool *propertyWasSet = nullptr); int intValue(const Item *item, const QString &name, int defaultValue = 0, bool *propertyWasSet = nullptr); @@ -78,55 +82,79 @@ public: const QString &defaultValue = QString(), bool *propertyWasSet = nullptr); QStringList stringListValue(const Item *item, const QString &name, bool *propertyWasSet = nullptr); + std::optional<QStringList> optionalStringListValue(const Item *item, const QString &name, + bool *propertyWasSet = nullptr); + + QVariant variantValue(const Item *item, const QString &name, bool *propertySet = nullptr); void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, - QScriptValue &v); + JSValue &v); - QScriptValue scriptValue(const Item *item); + JSValue scriptValue(const Item *item); struct FileContextScopes { - QScriptValue fileScope; - QScriptValue importScope; + JSValue fileScope = JS_UNDEFINED; + JSValue importScope = JS_UNDEFINED; }; FileContextScopes fileContextScopes(const FileContextConstPtr &file); - void setCachingEnabled(bool enabled); + void setCachingEnabled(bool enabled) { m_valueCacheEnabled = enabled; } + bool cachingEnabled() const { return m_valueCacheEnabled; } + void clearCache(const Item *item); + void invalidateCache(const Item *item); + void clearCacheIfInvalidated(EvaluationData &edata); - PropertyDependencies propertyDependencies() const; - void clearPropertyDependencies(); + PropertyDependencies &propertyDependencies() { return m_propertyDependencies; } + void clearPropertyDependencies() { m_propertyDependencies.clear(); } - void handleEvaluationError(const Item *item, const QString &name, - const QScriptValue &scriptValue); + std::stack<QualifiedId> &requestedProperties() { return m_requestedProperties; } - void setPathPropertiesBaseDir(const QString &dirPath); - void clearPathPropertiesBaseDir(); + void handleEvaluationError(const Item *item, const QString &name); + + QString pathPropertiesBaseDir() const { return m_pathPropertiesBaseDir; } + void setPathPropertiesBaseDir(const QString &dirPath) { m_pathPropertiesBaseDir = dirPath; } + void clearPathPropertiesBaseDir() { m_pathPropertiesBaseDir.clear(); } bool isNonDefaultValue(const Item *item, const QString &name) const; private: - void onItemPropertyChanged(Item *item) override; - bool evaluateProperty(QScriptValue *result, const Item *item, const QString &name, + void onItemPropertyChanged(Item *item) override { invalidateCache(item); } + bool evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet); + void clearCache(EvaluationData &edata); - ScriptEngine *m_scriptEngine; - EvaluatorScriptClass *m_scriptClass; - mutable QHash<const Item *, QScriptValue> m_scriptValueMap; + ScriptEngine * const m_scriptEngine; + const JSClassID m_scriptClass; + mutable QHash<const Item *, JSValue> m_scriptValueMap; mutable QHash<FileContextConstPtr, FileContextScopes> m_fileContextScopesMap; + QString m_pathPropertiesBaseDir; + PropertyDependencies m_propertyDependencies; + std::stack<QualifiedId> m_requestedProperties; + std::mutex m_cacheInvalidationMutex; + Set<const Item *> m_invalidatedCaches; + bool m_valueCacheEnabled = false; }; -void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, +void throwOnEvaluationError(ScriptEngine *engine, const std::function<CodeLocation()> &provideFallbackCodeLocation); class EvalCacheEnabler { public: - EvalCacheEnabler(Evaluator *evaluator) : m_evaluator(evaluator) + EvalCacheEnabler(Evaluator *evaluator, const QString &baseDir) : m_evaluator(evaluator) { m_evaluator->setCachingEnabled(true); + m_evaluator->setPathPropertiesBaseDir(baseDir); } - ~EvalCacheEnabler() { m_evaluator->setCachingEnabled(false); } + ~EvalCacheEnabler() { reset(); } + + void reset() + { + m_evaluator->setCachingEnabled(false); + m_evaluator->clearPathPropertiesBaseDir(); + } private: Evaluator * const m_evaluator; diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp deleted file mode 100644 index 375954133..000000000 --- a/src/lib/corelib/language/evaluatorscriptclass.cpp +++ /dev/null @@ -1,776 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "evaluatorscriptclass.h" - -#include "evaluationdata.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "scriptengine.h" -#include "propertydeclaration.h" -#include "value.h" -#include <logging/translator.h> -#include <tools/fileinfo.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/scripttools.h> -#include <tools/stringconstants.h> - -#include <QtCore/qbytearray.h> -#include <QtCore/qdebug.h> -#include <QtCore/qsettings.h> - -#include <QtScript/qscriptclasspropertyiterator.h> -#include <QtScript/qscriptstring.h> -#include <QtScript/qscriptvalue.h> - -#include <utility> - -namespace qbs { -namespace Internal { - -class SVConverter : ValueHandler -{ - EvaluatorScriptClass * const scriptClass; - ScriptEngine * const engine; - QScriptContext * const scriptContext; - const QScriptValue * const object; - Value * const valuePtr; - const Item * const itemOfProperty; - const QScriptString * const propertyName; - const EvaluationData * const data; - QScriptValue * const result; - char pushedScopesCount; - -public: - - SVConverter(EvaluatorScriptClass *esc, const QScriptValue *obj, const ValuePtr &v, - const Item *_itemOfProperty, const QScriptString *propertyName, const EvaluationData *data, - QScriptValue *result) - : scriptClass(esc) - , engine(static_cast<ScriptEngine *>(esc->engine())) - , scriptContext(esc->engine()->currentContext()) - , object(obj) - , valuePtr(v.get()) - , itemOfProperty(_itemOfProperty) - , propertyName(propertyName) - , data(data) - , result(result) - , pushedScopesCount(0) - { - } - - void start() - { - valuePtr->apply(this); - } - -private: - friend class AutoScopePopper; - - class AutoScopePopper - { - public: - AutoScopePopper(SVConverter *converter) - : m_converter(converter) - { - } - - ~AutoScopePopper() - { - m_converter->popScopes(); - } - - private: - SVConverter *m_converter; - }; - - void setupConvenienceProperty(const QString &conveniencePropertyName, QScriptValue *extraScope, - const QScriptValue &scriptValue) - { - if (!extraScope->isObject()) - *extraScope = engine->newObject(); - const PropertyDeclaration::Type type - = itemOfProperty->propertyDeclaration(propertyName->toString()).type(); - const bool isArray = type == PropertyDeclaration::StringList - || type == PropertyDeclaration::PathList - || type == PropertyDeclaration::Variant // TODO: Why? - || type == PropertyDeclaration::VariantList; - QScriptValue valueToSet = scriptValue; - if (isArray) { - if (!valueToSet.isValid() || valueToSet.isUndefined()) - valueToSet = engine->newArray(); - } else if (!valueToSet.isValid()) { - valueToSet = engine->undefinedValue(); - } - extraScope->setProperty(conveniencePropertyName, valueToSet); - } - - std::pair<QScriptValue, bool> createExtraScope(const JSSourceValue *value, Item *outerItem, - QScriptValue *outerScriptValue) - { - std::pair<QScriptValue, bool> result; - auto &extraScope = result.first; - result.second = true; - if (value->sourceUsesBase()) { - QScriptValue baseValue; - if (value->baseValue()) { - SVConverter converter(scriptClass, object, value->baseValue(), itemOfProperty, - propertyName, data, &baseValue); - converter.start(); - } - setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); - } - if (value->sourceUsesOuter()) { - QScriptValue v; - if (outerItem) { - v = data->evaluator->property(outerItem, *propertyName); - if (engine->hasErrorOrException(v)) { - extraScope = engine->lastErrorValue(v); - result.second = false; - return result; - } - } else if (outerScriptValue) { - v = *outerScriptValue; - } - if (v.isValid()) - setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); - } - if (value->sourceUsesOriginal()) { - QScriptValue originalValue; - if (data->item->propertyDeclaration(propertyName->toString()).isScalar()) { - const Item *item = itemOfProperty; - if (item->type() == ItemType::Module || item->type() == ItemType::Export) { - const QString errorMessage = Tr::tr("The special value 'original' cannot " - "be used on the right-hand side of a property declaration."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - // TODO: Provide a dedicated item type for not-yet-instantiated things that - // look like module instances in the AST visitor. - if (item->type() == ItemType::ModuleInstance - && !item->hasProperty(StringConstants::presentProperty())) { - const QString errorMessage = Tr::tr("Trying to assign property '%1' " - "on something that is not a module.").arg(propertyName->toString()); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - while (item->type() == ItemType::ModuleInstance) - item = item->prototype(); - if (item->type() != ItemType::Module && item->type() != ItemType::Export) { - const QString errorMessage = Tr::tr("The special value 'original' can only " - "be used with module properties."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - const ValuePtr v = item->property(*propertyName); - - // This can happen when resolving shadow products. The error will be ignored - // in that case. - if (!v) { - const QString errorMessage = Tr::tr("Error setting up 'original'."); - extraScope = engine->currentContext()->throwError(errorMessage); - result.second = false; - return result; - } - - SVConverter converter(scriptClass, object, v, item, - propertyName, data, &originalValue); - converter.start(); - } else { - originalValue = engine->newArray(0); - } - setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue); - } - return result; - } - - 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) override - { - QScriptValue outerScriptValue; - for (const JSSourceValue::Alternative &alternative : value->alternatives()) { - if (alternative.value->sourceUsesOuter() - && !data->item->outerItem() - && !outerScriptValue.isValid()) { - JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); - if (sver.hasError) { - *result = sver.scriptValue; - return; - } - outerScriptValue = sver.scriptValue; - } - JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), - data->item->outerItem(), - &alternative, - value, &outerScriptValue); - if (!sver.tryNextAlternative || sver.hasError) { - *result = sver.scriptValue; - return; - } - } - *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; - } - - struct JSSourceValueEvaluationResult - { - QScriptValue scriptValue; - bool tryNextAlternative = true; - bool hasError = false; - }; - - void injectErrorLocation(QScriptValue &sv, const CodeLocation &loc) - { - if (sv.isError() && !engine->lastErrorLocation(sv).isValid()) - sv = engine->currentContext()->throwError(engine->lastError(sv, loc).toString()); - } - - JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, - const JSSourceValue::Alternative *alternative = nullptr, - JSSourceValue *elseCaseValue = nullptr, QScriptValue *outerScriptValue = nullptr) - { - JSSourceValueEvaluationResult result; - QBS_ASSERT(!alternative || value == alternative->value.get(), return result); - AutoScopePopper autoScopePopper(this); - auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); - if (!maybeExtraScope.second) { - result.scriptValue = maybeExtraScope.first; - result.hasError = true; - return result; - } - const Evaluator::FileContextScopes fileCtxScopes - = data->evaluator->fileContextScopes(value->file()); - if (fileCtxScopes.importScope.isError()) { - result.scriptValue = fileCtxScopes.importScope; - result.hasError = true; - return result; - } - pushScope(fileCtxScopes.fileScope); - pushItemScopes(data->item); - if (itemOfProperty->type() != ItemType::ModuleInstance) { - // Own properties of module instances must not have the instance itself in the scope. - pushScope(*object); - } - if (value->definingItem()) - pushItemScopes(value->definingItem()); - pushScope(maybeExtraScope.first); - pushScope(fileCtxScopes.importScope); - if (alternative) { - QScriptValue sv = engine->evaluate(alternative->condition.value); - if (engine->hasErrorOrException(sv)) { - result.scriptValue = sv; - result.hasError = true; - injectErrorLocation(result.scriptValue, alternative->condition.location); - return result; - } - if (sv.toBool()) { - // The condition is true. Continue evaluating the value. - result.tryNextAlternative = false; - } else { - // The condition is false. Try the next alternative or the else value. - result.tryNextAlternative = true; - return result; - } - sv = engine->evaluate(alternative->overrideListProperties.value); - if (engine->hasErrorOrException(sv)) { - result.scriptValue = sv; - result.hasError = true; - injectErrorLocation(result.scriptValue, - alternative->overrideListProperties.location); - return result; - } - if (sv.toBool()) - elseCaseValue->setIsExclusiveListValue(); - } - result.scriptValue = engine->evaluate(value->sourceCodeForEvaluation(), - value->file()->filePath(), value->line()); - return result; - } - - void handle(ItemValue *value) override - { - *result = data->evaluator->scriptValue(value->item()); - if (!result->isValid()) - qDebug() << "SVConverter returned invalid script value."; - } - - void handle(VariantValue *variantValue) override - { - *result = engine->toScriptValue(variantValue->value()); - } -}; - -bool debugProperties = false; - -enum QueryPropertyType -{ - QPTDefault, - QPTParentProperty -}; - -EvaluatorScriptClass::EvaluatorScriptClass(ScriptEngine *scriptEngine) - : QScriptClass(scriptEngine) - , m_valueCacheEnabled(false) -{ -} - -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 {}); - - if (debugProperties) - qDebug() << "[SC] queryProperty " << object.objectId() << " " << name; - - auto const data = attachedPointer<EvaluationData>(object); - const QString nameString = name.toString(); - if (nameString == QStringLiteral("parent")) { - *id = QPTParentProperty; - m_queryResult.data = data; - return QScriptClass::HandlesReadAccess; - } - - *id = QPTDefault; - if (!data) { - if (debugProperties) - qDebug() << "[SC] queryProperty: no data attached"; - return {}; - } - - 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->ownProperty(name); - if (m_queryResult.value) { - m_queryResult.data = data; - m_queryResult.itemOfProperty = item; - return HandlesReadAccess; - } - } - - if (!ignoreParent && data->item && data->item->parent()) { - if (debugProperties) - qDebug() << "[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.foundInParent = true; - m_queryResult.data = data; - return qf; - } - } - - if (debugProperties) - qDebug() << "[SC] queryProperty: no such property"; - return {}; -} - -QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue) -{ - return (scriptValue.isObject() - ? QStringLiteral("[Object: ") - + QString::number(scriptValue.objectId()) + QLatin1Char(']') - : scriptValue.toVariant().toString()); -} - -void EvaluatorScriptClass::collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, - const QString &propertyName, const ValuePtr &value) -{ - QScriptValueList lst; - Set<Value *> oldNextChain = m_currentNextChain; - for (ValuePtr next = value; next; next = next->next()) - m_currentNextChain.insert(next.get()); - - for (ValuePtr next = value; next; next = next->next()) { - QScriptValue v = data->evaluator->property(next->definingItem(), propertyName); - const auto se = static_cast<const ScriptEngine *>(engine()); - if (se->hasErrorOrException(v)) { - *result = se->lastErrorValue(v); - return; - } - if (v.isUndefined()) - continue; - lst << v; - if (next->type() == Value::JSSourceValueType - && std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) { - lst = lst.mid(lst.length() - 2); - break; - } - } - m_currentNextChain = oldNextChain; - - *result = engine()->newArray(); - quint32 k = 0; - for (const QScriptValue &v : qAsConst(lst)) { - QBS_ASSERT(!v.isError(), continue); - if (v.isArray()) { - const quint32 vlen = v.property(StringConstants::lengthProperty()).toInt32(); - for (quint32 j = 0; j < vlen; ++j) - result->setProperty(k++, v.property(j)); - } else { - result->setProperty(k++, v); - } - } -} - -static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) -{ - const VariantValuePtr v = item->variantProperty - (StringConstants::qbsSourceDirPropertyInternal()); - return v ? v->value().toString() : defaultValue; -} - -static void makeTypeError(const ErrorInfo &error, QScriptValue &v) -{ - v = v.engine()->currentContext()->throwError(QScriptContext::TypeError, - error.toString()); -} - -static void makeTypeError(const PropertyDeclaration &decl, const CodeLocation &location, - QScriptValue &v) -{ - const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") - .arg(decl.name(), decl.typeString()), location); - makeTypeError(error, v); -} - -static void convertToPropertyType_impl(const QString &pathPropertiesBaseDir, const Item *item, - const PropertyDeclaration& decl, - const CodeLocation &location, QScriptValue &v) -{ - if (v.isUndefined() || v.isError()) - return; - QString srcDir; - QString actualBaseDir; - if (item && !pathPropertiesBaseDir.isEmpty()) { - const VariantValueConstPtr itemSourceDir - = item->variantProperty(QStringLiteral("sourceDirectory")); - actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; - } - switch (decl.type()) { - case PropertyDeclaration::UnknownType: - case PropertyDeclaration::Variant: - break; - case PropertyDeclaration::Boolean: - if (!v.isBool()) - v = v.toBool(); - break; - case PropertyDeclaration::Integer: - if (!v.isNumber()) - makeTypeError(decl, location, v); - break; - case PropertyDeclaration::Path: - { - if (!v.isString()) { - makeTypeError(decl, location, v); - break; - } - const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) - : pathPropertiesBaseDir; - if (!srcDir.isEmpty()) - v = v.engine()->toScriptValue(QDir::cleanPath( - FileInfo::resolvePath(srcDir, v.toString()))); - break; - } - case PropertyDeclaration::String: - if (!v.isString()) - makeTypeError(decl, location, v); - break; - case PropertyDeclaration::PathList: - srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) - : pathPropertiesBaseDir; - // Fall-through. - case PropertyDeclaration::StringList: - { - if (!v.isArray()) { - QScriptValue x = v.engine()->newArray(1); - x.setProperty(0, v); - v = x; - } - const quint32 c = v.property(StringConstants::lengthProperty()).toUInt32(); - for (quint32 i = 0; i < c; ++i) { - QScriptValue elem = v.property(i); - if (elem.isUndefined()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " - "String expected.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (elem.isNull()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " - "String expected.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (!elem.isString()) { - ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " - "string type.").arg(i).arg(decl.name()), location); - makeTypeError(error, v); - break; - } - if (srcDir.isEmpty()) - continue; - elem = v.engine()->toScriptValue( - QDir::cleanPath(FileInfo::resolvePath(srcDir, elem.toString()))); - v.setProperty(i, elem); - } - break; - } - case PropertyDeclaration::VariantList: - if (!v.isArray()) { - QScriptValue x = v.engine()->newArray(1); - x.setProperty(0, v); - v = x; - } - break; - } -} - -void EvaluatorScriptClass::convertToPropertyType(const PropertyDeclaration &decl, - const CodeLocation &loc, QScriptValue &v) -{ - convertToPropertyType_impl(QString(), nullptr, decl, loc, v); -} - -void EvaluatorScriptClass::convertToPropertyType(const Item *item, const PropertyDeclaration& decl, - const Value *value, QScriptValue &v) -{ - if (value->type() == Value::VariantValueType && v.isUndefined() && !decl.isScalar()) { - v = v.engine()->newArray(); // QTBUG-51237 - return; - } - convertToPropertyType_impl(m_pathPropertiesBaseDir, item, decl, value->location(), v); -} - -class PropertyStackManager -{ -public: - PropertyStackManager(const Item *itemOfProperty, const QScriptString &name, const Value *value, - std::stack<QualifiedId> &requestedProperties, - PropertyDependencies &propertyDependencies) - : m_requestedProperties(requestedProperties) - { - if (value->type() == Value::JSSourceValueType - && (itemOfProperty->type() == ItemType::ModuleInstance - || itemOfProperty->type() == ItemType::Module - || itemOfProperty->type() == ItemType::Export)) { - const VariantValueConstPtr varValue - = itemOfProperty->variantProperty(StringConstants::nameProperty()); - if (!varValue) - return; - m_stackUpdate = true; - const QualifiedId fullPropName - = QualifiedId::fromString(varValue->value().toString()) << name.toString(); - if (!requestedProperties.empty()) - propertyDependencies[fullPropName].insert(requestedProperties.top()); - m_requestedProperties.push(fullPropName); - } - } - - ~PropertyStackManager() - { - if (m_stackUpdate) - m_requestedProperties.pop(); - } - -private: - std::stack<QualifiedId> &m_requestedProperties; - bool m_stackUpdate = false; -}; - -QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QScriptString &name, - uint id) -{ - const bool foundInParent = m_queryResult.foundInParent; - const EvaluationData *data = m_queryResult.data; - const Item * const itemOfProperty = m_queryResult.itemOfProperty; - m_queryResult.foundInParent = false; - m_queryResult.data = nullptr; - m_queryResult.itemOfProperty = nullptr; - QBS_ASSERT(data, {}); - - const auto 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 {}); - QBS_ASSERT(m_queryResult.isNull(), return {}); - - if (debugProperties) - qDebug() << "[SC] property " << name; - - PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), - m_requestedProperties, m_propertyDependencies); - - QScriptValue result; - if (m_valueCacheEnabled) { - result = data->valueCache.value(name); - if (result.isValid()) { - if (debugProperties) - qDebug() << "[SC] cache hit " << name << ": " << resultToString(result); - return result; - } - } - - if (value->next() && !m_currentNextChain.contains(value.get())) { - collectValuesFromNextChain(data, &result, name.toString(), value); - } else { - QScriptValue parentObject; - if (foundInParent) - parentObject = data->evaluator->scriptValue(data->item->parent()); - SVConverter converter(this, foundInParent ? &parentObject : &object, value, itemOfProperty, - &name, data, &result); - converter.start(); - - const PropertyDeclaration decl = data->item->propertyDeclaration(name.toString()); - convertToPropertyType(data->item, decl, value.get(), result); - } - - if (debugProperties) - qDebug() << "[SC] cache miss " << name << ": " << resultToString(result); - if (m_valueCacheEnabled) - data->valueCache.insert(name, result); - return result; -} - -class EvaluatorScriptClassPropertyIterator : public QScriptClassPropertyIterator -{ -public: - EvaluatorScriptClassPropertyIterator(const QScriptValue &object, EvaluationData *data) - : QScriptClassPropertyIterator(object), m_it(data->item->properties()) - { - } - - bool hasNext() const override - { - return m_it.hasNext(); - } - - void next() override - { - m_it.next(); - } - - bool hasPrevious() const override - { - return m_it.hasPrevious(); - } - - void previous() override - { - m_it.previous(); - } - - void toFront() override - { - m_it.toFront(); - } - - void toBack() override - { - m_it.toBack(); - } - - QScriptString name() const override - { - return object().engine()->toStringHandle(m_it.key()); - } - -private: - QMapIterator<QString, ValuePtr> m_it; -}; - -QScriptClassPropertyIterator *EvaluatorScriptClass::newIterator(const QScriptValue &object) -{ - auto const data = attachedPointer<EvaluationData>(object); - return data ? new EvaluatorScriptClassPropertyIterator(object, data) : nullptr; -} - -void EvaluatorScriptClass::setValueCacheEnabled(bool enabled) -{ - m_valueCacheEnabled = enabled; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h deleted file mode 100755 index c234c17fa..000000000 --- a/src/lib/corelib/language/evaluatorscriptclass.h +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QBS_EVALUATORSCRIPTCLASS_H -#define QBS_EVALUATORSCRIPTCLASS_H - -#include "forward_decls.h" -#include "qualifiedid.h" - -#include <tools/set.h> - -#include <QtScript/qscriptclass.h> - -#include <stack> - -QT_BEGIN_NAMESPACE -class QScriptContext; -QT_END_NAMESPACE - -namespace qbs { -namespace Internal { -class EvaluationData; -class Item; -class PropertyDeclaration; -class ScriptEngine; - -class EvaluatorScriptClass : public QScriptClass -{ -public: - EvaluatorScriptClass(ScriptEngine *scriptEngine); - - QueryFlags queryProperty(const QScriptValue &object, - const QScriptString &name, - QueryFlags flags, uint *id) override; - QScriptValue property(const QScriptValue &object, - const QScriptString &name, uint id) override; - QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override; - - void setValueCacheEnabled(bool enabled); - - void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, - QScriptValue &v); - - PropertyDependencies propertyDependencies() const { return m_propertyDependencies; } - void clearPropertyDependencies() { m_propertyDependencies.clear(); } - - void setPathPropertiesBaseDir(const QString &dirPath) { m_pathPropertiesBaseDir = dirPath; } - void clearPathPropertiesBaseDir() { m_pathPropertiesBaseDir.clear(); } - -private: - QueryFlags queryItemProperty(const EvaluationData *data, - const QString &name, - bool ignoreParent = false); - static QString resultToString(const QScriptValue &scriptValue); - void collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, const QString &propertyName, const ValuePtr &value); - - void convertToPropertyType(const Item *item, - const PropertyDeclaration& decl, const Value *value, - QScriptValue &v); - - struct QueryResult - { - QueryResult() - : data(nullptr), itemOfProperty(nullptr) - {} - - bool isNull() const - { - static const QueryResult pristine; - return *this == pristine; - } - - bool operator==(const QueryResult &rhs) const - { - return foundInParent == rhs.foundInParent - && data == rhs.data - && itemOfProperty == rhs.itemOfProperty - && value == rhs.value; - } - - bool foundInParent = false; - const EvaluationData *data; - const Item *itemOfProperty; // The item that owns the property. - ValuePtr value; - }; - QueryResult m_queryResult; - bool m_valueCacheEnabled; - Set<Value *> m_currentNextChain; - PropertyDependencies m_propertyDependencies; - std::stack<QualifiedId> m_requestedProperties; - QString m_pathPropertiesBaseDir; -}; - -} // namespace Internal -} // namespace qbs - -#endif // QBS_EVALUATORSCRIPTCLASS_H diff --git a/src/lib/corelib/language/forward_decls.h b/src/lib/corelib/language/forward_decls.h index 6697ac8c9..6ee22a5a6 100644 --- a/src/lib/corelib/language/forward_decls.h +++ b/src/lib/corelib/language/forward_decls.h @@ -142,12 +142,14 @@ class PersistentPool; } // namespace qbs #ifdef QT_CORE_LIB +#include "../tools/porting.h" + #include <QtCore/qhash.h> namespace qbs { namespace Internal { -template <typename T> inline static uint qHash(const std::shared_ptr<T> &p, uint seed = 0) +template <typename T> inline static QHashValueType qHash(const std::shared_ptr<T> &p, QHashValueType seed = 0) { return ::qHash(p.get(), seed); } diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp index 9f754bdd7..e5de8f195 100644 --- a/src/lib/corelib/language/item.cpp +++ b/src/lib/corelib/language/item.cpp @@ -40,13 +40,14 @@ #include "item.h" #include "builtindeclarations.h" -#include "deprecationinfo.h" #include "filecontext.h" #include "itemobserver.h" #include "itempool.h" #include "value.h" #include <api/languageinfo.h> +#include <loader/loaderutils.h> +#include <logging/categories.h> #include <logging/logger.h> #include <logging/translator.h> #include <tools/error.h> @@ -59,27 +60,19 @@ namespace qbs { namespace Internal { -Item::Item(ItemPool *pool, ItemType type) - : m_pool(pool) - , m_observer(nullptr) - , m_prototype(nullptr) - , m_scope(nullptr) - , m_outerItem(nullptr) - , m_parent(nullptr) - , m_type(type) -{ -} - Item *Item::create(ItemPool *pool, ItemType type) { return pool->allocateItem(type); } -Item *Item::clone() const +Item *Item::clone(ItemPool &pool) const { - Item *dup = create(pool(), type()); + assertModuleLocked(); + + Item *dup = create(&pool, type()); dup->m_id = m_id; dup->m_location = m_location; + dup->m_endPosition = m_endPosition; dup->m_prototype = m_prototype; dup->m_scope = m_scope; dup->m_outerItem = m_outerItem; @@ -89,25 +82,34 @@ Item *Item::clone() const dup->m_modules = m_modules; dup->m_children.reserve(m_children.size()); - for (const Item * const child : qAsConst(m_children)) { - Item *clonedChild = child->clone(); + for (const Item * const child : std::as_const(m_children)) { + Item *clonedChild = child->clone(pool); clonedChild->m_parent = dup; dup->m_children.push_back(clonedChild); } for (PropertyMap::const_iterator it = m_properties.constBegin(); it != m_properties.constEnd(); ++it) { - dup->m_properties.insert(it.key(), it.value()->clone()); + dup->m_properties.insert(it.key(), it.value()->clone(pool)); } return dup; } +Item *Item::rootPrototype() +{ + Item *proto = this; + while (proto->prototype()) + proto = proto->prototype(); + return proto; +} + QString Item::typeName() const { switch (type()) { case ItemType::IdScope: return QStringLiteral("[IdScope]"); case ItemType::ModuleInstance: return QStringLiteral("[ModuleInstance]"); + case ItemType::ModuleInstancePlaceholder: return QStringLiteral("[ModuleInstancePlaceholder]"); case ItemType::ModuleParameters: return QStringLiteral("[ModuleParametersInstance]"); case ItemType::ModulePrefix: return QStringLiteral("[ModulePrefix]"); case ItemType::Outer: return QStringLiteral("[Outer]"); @@ -118,6 +120,7 @@ QString Item::typeName() const bool Item::hasProperty(const QString &name) const { + assertModuleLocked(); const Item *item = this; do { if (item->m_properties.contains(name)) @@ -129,15 +132,18 @@ bool Item::hasProperty(const QString &name) const bool Item::hasOwnProperty(const QString &name) const { + assertModuleLocked(); return m_properties.contains(name); } ValuePtr Item::property(const QString &name) const { + assertModuleLocked(); ValuePtr value; const Item *item = this; do { - if ((value = item->m_properties.value(name))) + value = item->m_properties.value(name); + if (value) break; item = item->m_prototype; } while (item); @@ -146,30 +152,31 @@ ValuePtr Item::property(const QString &name) const ValuePtr Item::ownProperty(const QString &name) const { + assertModuleLocked(); return m_properties.value(name); } -ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate) +ItemValuePtr Item::itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate) { - return itemProperty(name, itemTemplate, ItemValueConstPtr()); + return itemProperty(name, itemTemplate, ItemValueConstPtr(), pool); } -ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value) +ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool) { - return itemProperty(name, value->item(), value); + return itemProperty(name, value->item(), value, pool); } ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate, - const ItemValueConstPtr &itemValue) + const ItemValueConstPtr &itemValue, ItemPool &pool) { const ValuePtr v = property(name); if (v && v->type() == Value::ItemValueType) return std::static_pointer_cast<ItemValue>(v); if (!itemTemplate) - return ItemValuePtr(); + return {}; const bool createdByPropertiesBlock = itemValue && itemValue->createdByPropertiesBlock(); - const ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()), - createdByPropertiesBlock); + ItemValuePtr result = ItemValue::create(Item::create(&pool, itemTemplate->type()), + createdByPropertiesBlock); setProperty(name, result); return result; } @@ -178,7 +185,7 @@ JSSourceValuePtr Item::sourceProperty(const QString &name) const { ValuePtr v = property(name); if (!v || v->type() != Value::JSSourceValueType) - return JSSourceValuePtr(); + return {}; return std::static_pointer_cast<JSSourceValue>(v); } @@ -186,7 +193,7 @@ VariantValuePtr Item::variantProperty(const QString &name) const { ValuePtr v = property(name); if (!v || v->type() != Value::VariantValueType) - return VariantValuePtr(); + return {}; return std::static_pointer_cast<VariantValue>(v); } @@ -201,37 +208,69 @@ bool Item::isOfTypeOrhasParentOfType(ItemType type) const return false; } +void Item::addObserver(ItemObserver *observer) const +{ + // Cached Module properties never change. + if (m_type == ItemType::Module) + return; + + std::lock_guard lock(m_observersMutex); + if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) + QBS_CHECK(!contains(m_observers, observer)); + m_observers << observer; +} + +void Item::removeObserver(ItemObserver *observer) const +{ + if (m_type == ItemType::Module) + return; + std::lock_guard lock(m_observersMutex); + const auto it = std::find(m_observers.begin(), m_observers.end(), observer); + QBS_CHECK(it != m_observers.end()); + m_observers.erase(it); +} + PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExpired) const { - PropertyDeclaration decl = m_propertyDeclarations.value(name); - if (decl.isValid()) - return decl; + auto it = m_propertyDeclarations.find(name); + if (it != m_propertyDeclarations.end()) + return it.value(); if (allowExpired) { - decl = m_expiredPropertyDeclarations.value(name); - if (decl.isValid()) - return decl; + it = m_expiredPropertyDeclarations.find(name); + if (it != m_expiredPropertyDeclarations.end()) + return it.value(); } - return m_prototype ? m_prototype->propertyDeclaration(name) : decl; + return m_prototype ? m_prototype->propertyDeclaration(name) : PropertyDeclaration(); } void Item::addModule(const Item::Module &module) { - const auto it = std::lower_bound(m_modules.begin(), m_modules.end(), module); - QBS_CHECK(it == m_modules.end() || (module.name != it->name && module.item != it->item)); - m_modules.insert(it, module); -} + if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) { + QBS_CHECK(none_of(m_modules, [&](const Module &m) { + if (m.name != module.name) + return false; + if (!!module.product != !!m.product) + return true; + if (!module.product) + return true; + if (module.product->multiplexConfigurationId == m.product->multiplexConfigurationId + && module.product->profileName == m.product->profileName) { + return true; + } + return false; + })); + } -void Item::setObserver(ItemObserver *observer) const -{ - QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten - m_observer = observer; + m_modules.push_back(module); } void Item::setProperty(const QString &name, const ValuePtr &value) { + assertModuleLocked(); m_properties.insert(name, value); - if (m_observer) - m_observer->onItemPropertyChanged(this); + std::lock_guard lock(m_observersMutex); + for (ItemObserver * const observer : m_observers) + observer->onItemPropertyChanged(this); } void Item::dump() const @@ -246,8 +285,14 @@ bool Item::isPresentModule() const return v && v->type() == Value::JSSourceValueType; } -void Item::setupForBuiltinType(Logger &logger) +bool Item::isFallbackModule() const { + return hasProperty(QLatin1String("__fallback")); +} + +void Item::setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger) +{ + assertModuleLocked(); const BuiltinDeclarations &builtins = BuiltinDeclarations::instance(); const auto properties = builtins.declarationsForType(type()).properties(); for (const PropertyDeclaration &pd : properties) { @@ -260,26 +305,12 @@ void Item::setupForBuiltinType(Logger &logger) sourceValue->setIsBuiltinDefaultValue(); sourceValue->setFile(file()); sourceValue->setSourceCode(pd.initialValueSource().isEmpty() - ? QStringRef(&StringConstants::undefinedValue()) - : QStringRef(&pd.initialValueSource())); + ? StringConstants::undefinedValue() + : pd.initialValueSource()); m_properties.insert(pd.name(), sourceValue); - } else if (pd.isDeprecated()) { - const DeprecationInfo &di = pd.deprecationInfo(); - if (di.removalVersion() <= LanguageInfo::qbsVersion()) { - QString message = Tr::tr("The property '%1' is no longer valid for %2 items. " - "It was removed in qbs %3.") - .arg(pd.name(), typeName(), di.removalVersion().toString()); - ErrorInfo error(message, value->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - throw error; - } - QString warning = Tr::tr("The property '%1' is deprecated and will be removed in " - "qbs %2.").arg(pd.name(), di.removalVersion().toString()); - ErrorInfo error(warning, value->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - logger.printWarning(error); + } else if (ErrorInfo error = pd.checkForDeprecation(deprecationMode, value->location(), + logger); error.hasError()) { + throw error; } } } @@ -289,6 +320,15 @@ void Item::copyProperty(const QString &propertyName, Item *target) const target->setProperty(propertyName, property(propertyName)); } +void Item::overrideProperties(const QVariantMap &config, const QString &key, + const SetupProjectParameters ¶meters, Logger &logger) +{ + const QVariant configMap = config.value(key); + if (configMap.isNull()) + return; + overrideProperties(configMap.toMap(), QualifiedId(key), parameters, logger); +} + static const char *valueType(const Value *v) { switch (v->type()) { @@ -326,7 +366,7 @@ void Item::dump(int indentation) const } if (!m_children.empty()) qDebug("%schildren:", indent.constData()); - for (const Item * const child : qAsConst(m_children)) + for (const Item * const child : std::as_const(m_children)) child->dump(indentation + 4); if (prototype()) { qDebug("%sprototype:", indent.constData()); @@ -334,8 +374,39 @@ void Item::dump(int indentation) const } } +void Item::lockModule() const +{ + QBS_CHECK(m_type == ItemType::Module); + m_moduleMutex.lock(); +#ifndef NDEBUG + QBS_CHECK(!m_moduleLocked); + m_moduleLocked = true; +#endif +} + +void Item::unlockModule() const +{ + QBS_CHECK(m_type == ItemType::Module); +#ifndef NDEBUG + QBS_CHECK(m_moduleLocked); + m_moduleLocked = false; +#endif + m_moduleMutex.unlock(); +} + +// This safeguard verifies that all contexts which access Module properties have really +// acquired the lock via ModuleItemLocker, as they must. +void Item::assertModuleLocked() const +{ +#ifndef NDEBUG + if (m_type == ItemType::Module) + QBS_CHECK(m_moduleLocked); +#endif +} + void Item::removeProperty(const QString &name) { + assertModuleLocked(); m_properties.remove(name); } @@ -387,5 +458,61 @@ void Item::setPropertyDeclarations(const Item::PropertyDeclarationMap &decls) m_propertyDeclarations = decls; } +void Item::overrideProperties( + const QVariantMap &overridden, + const QualifiedId &namePrefix, + const SetupProjectParameters ¶meters, + Logger &logger) +{ + if (overridden.isEmpty()) + return; + for (QVariantMap::const_iterator it = overridden.constBegin(); it != overridden.constEnd(); + ++it) { + const PropertyDeclaration decl = propertyDeclaration(it.key()); + if (!decl.isValid()) { + ErrorInfo error(Tr::tr("Unknown property: %1.%2"). + arg(namePrefix.toString(), it.key())); + handlePropertyError(error, parameters, logger); + continue; + } + const auto overridenValue = VariantValue::create(PropertyDeclaration::convertToPropertyType( + it.value(), decl.type(), namePrefix, it.key())); + overridenValue->markAsSetByCommandLine(); + setProperty(it.key(), overridenValue); + } +} + +void Item::setModules(const Modules &modules) +{ + m_modules = modules; +} + +Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, Item *module) +{ + qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")." + << "Creating dummy module for presence check."; + if (!module) { + module = Item::create(&pool, ItemType::ModuleInstance); + module->setFile(FileContext::create()); + module->setProperty(StringConstants::nameProperty(), VariantValue::create(name)); + } + module->setType(ItemType::ModuleInstance); + module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue()); + return module; +} + +void setScopeForDescendants(Item *item, Item *scope) +{ + for (Item * const child : item->children()) { + child->setScope(scope); + setScopeForDescendants(child, scope); + } +} + +CodeRange Item::codeRange() const +{ + return {{m_location.line(), m_location.column()}, m_endPosition}; +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h index c5d8ef980..d0dde98c4 100644 --- a/src/lib/corelib/language/item.h +++ b/src/lib/corelib/language/item.h @@ -46,66 +46,107 @@ #include "qualifiedid.h" #include <parser/qmljsmemorypool_p.h> #include <tools/codelocation.h> +#include <tools/deprecationwarningmode.h> #include <tools/error.h> #include <tools/version.h> #include <QtCore/qlist.h> #include <QtCore/qmap.h> +#include <atomic> +#include <mutex> +#include <utility> #include <vector> namespace qbs { + +class SetupProjectParameters; + namespace Internal { class ItemObserver; class ItemPool; class Logger; +class ModuleItemLocker; +class ProductContext; class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed { friend class ASTPropertiesItemHandler; friend class ItemPool; friend class ItemReaderASTVisitor; + friend class ModuleItemLocker; Q_DISABLE_COPY(Item) - Item(ItemPool *pool, ItemType type); + Item(ItemType type) : m_type(type) {} public: struct Module { - Module() - : item(nullptr), isProduct(false), required(true) - {} - QualifiedId name; - Item *item; - bool isProduct; - bool required; + Item *item = nullptr; + ProductContext *product = nullptr; // Set if and only if the dep is a product. + + // All the sites that declared an explicit dependency on this module. Can contain any + // number of module instances and at most one product. + using ParametersWithPriority = std::pair<QVariantMap, int>; + struct LoadContext { + LoadContext(Item *dependsItem, + const ParametersWithPriority ¶meters) + : dependsItem(dependsItem), parameters(parameters) {} + LoadContext(Item *dependsItem, ParametersWithPriority &¶meters) + : dependsItem(dependsItem), parameters(std::move(parameters)) {} + + LoadContext(const LoadContext &) = default; + LoadContext(LoadContext &&) = default; + LoadContext &operator=(const LoadContext &) = default; + LoadContext &operator=(LoadContext &&) = default; + + Item *loadingItem() const { return dependsItem ? dependsItem->parent() : nullptr; } + Item *dependsItem; + ParametersWithPriority parameters; + }; + std::vector<LoadContext> loadContexts; + QVariantMap parameters; VersionRange versionRange; + + // The shorter this value, the "closer to the product" we consider the module, + // and the higher its precedence is when merging property values. + int maxDependsChainLength = 0; + + bool required = true; }; using Modules = std::vector<Module>; using PropertyDeclarationMap = QMap<QString, PropertyDeclaration>; using PropertyMap = QMap<QString, ValuePtr>; static Item *create(ItemPool *pool, ItemType type); - Item *clone() const; - ItemPool *pool() const { return m_pool; } + Item *clone(ItemPool &pool) const; const QString &id() const { return m_id; } const CodeLocation &location() const { return m_location; } + CodeRange codeRange() const; Item *prototype() const { return m_prototype; } + Item *rootPrototype(); Item *scope() const { return m_scope; } Item *outerItem() const { return m_outerItem; } Item *parent() const { return m_parent; } const FileContextPtr &file() const { return m_file; } const QList<Item *> &children() const { return m_children; } + QList<Item *> &children() { return m_children; } Item *child(ItemType type, bool checkForMultiple = true) const; - const PropertyMap &properties() const { return m_properties; } + const PropertyMap &properties() const { assertModuleLocked(); return m_properties; } + PropertyMap &properties() { assertModuleLocked(); return m_properties; } const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; } PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const; + + // The list of modules is ordered such that dependencies appear before the modules + // depending on them. const Modules &modules() const { return m_modules; } + Modules &modules() { return m_modules; } + void addModule(const Module &module); void removeModules() { m_modules.clear(); } - void setModules(const Modules &modules) { m_modules = modules; } + void setModules(const Modules &modules); ItemType type() const { return m_type; } void setType(ItemType type) { m_type = type; } @@ -115,18 +156,20 @@ public: bool hasOwnProperty(const QString &name) const; ValuePtr property(const QString &name) const; ValuePtr ownProperty(const QString &name) const; - ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate = nullptr); - ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value); + ItemValuePtr itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate = nullptr); + ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool); JSSourceValuePtr sourceProperty(const QString &name) const; VariantValuePtr variantProperty(const QString &name) const; bool isOfTypeOrhasParentOfType(ItemType type) const; - void setObserver(ItemObserver *observer) const; + void addObserver(ItemObserver *observer) const; + void removeObserver(ItemObserver *observer) const; void setProperty(const QString &name, const ValuePtr &value); - void setProperties(const PropertyMap &props) { m_properties = props; } + void setProperties(const PropertyMap &props) { assertModuleLocked(); m_properties = props; } void removeProperty(const QString &name); void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); void setPropertyDeclarations(const PropertyDeclarationMap &decls); void setLocation(const CodeLocation &location) { m_location = location; } + void setEndPosition(const CodePosition &position) { m_endPosition = position; } void setPrototype(Item *prototype) { m_prototype = prototype; } void setFile(const FileContextPtr &file) { m_file = file; } void setId(const QString &id) { m_id = id; } @@ -139,23 +182,39 @@ public: static void removeChild(Item *parent, Item *child); void dump() const; bool isPresentModule() const; - void setupForBuiltinType(Logger &logger); + bool isFallbackModule() const; + void setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger); void copyProperty(const QString &propertyName, Item *target) const; + void overrideProperties( + const QVariantMap &config, + const QString &key, + const SetupProjectParameters ¶meters, + Logger &logger); + void overrideProperties( + const QVariantMap &config, + const QualifiedId &namePrefix, + const SetupProjectParameters ¶meters, + Logger &logger); private: ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate, - const ItemValueConstPtr &itemValue); + const ItemValueConstPtr &itemValue, ItemPool &pool); void dump(int indentation) const; - ItemPool *m_pool; - mutable ItemObserver *m_observer; + void lockModule() const; + void unlockModule() const; + void assertModuleLocked() const; + + mutable std::vector<ItemObserver *> m_observers; + mutable std::mutex m_observersMutex; QString m_id; CodeLocation m_location; - Item *m_prototype; - Item *m_scope; - Item *m_outerItem; - Item *m_parent; + CodePosition m_endPosition; + Item *m_prototype = nullptr; + Item *m_scope = nullptr; + Item *m_outerItem = nullptr; + Item *m_parent = nullptr; QList<Item *> m_children; FileContextPtr m_file; PropertyMap m_properties; @@ -163,10 +222,34 @@ private: PropertyDeclarationMap m_expiredPropertyDeclarations; Modules m_modules; ItemType m_type; + mutable std::mutex m_moduleMutex; +#ifndef NDEBUG + mutable std::atomic_bool m_moduleLocked = false; +#endif }; inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; } +Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, + Item *module); +void setScopeForDescendants(Item *item, Item *scope); + +// This mechanism is needed because Module items are shared between products (not doing so +// would be prohibitively expensive). +// The competing accesses are between +// - Attaching a temporary qbs module for evaluating the Module condition. +// - Cloning the module when creating an instance. +// - Directly accessing Module properties, which happens rarely (as opposed to properties of +// an instance). +class ModuleItemLocker +{ +public: + ModuleItemLocker(const Item &item) : m_item(item) { item.lockModule(); } + ~ModuleItemLocker() { m_item.unlockModule(); } +private: + const Item &m_item; +}; + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.cpp b/src/lib/corelib/language/itemdeclaration.cpp index d7230e9d6..eb9fd84a6 100644 --- a/src/lib/corelib/language/itemdeclaration.cpp +++ b/src/lib/corelib/language/itemdeclaration.cpp @@ -60,5 +60,11 @@ bool ItemDeclaration::isChildTypeAllowed(ItemType type) const return m_allowedChildTypes.contains(type); } +ErrorInfo ItemDeclaration::checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, Logger &logger) const +{ + return deprecationInfo().checkForDeprecation(mode, name, loc, true, logger); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.h b/src/lib/corelib/language/itemdeclaration.h index 6da699d28..1fbd7e456 100644 --- a/src/lib/corelib/language/itemdeclaration.h +++ b/src/lib/corelib/language/itemdeclaration.h @@ -71,6 +71,9 @@ public: const TypeNames &allowedChildTypes() const { return m_allowedChildTypes; } bool isChildTypeAllowed(ItemType type) const; + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const QString &name, + const CodeLocation &loc, Logger &logger) const; + private: ItemType m_type; Properties m_properties; diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp index ccd22fe2e..6552f92ef 100644 --- a/src/lib/corelib/language/itempool.cpp +++ b/src/lib/corelib/language/itempool.cpp @@ -53,7 +53,7 @@ ItemPool::~ItemPool() Item *ItemPool::allocateItem(const ItemType &type) { - const auto item = new (&m_pool) Item(this, type); + const auto item = new (&m_pool) Item(type); m_items.push_back(item); return item; } diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp deleted file mode 100644 index 578f194bc..000000000 --- a/src/lib/corelib/language/itemreader.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "itemreader.h" - -#include "itemreadervisitorstate.h" - -#include <tools/profiling.h> - -#include <QtCore/qfileinfo.h> - -#include <algorithm> - -namespace qbs { -namespace Internal { - -static void makePathsCanonical(QStringList &paths) -{ - auto it = std::remove_if(paths.begin(), paths.end(), [](QString &p) { - p = QFileInfo(p).canonicalFilePath(); - return p.isEmpty(); - }); - auto e = paths.end(); - if (it != e) - paths.erase(it, e); -} - -ItemReader::ItemReader(Logger &logger) : m_visitorState(new ItemReaderVisitorState(logger)) -{ -} - -ItemReader::~ItemReader() -{ - delete m_visitorState; -} - -void ItemReader::setSearchPaths(const QStringList &searchPaths) -{ - m_searchPaths = searchPaths; - makePathsCanonical(m_searchPaths); - m_allSearchPaths.clear(); -} - -void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) -{ - m_extraSearchPaths.push_back(extraSearchPaths); - makePathsCanonical(m_extraSearchPaths.back()); - m_allSearchPaths.clear(); -} - -void ItemReader::popExtraSearchPaths() -{ - m_extraSearchPaths.pop_back(); - m_allSearchPaths.clear(); -} - -std::vector<QStringList> ItemReader::extraSearchPathsStack() const -{ - return m_extraSearchPaths; -} - -void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &s) -{ - m_extraSearchPaths = s; - m_allSearchPaths.clear(); -} - -void ItemReader::clearExtraSearchPathsStack() -{ - m_extraSearchPaths.clear(); - m_allSearchPaths.clear(); -} - -const QStringList &ItemReader::allSearchPaths() const -{ - if (m_allSearchPaths.empty()) { - std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(), - [this] (const QStringList &paths) { - m_allSearchPaths += paths; - }); - m_allSearchPaths += m_searchPaths; - m_allSearchPaths.removeDuplicates(); - } - return m_allSearchPaths; -} - -Item *ItemReader::readFile(const QString &filePath) -{ - AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr); - return m_visitorState->readFile(filePath, allSearchPaths(), m_pool); -} - -Set<QString> ItemReader::filesRead() const -{ - return m_visitorState->filesRead(); -} - -void ItemReader::setEnableTiming(bool on) -{ - m_elapsedTime = on ? 0 : -1; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h index 724666cb4..7e9900a5b 100644 --- a/src/lib/corelib/language/itemtype.h +++ b/src/lib/corelib/language/itemtype.h @@ -74,6 +74,7 @@ enum class ItemType { // Internal items created mainly by the module loader. IdScope, ModuleInstance, + ModuleInstancePlaceholder, ModuleParameters, ModulePrefix, Outer, @@ -82,7 +83,7 @@ enum class ItemType { Unknown }; -inline uint qHash(ItemType t) { return QT_PREPEND_NAMESPACE(qHash)(uint(t)); } +inline auto qHash(ItemType t) { return QT_PREPEND_NAMESPACE(qHash)(uint(t)); } } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/jsimports.h b/src/lib/corelib/language/jsimports.h index a892e0ec0..a4a045f2b 100644 --- a/src/lib/corelib/language/jsimports.h +++ b/src/lib/corelib/language/jsimports.h @@ -71,7 +71,7 @@ public: pool.serializationOp<opType>(scopeName, filePaths, location); } }; -inline uint qHash(const JsImport &jsi) { return qHash(jsi.scopeName); } +inline auto qHash(const JsImport &jsi) { return qHash(jsi.scopeName); } inline bool operator<(const JsImport &lhs, const JsImport &rhs) { diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index 3a08c089b..b3f4b2a64 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -51,15 +51,19 @@ #include <buildgraph/rulegraph.h> // TODO: Move to language? #include <buildgraph/transformer.h> #include <jsextensions/jsextensions.h> +#include <language/value.h> +#include <loader/loaderutils.h> #include <logging/categories.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> #include <tools/hostosinfo.h> #include <tools/error.h> #include <tools/fileinfo.h> -#include <tools/scripttools.h> #include <tools/qbsassert.h> #include <tools/qttools.h> +#include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qcryptographichash.h> @@ -67,8 +71,6 @@ #include <QtCore/qdiriterator.h> #include <QtCore/qmap.h> -#include <QtScript/qscriptvalue.h> - #include <algorithm> #include <memory> #include <mutex> @@ -102,7 +104,7 @@ void FileTagger::setPatterns(const QStringList &patterns) m_patterns.clear(); for (const QString &pattern : patterns) { QBS_CHECK(!pattern.isEmpty()); - m_patterns << QRegExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + m_patterns << QRegularExpression(QRegularExpression::wildcardToRegularExpression(pattern)); } } @@ -113,9 +115,15 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const FileInfo fi(filePath); return !fi.exists() || fi.lastModified() > referenceTime; }; - return std::any_of(m_importedFilesUsed.cbegin(), m_importedFilesUsed.cend(), criterion); + return Internal::any_of(m_importedFilesUsed, criterion); } +void Probe::restoreValues() +{ + for (auto it = m_properties.begin(), end = m_properties.end(); it != end; ++it) { + m_values[it.key()] = VariantValue::createStored(it.value()); + } +} /*! * \class SourceArtifact @@ -136,41 +144,16 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const /*! * \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. + * \brief Represents the wildcard patterns 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. - */ -std::vector<SourceArtifactPtr> ResolvedGroup::allFiles() const -{ - std::vector<SourceArtifactPtr> lst = files; - if (wildcards) - lst << wildcards->files; - return lst; -} - -void ResolvedGroup::load(PersistentPool &pool) -{ - serializationOp<PersistentPool::Load>(pool); - if (wildcards) - wildcards->group = this; -} - -void ResolvedGroup::store(PersistentPool &pool) -{ - serializationOp<PersistentPool::Store>(pool); -} - -/*! * \class RuleArtifact * \brief The \c RuleArtifact class represents an Artifact item encountered in the context * of a Rule item. @@ -276,7 +259,7 @@ QString Rule::toString() const FileTags Rule::staticOutputFileTags() const { FileTags result; - for (const RuleArtifactConstPtr &artifact : artifacts) + for (const auto &artifact : artifacts) result.unite(artifact->fileTags); return result; } @@ -312,7 +295,7 @@ void ResolvedProduct::accept(BuildGraphVisitor *visitor) const { if (!buildData) return; - for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) + for (BuildGraphNode * const node : std::as_const(buildData->rootNodes())) node->accept(visitor); } @@ -323,8 +306,8 @@ void ResolvedProduct::accept(BuildGraphVisitor *visitor) const std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const { std::vector<SourceArtifactPtr> lst; - for (const GroupConstPtr &group : groups) - lst << group->allFiles(); + for (const auto &group : groups) + lst << group->files; return lst; } @@ -335,9 +318,9 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const std::vector<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const { std::vector<SourceArtifactPtr> lst; - for (const GroupConstPtr &group : groups) { + for (const auto &group : groups) { if (group->enabled) - lst << group->allFiles(); + lst << group->files; } return lst; } @@ -346,9 +329,9 @@ FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const { FileTags result; std::unique_ptr<int> priority; - for (const FileTaggerConstPtr &tagger : qAsConst(fileTaggers)) { - for (const QRegExp &pattern : tagger->patterns()) { - if (FileInfo::globMatches(pattern, fileName)) { + for (const FileTaggerConstPtr &tagger : std::as_const(fileTaggers)) { + for (const QRegularExpression &pattern : tagger->patterns()) { + if (pattern.match(fileName).hasMatch()) { if (priority) { if (*priority != tagger->priority()) { // The taggers are expected to be sorted by priority. @@ -373,6 +356,8 @@ void ResolvedProduct::load(PersistentPool &pool) rule->product = this; for (const ResolvedModulePtr &module : modules) module->product = this; + for (const auto &group: groups) + group->restoreWildcards(buildDirectory()); } void ResolvedProduct::store(PersistentPool &pool) @@ -424,18 +409,9 @@ QString ResolvedProduct::uniqueName() const return uniqueName(name, multiplexConfigurationId); } -QString ResolvedProduct::fullDisplayName(const QString &name, - const QString &multiplexConfigurationId) -{ - QString result = name; - if (!multiplexConfigurationId.isEmpty()) - result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId)); - return result; -} - QString ResolvedProduct::fullDisplayName() const { - return fullDisplayName(name, multiplexConfigurationId); + return fullProductDisplayName(name, multiplexConfigurationId); } QString ResolvedProduct::profile() const @@ -511,6 +487,19 @@ QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const return m_executablePathCache.value(origFilePath); } +void ResolvedGroup::restoreWildcards(const QString &buildDir) +{ + if (wildcards) { + wildcards->buildDir = buildDir; + wildcards->prefix = prefix; + wildcards->baseDir = FileInfo::path(location.filePath()); + for (const auto &sourceArtifact : files) { + if (sourceArtifact->fromWildcard) + wildcards->expandedFiles += sourceArtifact->absoluteFilePath; + } + } +} + ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(nullptr) { @@ -522,7 +511,7 @@ void ResolvedProject::accept(BuildGraphVisitor *visitor) const { for (const ResolvedProductPtr &product : products) product->accept(visitor); - for (const ResolvedProjectPtr &subProject : qAsConst(subProjects)) + for (const ResolvedProjectPtr &subProject : std::as_const(subProjects)) subProject->accept(visitor); } @@ -541,7 +530,7 @@ TopLevelProject *ResolvedProject::topLevelProject() std::vector<ResolvedProjectPtr> ResolvedProject::allSubProjects() const { std::vector<ResolvedProjectPtr> projectList = subProjects; - for (const ResolvedProjectConstPtr &subProject : subProjects) + for (const auto &subProject : subProjects) projectList << subProject->allSubProjects(); return projectList; } @@ -549,7 +538,7 @@ std::vector<ResolvedProjectPtr> ResolvedProject::allSubProjects() const std::vector<ResolvedProductPtr> ResolvedProject::allProducts() const { std::vector<ResolvedProductPtr> productList = products; - for (const ResolvedProjectConstPtr &subProject : qAsConst(subProjects)) + for (const auto &subProject : std::as_const(subProjects)) productList << subProject->allProducts(); return productList; } @@ -561,11 +550,11 @@ void ResolvedProject::load(PersistentPool &pool) [](const ResolvedProductPtr &p) { if (!p->buildData) return; - for (BuildGraphNode * const node : qAsConst(p->buildData->allNodes())) { + for (BuildGraphNode * const node : std::as_const(p->buildData->allNodes())) { node->product = p; // restore parent links - for (BuildGraphNode * const child : qAsConst(node->children)) + for (BuildGraphNode * const child : std::as_const(node->children)) child->parents.insert(node); } }); @@ -591,7 +580,7 @@ TopLevelProject::~TopLevelProject() QString TopLevelProject::deriveId(const QVariantMap &config) { const QVariantMap qbsProperties = config.value(StringConstants::qbsModule()).toMap(); - const QString configurationName = qbsProperties.value( + QString configurationName = qbsProperties.value( StringConstants::configurationNameProperty()).toString(); return configurationName; } @@ -614,10 +603,20 @@ QString TopLevelProject::profile() const void TopLevelProject::makeModuleProvidersNonTransient() { - for (ModuleProviderInfo &m : moduleProviderInfo) + for (ModuleProviderInfo &m : moduleProviderInfo.providers) m.transientOutput = false; } +QVariantMap TopLevelProject::fullProfileConfigsTree() const +{ + QVariantMap tree; + for (auto it = profileConfigs.cbegin(); it != profileConfigs.cend(); ++it) { + tree.insert(it.key(), SetupProjectParameters::finalBuildConfigurationTree( + it.value().toMap(), overriddenValues)); + } + return tree; +} + QString TopLevelProject::buildGraphFilePath() const { return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); @@ -664,7 +663,7 @@ void TopLevelProject::store(PersistentPool &pool) void TopLevelProject::cleanupModuleProviderOutput() { QString error; - for (const ModuleProviderInfo &m : moduleProviderInfo) { + for (const ModuleProviderInfo &m : std::as_const(moduleProviderInfo.providers)) { if (m.transientOutput) { if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error)) qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; @@ -708,25 +707,22 @@ void TopLevelProject::cleanupModuleProviderOutput() * \brief The \c SourceArtifacts resulting from the expanded list of matching files. */ -Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, - const QString &baseDir, const QString &buildDir) +void SourceWildCards::expandPatterns() { - Set<QString> files = expandPatterns(group, patterns, baseDir, buildDir); - files -= expandPatterns(group, excludePatterns, baseDir, buildDir); - return files; + dirTimeStamps.clear(); + expandedFiles = expandPatterns(patterns) - expandPatterns(excludePatterns); } -Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, - const QStringList &patterns, const QString &baseDir, const QString &buildDir) +Set<QString> SourceWildCards::expandPatterns(const QStringList &patterns) { Set<QString> files; - QString expandedPrefix = group->prefix; + QString expandedPrefix = prefix; if (expandedPrefix.startsWith(StringConstants::tildeSlash())) expandedPrefix.replace(0, 1, QDir::homePath()); for (QString pattern : patterns) { pattern.prepend(expandedPrefix); pattern.replace(QLatin1Char('\\'), QLatin1Char('/')); - QStringList parts = pattern.split(QLatin1Char('/'), QString::SkipEmptyParts); + QStringList parts = pattern.split(QLatin1Char('/'), Qt::SkipEmptyParts); if (FileInfo::isAbsolute(pattern)) { QString rootDir; if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) { @@ -736,18 +732,17 @@ Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group, } else { rootDir = QLatin1Char('/'); } - expandPatterns(files, group, parts, rootDir, buildDir); + expandPatterns(files, parts, rootDir); } else { - expandPatterns(files, group, parts, baseDir, buildDir); + expandPatterns(files, parts, baseDir); } } return files; } -void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &group, - const QStringList &parts, - const QString &baseDir, const QString &buildDir) +void SourceWildCards::expandPatterns(Set<QString> &result, const QStringList &parts, + const QString &baseDir) { // People might build directly in the project source directory. This is okay, since // we keep the build data in a "container" directory. However, we must make sure we don't @@ -755,8 +750,6 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & if (baseDir.startsWith(buildDir)) return; - dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified()); - QStringList changed_parts = parts; bool recursive = false; QString part = changed_parts.takeFirst(); @@ -783,8 +776,12 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & : QDir::Files | QDir::System | QDir::Dirs; // This one is needed to get symbolic links to directories - if (isDir && !FileInfo::isPattern(filePattern)) + if (FileInfo::isPattern(filePattern)) { + if (!recursive) + dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified()); + } else if (isDir) { itFilters |= QDir::Hidden; + } if (filePattern != StringConstants::dotDot() && filePattern != StringConstants::dot()) itFilters |= QDir::NoDotAndDotDot; @@ -796,16 +793,28 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr & continue; // See above. if (!isDir && it.fileInfo().isDir() && !it.fileInfo().isSymLink()) continue; - if (isDir) { - expandPatterns(result, group, changed_parts, filePath, buildDir); - } else { - if (parentDir != baseDir) - dirTimeStamps.emplace_back(parentDir, FileInfo(baseDir).lastModified()); + if (isDir) + expandPatterns(result, changed_parts, filePath); + else result += QDir::cleanPath(filePath); - } } } +bool SourceWildCards::hasChangedSinceExpansion() const +{ + const bool reExpansionRequired = + Internal::any_of(dirTimeStamps, + [](const std::pair<QString, FileTime> &pair) { + return FileInfo(pair.first).lastModified() > pair.second; + }); + if (reExpansionRequired) + return true; + + auto wc = *this; + wc.expandPatterns(); + return this->expandedFiles != wc.expandedFiles; +} + template<typename L> QMap<QString, typename L::value_type> listToMap(const L &list) { @@ -824,14 +833,13 @@ bool listsAreEqual(const L &l1, const L &l2) using V = typename L::value_type; const QMap<QString, V> map1 = listToMap(l1); const QMap<QString, V> map2 = listToMap(l2); - for (const QString &key : map1.keys()) { + const auto keys = map1.keys(); + return Internal::all_of(keys, [&map1, &map2](const auto &key) { const V &value2 = map2.value(key); if (!value2) return false; - if (!equals(map1.value(key).get(), value2.get())) - return false; - } - return true; + return equals(map1.value(key).get(), value2.get()); + }); } QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; } @@ -857,6 +865,7 @@ bool operator==(const SourceArtifactInternal &sa1, const SourceArtifactInternal && sa1.fileTags == sa2.fileTags && sa1.overrideFileTags == sa2.overrideFileTags && sa1.targetOfModule == sa2.targetOfModule + && !sa1.properties == !sa2.properties && *sa1.properties == *sa2.properties; } @@ -894,8 +903,8 @@ bool operator==(const RuleArtifact &a1, const RuleArtifact &a2) return a1.filePath == a2.filePath && a1.fileTags == a2.fileTags && a1.alwaysUpdated == a2.alwaysUpdated - && Set<RuleArtifact::Binding>::fromStdVector(a1.bindings) == - Set<RuleArtifact::Binding>::fromStdVector(a2.bindings); + && rangeTo<Set<RuleArtifact::Binding>>(a1.bindings) == + rangeTo<Set<RuleArtifact::Binding>>(a2.bindings); } bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) @@ -903,7 +912,7 @@ 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) +QHashValueType qHash(const RuleArtifact::Binding &b) { return qHash(std::make_pair(b.code, b.name.join(QLatin1Char(',')))); } @@ -914,11 +923,6 @@ bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1, return listsAreEqual(l1, l2); } -QString multiplexIdToString(const QString &id) -{ - return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8())); -} - bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b) { return equals(a.m_sharedData.get(), b.m_sharedData.get()); @@ -934,7 +938,7 @@ bool operator==(const ExportedProperty &p1, const ExportedProperty &p2) bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2) { - return d1.name == d2.name && d1.moduleProperties == d2.moduleProperties; + return d1.name == d2.name && qVariantMapsEqual(d1.moduleProperties, d2.moduleProperties); } bool equals(const std::vector<ExportedItemPtr> &l1, const std::vector<ExportedItemPtr> &l2) @@ -961,20 +965,35 @@ bool operator==(const ExportedModule &m1, const ExportedModule &m2) for (auto it1 = m1.cbegin(), it2 = m2.cbegin(); it1 != m1.cend(); ++it1, ++it2) { if (it1.key()->name != it2.key()->name) return false; - if (it1.value() != it2.value()) + if (!qVariantMapsEqual(it1.value(), it2.value())) return false; } return true; }; - return m1.propertyValues == m2.propertyValues - && m1.modulePropertyValues == m2.modulePropertyValues - && equals(m1.children, m2.children) - && m1.m_properties == m2.m_properties - && m1.importStatements == m2.importStatements - && m1.productDependencies.size() == m2.productDependencies.size() - && m1.productDependencies == m2.productDependencies - && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters); + return qVariantMapsEqual(m1.propertyValues, m2.propertyValues) + && qVariantMapsEqual(m1.modulePropertyValues, m2.modulePropertyValues) + && equals(m1.children, m2.children) && m1.m_properties == m2.m_properties + && m1.importStatements == m2.importStatements + && m1.productDependencies.size() == m2.productDependencies.size() + && m1.productDependencies == m2.productDependencies + && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters); +} + +JSValue PrivateScriptFunction::getFunction(ScriptEngine *engine, const QString &errorMessage) const +{ + if (JS_IsUndefined(scriptFunction)) { + ScopedJsValue val(engine->context(), + engine->evaluate(JsValueOwner::Caller, sourceCode(), + location().filePath(), location().line())); + if (Q_UNLIKELY(!JS_IsFunction(engine->context(), val))) + throw ErrorInfo(errorMessage, location()); + scriptFunction = val.release(); + engine->addExternallyCachedValue(&scriptFunction); + } else { + QBS_CHECK(JS_IsFunction(engine->context(), scriptFunction)); + } + return scriptFunction; } } // namespace Internal diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index 2d934edc2..774d703d0 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -49,35 +49,34 @@ #include <buildgraph/forward_decls.h> #include <tools/codelocation.h> +#include <tools/fileinfo.h> #include <tools/filetime.h> #include <tools/joblimits.h> #include <tools/persistence.h> +#include <tools/porting.h> #include <tools/set.h> #include <tools/weakpointer.h> #include <QtCore/qdatastream.h> #include <QtCore/qhash.h> #include <QtCore/qprocess.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qstring.h> #include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptvalue.h> +#include <quickjs.h> #include <memory> #include <mutex> #include <vector> -QT_BEGIN_NAMESPACE -class QScriptEngine; -QT_END_NAMESPACE - namespace qbs { namespace Internal { class BuildGraphLocker; class BuildGraphLoader; class BuildGraphVisitor; +class ScriptEngine; class FileTagger { @@ -88,7 +87,7 @@ public: return FileTaggerPtr(new FileTagger(patterns, fileTags, priority)); } - const QList<QRegExp> &patterns() const { return m_patterns; } + const QList<QRegularExpression> &patterns() const { return m_patterns; } const FileTags &fileTags() const { return m_fileTags; } int priority() const { return m_priority; } @@ -103,7 +102,7 @@ private: void setPatterns(const QStringList &patterns); - QList<QRegExp> m_patterns; + QList<QRegularExpression> m_patterns; FileTags m_fileTags; int m_priority = 0; }; @@ -118,17 +117,20 @@ public: const QString &configureScript, const QVariantMap &properties, const QVariantMap &initialProperties, + const QMap<QString, VariantValuePtr> &values, const std::vector<QString> &importedFilesUsed) { return ProbeConstPtr(new Probe(globalId, location, condition, configureScript, properties, - initialProperties, importedFilesUsed)); + initialProperties, values, importedFilesUsed)); } const QString &globalId() const { return m_globalId; } bool condition() const { return m_condition; } + const CodeLocation &location() const { return m_location; } const QString &configureScript() const { return m_configureScript; } const QVariantMap &properties() const { return m_properties; } const QVariantMap &initialProperties() const { return m_initialProperties; } + const QMap<QString, VariantValuePtr> &values() const { return m_values; } const std::vector<QString> &importedFilesUsed() const { return m_importedFilesUsed; } bool needsReconfigure(const FileTime &referenceTime) const; @@ -136,31 +138,39 @@ public: { pool.serializationOp<opType>(m_globalId, m_location, m_condition, m_configureScript, m_properties, m_initialProperties, m_importedFilesUsed); + if constexpr (opType == PersistentPool::OpType::Load) + restoreValues(); } private: Probe() = default; - Probe(const QString &globalId, + Probe(QString globalId, const CodeLocation &location, bool condition, - const QString &configureScript, - const QVariantMap &properties, - const QVariantMap &initialProperties, - const std::vector<QString> &importedFilesUsed) - : m_globalId(globalId) + QString configureScript, + QVariantMap properties, + QVariantMap initialProperties, + QMap<QString, VariantValuePtr> values, + std::vector<QString> importedFilesUsed) + : m_globalId(std::move(globalId)) , m_location(location) - , m_configureScript(configureScript) - , m_properties(properties) - , m_initialProperties(initialProperties) - , m_importedFilesUsed(importedFilesUsed) + , m_configureScript(std::move(configureScript)) + , m_properties(std::move(properties)) + , m_initialProperties(std::move(initialProperties)) + , m_values(std::move(values)) + , m_importedFilesUsed(std::move(importedFilesUsed)) , m_condition(condition) - {} + { + } + + void restoreValues(); QString m_globalId; CodeLocation m_location; QString m_configureScript; QVariantMap m_properties; QVariantMap m_initialProperties; + QMap<QString, VariantValuePtr> m_values; std::vector<QString> m_importedFilesUsed; bool m_condition = false; }; @@ -212,7 +222,7 @@ private: : alwaysUpdated(true) {} }; -uint qHash(const RuleArtifact::Binding &b); +QHashValueType 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); @@ -232,11 +242,12 @@ public: bool overrideFileTags; QString targetOfModule; PropertyMapPtr properties; + bool fromWildcard; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { pool.serializationOp<opType>(absoluteFilePath, fileTags, overrideFileTags, properties, - targetOfModule); + targetOfModule, fromWildcard); } private: @@ -250,26 +261,28 @@ inline bool operator!=(const SourceArtifactInternal &sa1, const SourceArtifactIn class SourceWildCards { public: - Set<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir, - const QString &buildDir); + void expandPatterns(); + bool hasChangedSinceExpansion() const; + + // to be restored by the owning class + QString prefix; + QString baseDir; + QString buildDir; + Set<QString> expandedFiles; - const ResolvedGroup *group = nullptr; // The owning group. + // stored QStringList patterns; QStringList excludePatterns; std::vector<std::pair<QString, FileTime>> dirTimeStamps; - std::vector<SourceArtifactPtr> files; template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { - pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps, files); + pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps); } private: - Set<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns, - const QString &baseDir, const QString &buildDir); - void expandPatterns(Set<QString> &result, const GroupConstPtr &group, - const QStringList &parts, const QString &baseDir, - const QString &buildDir); + Set<QString> expandPatterns(const QStringList &patterns); + void expandPatterns(Set<QString> &result, const QStringList &parts, const QString &baseDir); }; class QBS_AUTOTEST_EXPORT ResolvedGroup @@ -289,13 +302,9 @@ public: QString targetOfModule; bool overrideTags = false; - std::vector<SourceArtifactPtr> allFiles() const; - - void load(PersistentPool &pool); - void store(PersistentPool &pool); + void restoreWildcards(const QString &buildDir); -private: - template<PersistentPool::OpType opType> void serializationOp(PersistentPool &pool) + template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { pool.serializationOp<opType>(name, enabled, location, prefix, files, wildcards, properties, fileTags, targetOfModule, overrideTags); @@ -334,8 +343,8 @@ class PrivateScriptFunction friend bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); public: void initialize(const ScriptFunctionPtr &sharedData) { m_sharedData = sharedData; } - mutable QScriptValue scriptFunction; // not stored + JSValue getFunction(ScriptEngine *engine, const QString &errorMessage) const; QString &sourceCode() const { return m_sharedData->sourceCode; } CodeLocation &location() const { return m_sharedData->location; } ResolvedFileContextConstPtr &fileContext() const { return m_sharedData->fileContext; } @@ -348,6 +357,7 @@ public: private: ScriptFunctionPtr m_sharedData; + mutable JSValue scriptFunction = JS_UNDEFINED; // not stored }; bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); @@ -362,7 +372,7 @@ public: static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); } QString name; - QStringList moduleDependencies; + QStringList moduleDependencies; // TODO: Still needed? PrivateScriptFunction setupBuildEnvironmentScript; PrivateScriptFunction setupRunEnvironmentScript; ResolvedProduct *product = nullptr; @@ -598,7 +608,6 @@ public: static QString uniqueName(const QString &name, const QString &multiplexConfigurationId); QString uniqueName() const; - static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId); QString fullDisplayName() const; QString profile() const; @@ -688,12 +697,13 @@ public: QString buildDirectory; // Not saved QProcessEnvironment environment; std::vector<ProbeConstPtr> probes; - ModuleProviderInfoList moduleProviderInfo; + StoredModuleProviderInfo moduleProviderInfo; QHash<QString, QString> canonicalFilePathResults; // Results of calls to "File.canonicalFilePath()." QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()". QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults; // Results of calls to "File.directoryEntries()". QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()". + CodeLinks codeLinks; std::unique_ptr<ProjectBuildData> buildData; BuildGraphLocker *bgLocker; // This holds the system-wide build graph file lock. bool locked; // This is the API-level lock for the project instance. @@ -708,6 +718,7 @@ public: QString id() const { return m_id; } QString profile() const; void makeModuleProvidersNonTransient(); + QVariantMap fullProfileConfigsTree() const; // Tree-ified + overridden values QVariantMap profileConfigs; QVariantMap overriddenValues; @@ -724,7 +735,7 @@ private: directoryEntriesResults, fileLastModifiedResults, environment, probes, profileConfigs, overriddenValues, buildSystemFiles, lastStartResolveTime, lastEndResolveTime, warningsEncountered, - buildData, moduleProviderInfo); + buildData, moduleProviderInfo, codeLinks); } void load(PersistentPool &pool) override; void store(PersistentPool &pool) override; @@ -738,8 +749,6 @@ private: bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1, const std::vector<ArtifactPropertiesPtr> &l2); -QString multiplexIdToString(const QString &id); - } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri deleted file mode 100644 index e07a671b9..000000000 --- a/src/lib/corelib/language/language.pri +++ /dev/null @@ -1,82 +0,0 @@ -include(../../../install_prefix.pri) - -HEADERS += \ - $$PWD/artifactproperties.h \ - $$PWD/astimportshandler.h \ - $$PWD/astpropertiesitemhandler.h \ - $$PWD/asttools.h \ - $$PWD/builtindeclarations.h \ - $$PWD/deprecationinfo.h \ - $$PWD/evaluationdata.h \ - $$PWD/evaluator.h \ - $$PWD/evaluatorscriptclass.h \ - $$PWD/filecontext.h \ - $$PWD/filecontextbase.h \ - $$PWD/filetags.h \ - $$PWD/forward_decls.h \ - $$PWD/identifiersearch.h \ - $$PWD/item.h \ - $$PWD/itemdeclaration.h \ - $$PWD/itemobserver.h \ - $$PWD/itempool.h \ - $$PWD/itemreader.h \ - $$PWD/itemreaderastvisitor.h \ - $$PWD/itemreadervisitorstate.h \ - $$PWD/itemtype.h \ - $$PWD/jsimports.h \ - $$PWD/language.h \ - $$PWD/loader.h \ - $$PWD/moduleloader.h \ - $$PWD/modulemerger.h \ - $$PWD/moduleproviderinfo.h \ - $$PWD/preparescriptobserver.h \ - $$PWD/projectresolver.h \ - $$PWD/property.h \ - $$PWD/propertydeclaration.h \ - $$PWD/propertymapinternal.h \ - $$PWD/qualifiedid.h \ - $$PWD/resolvedfilecontext.h \ - $$PWD/scriptengine.h \ - $$PWD/scriptimporter.h \ - $$PWD/scriptpropertyobserver.h \ - $$PWD/value.h - -SOURCES += \ - $$PWD/artifactproperties.cpp \ - $$PWD/astimportshandler.cpp \ - $$PWD/astpropertiesitemhandler.cpp \ - $$PWD/asttools.cpp \ - $$PWD/builtindeclarations.cpp \ - $$PWD/evaluator.cpp \ - $$PWD/evaluatorscriptclass.cpp \ - $$PWD/filecontext.cpp \ - $$PWD/filecontextbase.cpp \ - $$PWD/filetags.cpp \ - $$PWD/identifiersearch.cpp \ - $$PWD/item.cpp \ - $$PWD/itemdeclaration.cpp \ - $$PWD/itempool.cpp \ - $$PWD/itemreader.cpp \ - $$PWD/itemreaderastvisitor.cpp \ - $$PWD/itemreadervisitorstate.cpp \ - $$PWD/language.cpp \ - $$PWD/loader.cpp \ - $$PWD/moduleloader.cpp \ - $$PWD/modulemerger.cpp \ - $$PWD/preparescriptobserver.cpp \ - $$PWD/scriptpropertyobserver.cpp \ - $$PWD/projectresolver.cpp \ - $$PWD/property.cpp \ - $$PWD/propertydeclaration.cpp \ - $$PWD/propertymapinternal.cpp \ - $$PWD/qualifiedid.cpp \ - $$PWD/resolvedfilecontext.cpp \ - $$PWD/scriptengine.cpp \ - $$PWD/scriptimporter.cpp \ - $$PWD/value.cpp - -!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 deleted file mode 100644 index f248fbb1a..000000000 --- a/src/lib/corelib/language/loader.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "loader.h" - -#include "evaluator.h" -#include "language.h" -#include "moduleloader.h" -#include "projectresolver.h" -#include "scriptengine.h" - -#include <logging/translator.h> -#include <tools/fileinfo.h> -#include <tools/profile.h> -#include <tools/progressobserver.h> -#include <tools/qbsassert.h> -#include <tools/settings.h> -#include <tools/setupprojectparameters.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdir.h> -#include <QtCore/qobject.h> -#include <QtCore/qtimer.h> - -namespace qbs { -namespace Internal { - -Loader::Loader(ScriptEngine *engine, Logger logger) - : m_logger(std::move(logger)) - , m_progressObserver(nullptr) - , m_engine(engine) -{ - m_logger.storeWarnings(); -} - -void Loader::setProgressObserver(ProgressObserver *observer) -{ - m_progressObserver = observer; -} - -void Loader::setSearchPaths(const QStringList &_searchPaths) -{ - QStringList searchPaths; - for (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_searchPaths = searchPaths; -} - -void Loader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) -{ - m_oldProjectProbes = oldProbes; -} - -void Loader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) -{ - m_oldProductProbes = oldProbes; -} - -void Loader::setStoredProfiles(const QVariantMap &profiles) -{ - m_storedProfiles = profiles; -} - -void Loader::setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo) -{ - m_storedModuleProviderInfo = providerInfo; -} - -TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters) -{ - SetupProjectParameters parameters = _parameters; - - if (parameters.topLevelProfile().isEmpty()) { - Settings settings(parameters.settingsDirectory()); - QString profileName = settings.defaultProfile(); - if (profileName.isEmpty()) { - m_logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. " - "Using default property values."); - profileName = Profile::fallbackName(); - } - parameters.setTopLevelProfile(profileName); - parameters.expandBuildConfiguration(); - } - - setupProjectFilePath(parameters); - QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); - m_logger.qbsDebug() << "Using project file '" - << QDir::toNativeSeparators(parameters.projectFilePath()) << "'."; - - m_engine->setEnvironment(parameters.adjustedEnvironment()); - m_engine->clearExceptions(); - m_engine->clearImportsCache(); - m_engine->clearRequestedProperties(); - m_engine->enableProfiling(parameters.logElapsedTime()); - m_logger.clearWarnings(); - EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation); - - QTimer cancelationTimer; - - // 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.finalBuildConfigurationTree())), 1); - cancelationTimer.setSingleShot(false); - QObject::connect(&cancelationTimer, &QTimer::timeout, [this]() { - QBS_ASSERT(m_progressObserver, return); - if (m_progressObserver->canceled()) - m_engine->cancel(); - }); - cancelationTimer.start(1000); - } - - const FileTime resolveTime = FileTime::currentTime(); - Evaluator evaluator(m_engine); - ModuleLoader moduleLoader(&evaluator, m_logger); - moduleLoader.setProgressObserver(m_progressObserver); - moduleLoader.setSearchPaths(m_searchPaths); - moduleLoader.setOldProjectProbes(m_oldProjectProbes); - moduleLoader.setOldProductProbes(m_oldProductProbes); - moduleLoader.setLastResolveTime(m_lastResolveTime); - moduleLoader.setStoredProfiles(m_storedProfiles); - moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo); - const ModuleLoaderResult loadResult = moduleLoader.load(parameters); - ProjectResolver resolver(&evaluator, loadResult, std::move(parameters), m_logger); - resolver.setProgressObserver(m_progressObserver); - const TopLevelProjectPtr project = resolver.resolve(); - project->lastStartResolveTime = resolveTime; - project->lastEndResolveTime = FileTime::currentTime(); - - // E.g. if the top-level project is disabled. - if (m_progressObserver) - m_progressObserver->setFinished(); - - return project; -} - -void Loader::setupProjectFilePath(SetupProjectParameters ¶meters) -{ - QString projectFilePath = parameters.projectFilePath(); - if (projectFilePath.isEmpty()) - projectFilePath = QDir::currentPath(); - const QFileInfo projectFileInfo(projectFilePath); - if (!projectFileInfo.exists()) - throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath)); - if (projectFileInfo.isRelative()) - projectFilePath = projectFileInfo.absoluteFilePath(); - if (projectFileInfo.isFile()) { - parameters.setProjectFilePath(projectFilePath); - return; - } - if (!projectFileInfo.isDir()) - throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath)); - - const QStringList &actualFileNames - = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); - if (actualFileNames.empty()) { - QString error; - if (parameters.projectFilePath().isEmpty()) - error = Tr::tr("No project file given and none found in current directory.\n"); - else - error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath); - throw ErrorInfo(error); - } - if (actualFileNames.size() > 1) { - throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") - .arg(projectFilePath)); - } - projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front()); - - projectFilePath = QDir::current().filePath(projectFilePath); - projectFilePath = QDir::cleanPath(projectFilePath); - parameters.setProjectFilePath(projectFilePath); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp deleted file mode 100644 index ec7c38197..000000000 --- a/src/lib/corelib/language/moduleloader.cpp +++ /dev/null @@ -1,4142 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "moduleloader.h" - -#include "builtindeclarations.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "itemreader.h" -#include "language.h" -#include "modulemerger.h" -#include "qualifiedid.h" -#include "scriptengine.h" -#include "value.h" - -#include <api/languageinfo.h> -#include <language/language.h> -#include <logging/categories.h> -#include <logging/logger.h> -#include <logging/translator.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/jsliterals.h> -#include <tools/preferences.h> -#include <tools/profile.h> -#include <tools/profiling.h> -#include <tools/progressobserver.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/scripttools.h> -#include <tools/settings.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdebug.h> -#include <QtCore/qdir.h> -#include <QtCore/qdiriterator.h> -#include <QtCore/qjsondocument.h> -#include <QtCore/qjsonobject.h> -#include <QtCore/qtemporaryfile.h> -#include <QtCore/qtextstream.h> -#include <QtScript/qscriptvalueiterator.h> - -#include <algorithm> -#include <memory> -#include <utility> - -namespace qbs { -namespace Internal { - -static QString shadowProductPrefix() { return QStringLiteral("__shadow__"); } - -static void handlePropertyError(const ErrorInfo &error, const SetupProjectParameters ¶ms, - Logger &logger) -{ - if (params.propertyCheckingMode() == ErrorHandlingMode::Strict) - throw error; - logger.printWarning(error); -} - -class ModuleLoader::ItemModuleList : public QList<Item::Module> {}; - -static QString probeGlobalId(Item *probe) -{ - QString id; - - for (Item *obj = probe; obj; obj = obj->prototype()) { - if (!obj->id().isEmpty()) { - id = obj->id(); - break; - } - } - - if (id.isEmpty()) - return {}; - - QBS_CHECK(probe->file()); - return id + QLatin1Char('_') + probe->file()->filePath(); -} - -class ModuleLoader::ProductSortByDependencies -{ -public: - ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp) - { - } - - void apply() - { - QHash<QString, std::vector<ProductContext *>> productsMap; - QList<ProductContext *> allProducts; - for (ProjectContext * const projectContext : qAsConst(m_tlp.projects)) { - for (auto &product : projectContext->products) { - allProducts.push_back(&product); - productsMap[product.name].push_back(&product); - } - } - Set<ProductContext *> allDependencies; - for (auto productContext : qAsConst(allProducts)) { - auto &productDependencies = m_dependencyMap[productContext]; - for (const auto &dep : qAsConst(productContext->info.usedProducts)) { - QBS_CHECK(!dep.name.isEmpty()); - const auto &deps = productsMap.value(dep.name); - if (dep.profile == StringConstants::star()) { - QBS_CHECK(!deps.empty()); - for (ProductContext *depProduct : deps) { - if (depProduct == productContext) - continue; - productDependencies.push_back(depProduct); - allDependencies << depProduct; - } - } else { - auto it = std::find_if(deps.begin(), deps.end(), [&dep] (ProductContext *p) { - return p->multiplexConfigurationId == dep.multiplexConfigurationId; - }); - if (it == deps.end()) { - QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty()); - const QString productName = ResolvedProduct::fullDisplayName( - productContext->name, productContext->multiplexConfigurationId); - const QString depName = ResolvedProduct::fullDisplayName( - dep.name, dep.multiplexConfigurationId); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not " - "fulfilled.").arg(productName, depName), - productContext->item->location()); - } - productDependencies.push_back(*it); - allDependencies << *it; - } - } - } - const Set<ProductContext *> rootProducts - = Set<ProductContext *>::fromList(allProducts) - allDependencies; - for (ProductContext * const rootProduct : rootProducts) - traverse(rootProduct); - if (m_sortedProducts.size() < allProducts.size()) { - for (auto const product : qAsConst(allProducts)) { - QList<ModuleLoader::ProductContext *> path; - findCycle(product, path); - } - } - QBS_CHECK(m_sortedProducts.size() == allProducts.size()); - } - - // No product at position i has dependencies to a product at position j > i. - const QList<ProductContext *> &sortedProducts() const - { - return m_sortedProducts; - } - -private: - void traverse(ModuleLoader::ProductContext *product) - { - if (!m_seenProducts.insert(product).second) - return; - for (const auto &dependency : m_dependencyMap.value(product)) - traverse(dependency); - m_sortedProducts << product; - } - - void findCycle(ModuleLoader::ProductContext *product, - QList<ModuleLoader::ProductContext *> &path) - { - if (path.contains(product)) { - ErrorInfo error(Tr::tr("Cyclic dependencies detected.")); - for (const auto * const p : path) - error.append(p->name, p->item->location()); - error.append(product->name, product->item->location()); - throw error; - } - path << product; - for (auto const dep : m_dependencyMap.value(product)) - findCycle(dep, path); - path.removeLast(); - } - - TopLevelProjectContext &m_tlp; - QHash<ProductContext *, std::vector<ProductContext *>> m_dependencyMap; - Set<ProductContext *> m_seenProducts; - QList<ProductContext *> m_sortedProducts; -}; - -class SearchPathsManager { -public: - SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths) - : m_itemReader(itemReader) - { - m_itemReader->pushExtraSearchPaths(extraSearchPaths); - } - ~SearchPathsManager() { m_itemReader->popExtraSearchPaths(); } - -private: - ItemReader * const m_itemReader; -}; - -ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger) - : m_pool(nullptr) - , m_logger(logger) - , m_progressObserver(nullptr) - , m_reader(new ItemReader(logger)) - , m_evaluator(evaluator) -{ -} - -ModuleLoader::~ModuleLoader() -{ - delete m_reader; -} - -void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) -{ - m_progressObserver = progressObserver; -} - -void ModuleLoader::setSearchPaths(const QStringList &searchPaths) -{ - m_reader->setSearchPaths(searchPaths); - qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths; -} - -void ModuleLoader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) -{ - m_oldProjectProbes.clear(); - for (const ProbeConstPtr& probe : oldProbes) - m_oldProjectProbes[probe->globalId()] << probe; -} - -void ModuleLoader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) -{ - m_oldProductProbes = oldProbes; -} - -void ModuleLoader::setStoredProfiles(const QVariantMap &profiles) -{ - m_storedProfiles = profiles; -} - -void ModuleLoader::setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo) -{ - m_moduleProviderInfo = moduleProviderInfo; -} - -ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) -{ - TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"), - parameters.logElapsedTime()); - qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath(); - m_parameters = parameters; - m_modulePrototypes.clear(); - m_modulePrototypeEnabledInfo.clear(); - m_parameterDeclarations.clear(); - m_disabledItems.clear(); - m_reader->clearExtraSearchPathsStack(); - m_reader->setEnableTiming(parameters.logElapsedTime()); - m_elapsedTimeProbes = m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts - = m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies - = m_elapsedTimePropertyChecking = 0; - m_elapsedTimeProbes = 0; - m_probesEncountered = m_probesRun = m_probesCachedCurrent = m_probesCachedOld = 0; - m_settings = std::make_unique<Settings>(parameters.settingsDirectory()); - - const auto keys = m_parameters.overriddenValues().keys(); - for (const QString &key : keys) { - static const QStringList prefixes({ StringConstants::projectPrefix(), - QStringLiteral("projects"), - QStringLiteral("products"), QStringLiteral("modules"), - StringConstants::moduleProviders(), - StringConstants::qbsModule()}); - bool ok = false; - for (const auto &prefix : prefixes) { - if (key.startsWith(prefix + QLatin1Char('.'))) { - ok = true; - break; - } - } - if (ok) { - collectNameFromOverride(key); - continue; - } - ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(key)); - e.append(Tr::tr("Please use one of the following:")); - e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>." - "<property-name>:value")); - e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>." - "<property-name>:value")); - handlePropertyError(e, m_parameters, m_logger); - } - - ModuleLoaderResult result; - result.profileConfigs = m_storedProfiles; - m_pool = result.itemPool.get(); - m_reader->setPool(m_pool); - - const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree() - .value(StringConstants::projectPrefix()).toMap() - .value(StringConstants::qbsSearchPathsProperty()).toStringList(); - Item *root; - { - SearchPathsManager searchPathsManager(m_reader, topLevelSearchPaths); - root = loadItemFromFile(parameters.projectFilePath(), CodeLocation()); - if (!root) - return ModuleLoaderResult(); - } - - switch (root->type()) { - case ItemType::Product: - root = wrapInProjectIfNecessary(root); - break; - case ItemType::Project: - break; - default: - throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it" - " is of type '%1'.").arg(root->typeName()), root->location()); - } - - const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), - TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())); - root->setProperty(StringConstants::sourceDirectoryProperty(), - VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath())); - root->setProperty(StringConstants::buildDirectoryProperty(), - VariantValue::create(buildDirectory)); - root->setProperty(StringConstants::profileProperty(), - VariantValue::create(m_parameters.topLevelProfile())); - handleTopLevelProject(&result, root, buildDirectory, - Set<QString>() << QDir::cleanPath(parameters.projectFilePath())); - result.root = root; - result.qbsFiles = m_reader->filesRead() - m_tempQbsFiles; - for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) - result.profileConfigs.remove(it.key()); - printProfilingInfo(); - return result; -} - -class PropertyDeclarationCheck : public ValueHandler -{ - const Set<Item *> &m_disabledItems; - Set<Item *> m_handledItems; - std::vector<Item *> m_parentItems; - Item *m_currentModuleInstance = nullptr; - QualifiedId m_currentModuleName; - QString m_currentName; - SetupProjectParameters m_params; - Logger &m_logger; -public: - PropertyDeclarationCheck(const Set<Item *> &disabledItems, - const SetupProjectParameters ¶ms, Logger &logger) - : m_disabledItems(disabledItems) - , m_params(params) - , m_logger(logger) - { - } - - void operator()(Item *item) - { - handleItem(item); - } - -private: - void handle(JSSourceValue *value) override - { - if (!value->createdByPropertiesBlock()) { - const ErrorInfo error(Tr::tr("Property '%1' is not declared.") - .arg(m_currentName), value->location()); - handlePropertyError(error, m_params, m_logger); - } - } - - void handle(ItemValue *value) override - { - if (checkItemValue(value)) - handleItem(value->item()); - } - - bool checkItemValue(ItemValue *value) - { - // TODO: Remove once QBS-1030 is fixed. - if (parentItem()->type() == ItemType::Artifact) - return false; - - if (parentItem()->type() == ItemType::Properties) - return false; - - if (parentItem()->isOfTypeOrhasParentOfType(ItemType::Export)) { - // Export item prototypes do not have instantiated modules. - // The module instances are where the Export is used. - QBS_ASSERT(m_currentModuleInstance, return false); - auto hasCurrentModuleName = [this](const Item::Module &m) { - return m.name == m_currentModuleName; - }; - if (any_of(m_currentModuleInstance->modules(), hasCurrentModuleName)) - return true; - } - - // TODO: We really should have a dedicated item type for "pre-instantiated" item values - // and only use ModuleInstance for actual module instances. - const bool itemIsModuleInstance = value->item()->type() == ItemType::ModuleInstance - && value->item()->hasProperty(StringConstants::presentProperty()); - - if (!itemIsModuleInstance - && value->item()->type() != ItemType::ModulePrefix - && (!parentItem()->file() || !parentItem()->file()->idScope() - || !parentItem()->file()->idScope()->hasProperty(m_currentName)) - && !value->createdByPropertiesBlock()) { - CodeLocation location = value->location(); - for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i) - location = m_parentItems.at(i)->location(); - const ErrorInfo error(Tr::tr("Item '%1' is not declared. " - "Did you forget to add a Depends item?") - .arg(m_currentModuleName.toString()), location); - handlePropertyError(error, m_params, m_logger); - return false; - } - - return true; - } - - void handleItem(Item *item) - { - if (!m_handledItems.insert(item).second) - return; - if (m_disabledItems.contains(item) - || (item->type() == ItemType::ModuleInstance && !item->isPresentModule()) - || item->type() == ItemType::Properties - - // The Properties child of a SubProject item is not a regular item. - || item->type() == ItemType::PropertiesInSubProject) { - return; - } - - // If a module was found but its validate script failed, only the canonical - // module instance will have the "non-present" flag set, so we need to locate it. - if (item->type() == ItemType::ModuleInstance) { - const Item *productItem = nullptr; - for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) { - if ((*it)->type() == ItemType::Product) { - productItem = *it; - break; - } - } - if (productItem) { - for (const Item::Module &m : productItem->modules()) { - if (m.name == m_currentModuleName) { - if (!m.item->isPresentModule()) - return; - break; - } - } - } - } - - m_parentItems.push_back(item); - for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); - it != item->properties().constEnd(); ++it) { - if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() - && it.value()->type() == Value::ItemValueType) - continue; - const PropertyDeclaration decl = item->propertyDeclaration(it.key()); - if (decl.isValid()) { - if (!decl.isDeprecated()) - continue; - const DeprecationInfo &di = decl.deprecationInfo(); - QString message; - bool warningOnly; - if (decl.isExpired()) { - message = Tr::tr("The property '%1' can no longer be used. " - "It was removed in Qbs %2.") - .arg(decl.name(), di.removalVersion().toString()); - warningOnly = false; - } else { - message = Tr::tr("The property '%1' is deprecated and will be removed " - "in Qbs %2.").arg(decl.name(), di.removalVersion().toString()); - warningOnly = true; - } - ErrorInfo error(message, it.value()->location()); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - if (warningOnly) - m_logger.printWarning(error); - else - handlePropertyError(error, m_params, m_logger); - continue; - } - m_currentName = it.key(); - const QualifiedId oldModuleName = m_currentModuleName; - if (parentItem()->type() != ItemType::ModulePrefix) - m_currentModuleName.clear(); - m_currentModuleName.push_back(m_currentName); - it.value()->apply(this); - m_currentModuleName = oldModuleName; - } - m_parentItems.pop_back(); - for (Item * const child : item->children()) { - switch (child->type()) { - case ItemType::Export: - case ItemType::Depends: - case ItemType::Parameter: - case ItemType::Parameters: - break; - case ItemType::Group: - if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance) - break; - Q_FALLTHROUGH(); - default: - handleItem(child); - } - } - - // Properties that don't refer to an existing module with a matching Depends item - // only exist in the prototype of an Export item, not in the instance. - // Example 1 - setting a property of an unknown module: Export { abc.def: true } - // Example 2 - setting a non-existing Export property: Export { blubb: true } - if (item->type() == ItemType::ModuleInstance && item->prototype()) { - Item *oldInstance = m_currentModuleInstance; - m_currentModuleInstance = item; - handleItem(item->prototype()); - m_currentModuleInstance = oldInstance; - } - } - - void handle(VariantValue *) override { /* only created internally - no need to check */ } - - Item *parentItem() const { return m_parentItems.back(); } -}; - -void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, - const QString &buildDirectory, const Set<QString> &referencedFilePaths) -{ - TopLevelProjectContext tlp; - tlp.buildDirectory = buildDirectory; - handleProject(loadResult, &tlp, projectItem, referencedFilePaths); - checkProjectNamesInOverrides(tlp); - collectProductsByName(tlp); - checkProductNamesInOverrides(); - - adjustDependenciesForMultiplexing(tlp); - - m_dependencyResolvingPass = 1; - for (ProjectContext * const projectContext : qAsConst(tlp.projects)) { - m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack); - for (ProductContext &productContext : projectContext->products) { - try { - setupProductDependencies(&productContext, Set<DeferredDependsContext>()); - } catch (const ErrorInfo &err) { - if (productContext.name.isEmpty()) - throw err; - handleProductError(err, &productContext); - } - for (std::size_t i = 0; i < productContext.newlyAddedModuleProviderSearchPaths.size(); ++i) - m_reader->popExtraSearchPaths(); - productContext.newlyAddedModuleProviderSearchPaths.clear(); - } - } - if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) { - collectProductsByType(tlp); - m_dependencyResolvingPass = 2; - - // Doing the normalization for the Export items themselves (as opposed to doing it only - // for the corresponding module instances) serves two purposes: - // (1) It makes recursive use of Depends.productTypes via Export items work; otherwise, - // we'd need an additional dependency resolving pass for every export level. - // (2) The "expanded" Depends items are available to the Exporter.qbs module. - for (Item * const exportItem : m_exportsWithDeferredDependsItems) - normalizeDependencies(nullptr, DeferredDependsContext(nullptr, exportItem)); - - for (const auto &deferredDependsData : m_productsWithDeferredDependsItems) { - ProductContext * const productContext = deferredDependsData.first; - m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); - try { - setupProductDependencies(productContext, deferredDependsData.second); - } catch (const ErrorInfo &err) { - handleProductError(err, productContext); - } - } - } - - ProductSortByDependencies productSorter(tlp); - productSorter.apply(); - for (ProductContext * const p : productSorter.sortedProducts()) { - try { - handleProduct(p); - if (p->name.startsWith(shadowProductPrefix())) - tlp.probes << p->info.probes; - } catch (const ErrorInfo &err) { - handleProductError(err, p); - } - } - - loadResult->projectProbes = tlp.probes; - loadResult->moduleProviderInfo = m_moduleProviderInfo; - - m_reader->clearExtraSearchPathsStack(); - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimePropertyChecking : nullptr); - PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger); - check(projectItem); -} - -void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, - TopLevelProjectContext *topLevelProjectContext, Item *projectItem, - const Set<QString> &referencedFilePaths) -{ - QScopedPointer<ProjectContext> p(new ProjectContext); - auto &projectContext = *p; - projectContext.topLevelProject = topLevelProjectContext; - projectContext.result = loadResult; - ItemValuePtr itemValue = ItemValue::create(projectItem); - projectContext.scope = Item::create(m_pool, ItemType::Scope); - projectContext.scope->setFile(projectItem->file()); - projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); - ProductContext dummyProductContext; - dummyProductContext.project = &projectContext; - dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree(); - projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem)); - overrideItemProperties(projectItem, StringConstants::projectPrefix(), - m_parameters.overriddenValuesTree()); - projectContext.name = m_evaluator->stringValue(projectItem, - StringConstants::nameProperty()); - if (projectContext.name.isEmpty()) { - projectContext.name = FileInfo::baseName(projectItem->location().filePath()); - projectItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(projectContext.name)); - } - overrideItemProperties(projectItem, - StringConstants::projectsOverridePrefix() + projectContext.name, - m_parameters.overriddenValuesTree()); - if (!checkItemCondition(projectItem)) { - m_disabledProjects.insert(projectContext.name); - return; - } - p.take(); - topLevelProjectContext->projects.push_back(&projectContext); - m_reader->pushExtraSearchPaths(readExtraSearchPaths(projectItem) - << projectItem->file()->dirPath()); - projectContext.searchPathsStack = m_reader->extraSearchPathsStack(); - projectContext.item = projectItem; - - const QString minVersionStr - = m_evaluator->stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), - QStringLiteral("1.3.0")); - const Version minVersion = Version::fromString(minVersionStr); - if (!minVersion.isValid()) { - throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " - "is not a valid version string.").arg(minVersionStr), projectItem->location()); - } - if (!m_qbsVersion.isValid()) - m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); - if (m_qbsVersion < minVersion) { - throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " - "this is qbs version %2.").arg(minVersion.toString(), - m_qbsVersion.toString())); - } - - resolveProbes(&dummyProductContext, projectItem); - projectContext.topLevelProject->probes << dummyProductContext.info.probes; - - handleProfileItems(projectItem, &projectContext); - - QList<Item *> multiplexedProducts; - for (Item * const child : projectItem->children()) { - child->setScope(projectContext.scope); - if (child->type() == ItemType::Product) - multiplexedProducts << multiplexProductItem(&dummyProductContext, child); - } - for (Item * const additionalProductItem : qAsConst(multiplexedProducts)) - Item::addChild(projectItem, additionalProductItem); - - const QList<Item *> originalChildren = projectItem->children(); - for (Item * const child : originalChildren) { - switch (child->type()) { - case ItemType::Product: - prepareProduct(&projectContext, child); - break; - case ItemType::SubProject: - handleSubProject(&projectContext, child, referencedFilePaths); - break; - case ItemType::Project: - copyProperties(projectItem, child); - handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths); - break; - default: - break; - } - } - - const QStringList refs = m_evaluator->stringListValue( - projectItem, StringConstants::referencesProperty()); - const CodeLocation referencingLocation - = projectItem->property(StringConstants::referencesProperty())->location(); - QList<Item *> additionalProjectChildren; - for (const QString &filePath : refs) { - try { - additionalProjectChildren << loadReferencedFile(filePath, referencingLocation, - referencedFilePaths, dummyProductContext); - } catch (const ErrorInfo &error) { - if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } - } - for (Item * const subItem : qAsConst(additionalProjectChildren)) { - Item::addChild(projectContext.item, subItem); - switch (subItem->type()) { - case ItemType::Product: - prepareProduct(&projectContext, subItem); - break; - case ItemType::Project: - copyProperties(projectItem, subItem); - handleProject(loadResult, topLevelProjectContext, subItem, - Set<QString>(referencedFilePaths) << subItem->file()->filePath()); - break; - default: - break; - } - } - m_reader->popExtraSearchPaths(); -} - -QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const -{ - const auto &mprow = table.at(row); - QVariantMap multiplexConfiguration; - for (size_t column = 0; column < mprow.size(); ++column) { - const QString &propertyName = properties.at(column); - const VariantValuePtr &mpvalue = mprow.at(column); - multiplexConfiguration.insert(propertyName, mpvalue->value()); - } - return QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration) - .toJson(QJsonDocument::Compact) - .toBase64()); -} - -void qbs::Internal::ModuleLoader::ModuleLoader::dump(const ModuleLoader::MultiplexInfo &mpi) -{ - QStringList header; - for (const auto &str : mpi.properties) - header << str; - qDebug() << header; - - for (const auto &row : mpi.table) { - QVariantList values; - for (const auto &elem : row) { - values << elem->value(); - } - qDebug() << values; - } -} - -ModuleLoader::MultiplexTable ModuleLoader::combine(const MultiplexTable &table, - const MultiplexRow &values) -{ - MultiplexTable result; - if (table.empty()) { - result.resize(values.size()); - for (size_t i = 0; i < values.size(); ++i) { - MultiplexRow row; - row.resize(1); - row[0] = values.at(i); - result[i] = row; - } - } else { - for (const auto &row : table) { - for (const auto &value : values) { - MultiplexRow newRow = row; - newRow.push_back(value); - result.push_back(newRow); - } - } - } - return result; -} - -ModuleLoader::MultiplexInfo ModuleLoader::extractMultiplexInfo(Item *productItem, - Item *qbsModuleItem) -{ - static const QString mpmKey = QStringLiteral("multiplexMap"); - - const QScriptValue multiplexMap = m_evaluator->value(qbsModuleItem, mpmKey); - QStringList multiplexByQbsProperties = m_evaluator->stringListValue( - productItem, StringConstants::multiplexByQbsPropertiesProperty()); - - MultiplexInfo multiplexInfo; - multiplexInfo.aggregate = m_evaluator->boolValue( - productItem, StringConstants::aggregateProperty()); - - const QString multiplexedType = m_evaluator->stringValue( - productItem, StringConstants::multiplexedTypeProperty()); - if (!multiplexedType.isEmpty()) - multiplexInfo.multiplexedType = VariantValue::create(multiplexedType); - - Set<QString> uniqueMultiplexByQbsProperties; - for (const QString &key : multiplexByQbsProperties) { - const QString mappedKey = multiplexMap.property(key).toString(); - if (mappedKey.isEmpty()) - throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key)); - - if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) { - throw ErrorInfo(Tr::tr("Duplicate entry '%1' in Product.%2.") - .arg(mappedKey, StringConstants::multiplexByQbsPropertiesProperty()), - productItem->location()); - } - - const QScriptValue arr = m_evaluator->value(qbsModuleItem, key); - if (arr.isUndefined()) - continue; - if (!arr.isArray()) - throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key)); - - const quint32 arrlen = arr.property(StringConstants::lengthProperty()).toUInt32(); - if (arrlen == 0) - continue; - - MultiplexRow mprow; - mprow.resize(arrlen); - Set<QVariant> entriesForKey; - for (quint32 i = 0; i < arrlen; ++i) { - const QVariant value = arr.property(i).toVariant(); - if (!entriesForKey.insert(value).second) { - throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.") - .arg(value.toString(), key), productItem->location()); - } - mprow[i] = VariantValue::create(value); - } - multiplexInfo.table = combine(multiplexInfo.table, mprow); - multiplexInfo.properties.push_back(mappedKey); - } - return multiplexInfo; -} - -template <typename T, typename F> -T ModuleLoader::callWithTemporaryBaseModule(ProductContext *productContext, const F &func) -{ - // Temporarily attach the qbs module here, in case we need to access one of its properties - // to evaluate properties. - const QString &qbsKey = StringConstants::qbsModule(); - Item *productItem = productContext->item; - ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later. - if (qbsValue) - qbsValue = qbsValue->clone(); - const Item::Module qbsModule = loadBaseModule(productContext, productItem); - productItem->addModule(qbsModule); - - auto &&result = func(qbsModule); - - // "Unload" the qbs module again. - if (qbsValue) - productItem->setProperty(qbsKey, qbsValue); - else - productItem->removeProperty(qbsKey); - productItem->removeModules(); - - return std::forward<T>(result); -} - -QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem) -{ - QString productName; - dummyContext->item = productItem; - auto extractMultiplexInfoFromProduct - = [this, productItem, &productName](const Item::Module &qbsModule) { - // Overriding the product item properties must be done here already, because multiplexing - // properties might depend on product properties. - const QString &nameKey = StringConstants::nameProperty(); - productName = m_evaluator->stringValue(productItem, nameKey); - if (productName.isEmpty()) { - productName = FileInfo::completeBaseName(productItem->file()->filePath()); - productItem->setProperty(nameKey, VariantValue::create(productName)); - } - overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName, - m_parameters.overriddenValuesTree()); - - return extractMultiplexInfo(productItem, qbsModule.item); - }; - const MultiplexInfo multiplexInfo - = callWithTemporaryBaseModule<const MultiplexInfo>(dummyContext, - extractMultiplexInfoFromProduct); - - if (multiplexInfo.table.size() > 1) - productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue()); - - VariantValuePtr productNameValue = VariantValue::create(productName); - - Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr; - QList<Item *> additionalProductItems; - std::vector<VariantValuePtr> multiplexConfigurationIdValues; - for (size_t row = 0; row < multiplexInfo.table.size(); ++row) { - Item *item = productItem; - const auto &mprow = multiplexInfo.table.at(row); - QBS_CHECK(mprow.size() == multiplexInfo.properties.size()); - if (row > 0) { - item = productItem->clone(); - additionalProductItems.push_back(item); - } - const QString multiplexConfigurationId = multiplexInfo.toIdString(row); - const VariantValuePtr multiplexConfigurationIdValue - = VariantValue::create(multiplexConfigurationId); - if (multiplexInfo.table.size() > 1 || aggregator) { - multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue); - item->setProperty(StringConstants::multiplexConfigurationIdProperty(), - multiplexConfigurationIdValue); - } - if (multiplexInfo.multiplexedType) - item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType); - for (size_t column = 0; column < mprow.size(); ++column) { - Item *qbsItem = moduleInstanceItem(item, StringConstants::qbsModule()); - const QString &propertyName = multiplexInfo.properties.at(column); - const VariantValuePtr &mpvalue = mprow.at(column); - qbsItem->setProperty(propertyName, mpvalue); - } - } - - if (aggregator) { - additionalProductItems << aggregator; - - // Add dependencies to all multiplexed instances. - for (const auto &v : multiplexConfigurationIdValues) { - Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends); - dependsItem->setProperty(StringConstants::nameProperty(), productNameValue); - dependsItem->setProperty(StringConstants::multiplexConfigurationIdProperty(), v); - dependsItem->setProperty(StringConstants::profilesProperty(), - VariantValue::create(QStringList())); - Item::addChild(aggregator, dependsItem); - } - } - - return additionalProductItems; -} - -void ModuleLoader::normalizeDependencies(ProductContext *product, - const DeferredDependsContext &dependsContext) -{ - std::vector<Item *> dependsItemsToAdd; - std::vector<Item *> dependsItemsToRemove; - std::vector<Item *> deferredDependsItems; - for (Item *dependsItem : dependsContext.parentItem->children()) { - if (dependsItem->type() != ItemType::Depends) - continue; - bool productTypesIsSet; - const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem, - StringConstants::productTypesProperty(), &productTypesIsSet); - if (productTypesIsSet) { - bool nameIsSet; - m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), QString(), - &nameIsSet); - - // The second condition is for the case where the dependency comes from an Export item - // that has itself been normalized in the mean time. - if (nameIsSet && !dependsItem->variantProperty(StringConstants::nameProperty())) { - throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually " - "exclusive."), dependsItem->location()); - } - - bool submodulesPropertySet; - m_evaluator->stringListValue( dependsItem, StringConstants::submodulesProperty(), - &submodulesPropertySet); - if (submodulesPropertySet) { - throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are " - "mutually exclusive."), dependsItem->location()); - } - - // We ignore the "limitToSubProject" property for dependencies from Export items, - // because we cannot make it work consistently, as the importing product is not - // yet known when normalizing via an Export item. - const bool limitToSubProject = dependsContext.parentItem->type() == ItemType::Product - && m_evaluator->boolValue(dependsItem, - StringConstants::limitToSubProjectProperty()); - static const auto hasSameSubProject - = [](const ProductContext &product, const ProductContext &other) { - for (const Item *otherParent = other.item->parent(); otherParent; - otherParent = otherParent->parent()) { - if (otherParent == product.item->parent()) - return true; - } - return false; - }; - std::vector<const ProductContext *> matchingProducts; - for (const FileTag &typeTag : productTypes) { - const auto range = m_productsByType.equal_range(typeTag); - for (auto it = range.first; it != range.second; ++it) { - if (it->second != product - && (!product || it->second->name != product->name) - && (!limitToSubProject || hasSameSubProject(*product, *it->second))) { - matchingProducts.push_back(it->second); - } - } - } - if (matchingProducts.empty()) { - qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything." - << dependsItem->location(); - dependsItemsToRemove.push_back(dependsItem); - continue; - } - if (dependsContext.parentItem->type() != ItemType::Export) - deferredDependsItems.push_back(dependsItem); - for (std::size_t i = 1; i < matchingProducts.size(); ++i) { - Item * const dependsClone = dependsItem->clone(); - dependsClone->setProperty(StringConstants::nameProperty(), - VariantValue::create(matchingProducts.at(i)->name)); - dependsItemsToAdd.push_back(dependsClone); - if (dependsContext.parentItem->type() != ItemType::Export) - deferredDependsItems.push_back(dependsClone); - - } - dependsItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(matchingProducts.front()->name)); - } - } - for (Item * const newDependsItem : dependsItemsToAdd) - Item::addChild(dependsContext.parentItem, newDependsItem); - for (Item * const dependsItem : dependsItemsToRemove) - Item::removeChild(dependsContext.parentItem, dependsItem); - if (!deferredDependsItems.empty()) { - auto &allDeferredDependsItems - = product->deferredDependsItems[dependsContext.exportingProductItem]; - allDeferredDependsItems.insert(allDeferredDependsItems.end(), deferredDependsItems.cbegin(), - deferredDependsItems.cend()); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp) -{ - for (const ProjectContext * const project : tlp.projects) { - for (const ProductContext &product : project->products) - adjustDependenciesForMultiplexing(product); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product) -{ - for (Item *dependsItem : product.item->children()) { - if (dependsItem->type() == ItemType::Depends) - adjustDependenciesForMultiplexing(product, dependsItem); - } -} - -void ModuleLoader::adjustDependenciesForMultiplexing(const ProductContext &product, - Item *dependsItem) -{ - const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty()); - const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty(); - if (name == product.name) { - QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator. - return; - } - - bool profilesPropertyIsSet; - const QStringList profiles = m_evaluator->stringListValue(dependsItem, - StringConstants::profilesProperty(), &profilesPropertyIsSet); - - const auto productRange = m_productsByName.equal_range(name); - std::vector<const ProductContext *> dependencies; - bool hasNonMultiplexedDependency = false; - for (auto it = productRange.first; it != productRange.second; ++it) { - if (!it->second->multiplexConfigurationId.isEmpty()) { - dependencies.push_back(it->second); - if (productIsMultiplexed && !profilesPropertyIsSet) - break; - } else { - hasNonMultiplexedDependency = true; - break; - } - } - - // These are the allowed cases: - // (1) Normal dependency with no multiplexing whatsoever. - // (2) Both product and dependency are multiplexed. - // (3) The product is not multiplexed, but the dependency is. - // (3a) The dependency has an aggregator. We want to depend on the aggregator. - // (3b) The dependency does not have an aggregator. We want to depend on all the - // multiplexed variants. - // (4) The product is multiplexed, but the dependency is not. This case is implicitly - // handled, because we don't have to adapt any Depends items. - // (5) The product is a "shadow product". In that case, we know which product - // it should have a dependency on, and we make sure we depend on that. - - // (1) and (3a) - if (!productIsMultiplexed && hasNonMultiplexedDependency) - return; - - QStringList multiplexIds; - const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product); - const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name; - for (const ProductContext *dependency : dependencies) { - const bool depMatchesShadowProduct = isShadowProduct - && dependency->item == product.item->parent(); - const QString depMultiplexId = dependency->multiplexConfigurationId; - if (depMatchesShadowProduct) { // (5) - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - VariantValue::create(depMultiplexId)); - multiplexIds.clear(); - break; - } - if (productIsMultiplexed && !profilesPropertyIsSet) { // (2) - const ValuePtr &multiplexId = product.item->property( - StringConstants::multiplexConfigurationIdProperty()); - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - multiplexId); - break; - } - - // (3b) (or (2) if Depends.profiles is set). - const bool profileMatch = !profilesPropertyIsSet || profiles.empty() - || profiles.contains(dependency->profileName); - if (profileMatch) - multiplexIds << depMultiplexId; - } - if (!multiplexIds.empty()) { - dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), - VariantValue::create(multiplexIds)); - } -} - -void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimePrepareProducts : nullptr); - checkCancelation(); - qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); - - ProductContext productContext; - productContext.item = productItem; - productContext.project = projectContext; - productContext.name = m_evaluator->stringValue(productItem, StringConstants::nameProperty()); - QBS_CHECK(!productContext.name.isEmpty()); - const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule()); - if (!!qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { - qbsItemValue->item()->setProperty(StringConstants::nameProperty(), - VariantValue::create(StringConstants::nameProperty())); - auto evaluateQbsProfileProperty = [this](const Item::Module &qbsModule) { - return m_evaluator->stringValue(qbsModule.item, - StringConstants::profileProperty(), QString()); - }; - productContext.profileName - = callWithTemporaryBaseModule<QString>(&productContext, - evaluateQbsProfileProperty); - } else { - productContext.profileName = m_parameters.topLevelProfile(); - } - productContext.multiplexConfigurationId = m_evaluator->stringValue( - productItem, StringConstants::multiplexConfigurationIdProperty()); - QBS_CHECK(!productContext.profileName.isEmpty()); - const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName); - if (it == projectContext->result->profileConfigs.constEnd()) { - const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles); - if (!profile.exists()) { - ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()), - productItem->location()); - handleProductError(error, &productContext); - return; - } - const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( - profile, m_parameters.configurationName()); - productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( - buildConfig, m_parameters.overriddenValues()); - projectContext->result->profileConfigs.insert(productContext.profileName, - productContext.moduleProperties); - } else { - productContext.moduleProperties = it.value().toMap(); - } - initProductProperties(productContext); - - ItemValuePtr itemValue = ItemValue::create(productItem); - productContext.scope = Item::create(m_pool, ItemType::Scope); - productContext.scope->setProperty(StringConstants::productVar(), itemValue); - productContext.scope->setFile(productItem->file()); - productContext.scope->setScope(productContext.project->scope); - - const bool hasExportItems = mergeExportItems(productContext); - - setScopeForDescendants(productItem, productContext.scope); - - projectContext->products.push_back(productContext); - - if (!hasExportItems || getShadowProductInfo(productContext).first) - return; - - // This "shadow product" exists only to pull in a dependency on the actual product - // and nothing else, thus providing us with the pure environment that we need to - // evaluate the product's exported properties in isolation in the project resolver. - Item * const importer = Item::create(productItem->pool(), ItemType::Product); - importer->setProperty(QStringLiteral("name"), - VariantValue::create(shadowProductPrefix() + productContext.name)); - importer->setFile(productItem->file()); - importer->setLocation(productItem->location()); - importer->setScope(projectContext->scope); - importer->setupForBuiltinType(m_logger); - Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends); - dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); - dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); - dependsItem->setFile(importer->file()); - dependsItem->setLocation(importer->location()); - dependsItem->setupForBuiltinType(m_logger); - Item::addChild(importer, dependsItem); - Item::addChild(productItem, importer); - prepareProduct(projectContext, importer); -} - -void ModuleLoader::setupProductDependencies(ProductContext *productContext, - const Set<DeferredDependsContext> &deferredDependsContext) -{ - if (m_dependencyResolvingPass == 2) { - for (const DeferredDependsContext &ctx : deferredDependsContext) - normalizeDependencies(productContext, ctx); - for (const auto &deferralData : productContext->deferredDependsItems) { - for (Item * const deferredDependsItem : deferralData.second) { - - // Dependencies from Export items are handled in addProductModuleDependencies(). - if (deferredDependsItem->parent() == productContext->item) - adjustDependenciesForMultiplexing(*productContext, deferredDependsItem); - } - } - } - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimeProductDependencies : nullptr); - checkCancelation(); - Item *item = productContext->item; - qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name - << productContext->item->location(); - - if (m_dependencyResolvingPass == 1) - setSearchPathsForProduct(productContext); - SearchPathsManager searchPathsManager(m_reader, productContext->searchPaths); - - DependsContext dependsContext; - dependsContext.product = productContext; - dependsContext.productDependencies = &productContext->info.usedProducts; - resolveDependencies(&dependsContext, item, productContext); - if (m_dependencyResolvingPass == 2 - || !containsKey(m_productsWithDeferredDependsItems, productContext)) { - addProductModuleDependencies(productContext); - } - productContext->project->result->productInfos.insert(item, productContext->info); -} - -// Leaf modules first. -// TODO: Can this be merged with addTransitiveDependencies? Looks suspiciously similar. -void ModuleLoader::createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules) -{ - if (std::find_if(modules.cbegin(), modules.cend(), - [parentModule](const Item::Module &m) { return m.name == parentModule.name;}) - != modules.cend()) { - return; - } - for (const Item::Module &dep : parentModule.item->modules()) - createSortedModuleList(dep, modules); - modules.push_back(parentModule); - return; -} - -Item::Modules ModuleLoader::modulesSortedByDependency(const Item *productItem) -{ - QBS_CHECK(productItem->type() == ItemType::Product); - Item::Modules sortedModules; - const Item::Modules &unsortedModules = productItem->modules(); - for (const Item::Module &module : unsortedModules) - createSortedModuleList(module, sortedModules); - QBS_CHECK(sortedModules.size() == unsortedModules.size()); - - // Make sure the top-level items stay the same. - for (Item::Module &s : sortedModules) { - for (const Item::Module &u : unsortedModules) { - if (s.name == u.name) { - s.item = u.item; - break; - } - } - } - return sortedModules; -} - - -template<typename T> bool insertIntoSet(Set<T> &set, const T &value) -{ - const auto insertionResult = set.insert(value); - return insertionResult.second; -} - -void ModuleLoader::setupReverseModuleDependencies(const Item::Module &module, - ModuleDependencies &deps, - QualifiedIdSet &seenModules) -{ - if (!insertIntoSet(seenModules, module.name)) - return; - for (const Item::Module &m : module.item->modules()) { - deps[m.name].insert(module.name); - setupReverseModuleDependencies(m, deps, seenModules); - } -} - -ModuleLoader::ModuleDependencies ModuleLoader::setupReverseModuleDependencies(const Item *product) -{ - ModuleDependencies deps; - QualifiedIdSet seenModules; - for (const Item::Module &m : product->modules()) - setupReverseModuleDependencies(m, deps, seenModules); - return deps; -} - -void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() ? &m_elapsedTimeHandleProducts : nullptr); - if (productContext->info.delayedError.hasError()) - return; - - Item * const item = productContext->item; - - m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); - SearchPathsManager searchPathsManager(m_reader, productContext->searchPaths); - addTransitiveDependencies(productContext); - - // It is important that dependent modules are merged after their dependency, because - // the dependent module's merger potentially needs to replace module items that were - // set by the dependency module's merger (namely, scopes of defining items; see - // ModuleMerger::replaceItemInScopes()). - Item::Modules topSortedModules = modulesSortedByDependency(item); - for (Item::Module &module : topSortedModules) - ModuleMerger(m_logger, item, module).start(); - - // Re-sort the modules by name. This is more stable; see QBS-818. - // The list of modules in the product now has the same order as before, - // only the items have been replaced by their merged counterparts. - Item::Modules lexicographicallySortedModules = topSortedModules; - std::sort(lexicographicallySortedModules.begin(), lexicographicallySortedModules.end()); - item->setModules(lexicographicallySortedModules); - - for (const Item::Module &module : topSortedModules) { - if (!module.item->isPresentModule()) - continue; - try { - resolveProbes(productContext, module.item); - if (module.versionRange.minimum.isValid() - || module.versionRange.maximum.isValid()) { - if (module.versionRange.maximum.isValid() - && module.versionRange.minimum >= module.versionRange.maximum) { - throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module " - "'%3'").arg(module.versionRange.minimum.toString(), - module.versionRange.maximum.toString(), - module.name.toString())); - } - const Version moduleVersion = Version::fromString( - m_evaluator->stringValue(module.item, - StringConstants::versionProperty())); - if (moduleVersion < module.versionRange.minimum) { - throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " - "at least %3.").arg(module.name.toString(), - moduleVersion.toString(), - module.versionRange.minimum.toString())); - } - if (module.versionRange.maximum.isValid() - && moduleVersion >= module.versionRange.maximum) { - throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " - "lower than %3.").arg(module.name.toString(), - moduleVersion.toString(), - module.versionRange.maximum.toString())); - } - } - } catch (const ErrorInfo &error) { - handleModuleSetupError(productContext, module, error); - if (productContext->info.delayedError.hasError()) - return; - } - } - - resolveProbes(productContext, item); - - // Module validation must happen in an extra pass, after all Probes have been resolved. - EvalCacheEnabler cacheEnabler(m_evaluator); - for (const Item::Module &module : topSortedModules) { - if (!module.item->isPresentModule()) - continue; - try { - m_evaluator->boolValue(module.item, StringConstants::validateProperty()); - } catch (const ErrorInfo &error) { - handleModuleSetupError(productContext, module, error); - if (productContext->info.delayedError.hasError()) - return; - } - } - - if (!checkItemCondition(item)) { - const auto &exportsData = productContext->project->topLevelProject->productModules; - for (auto it = exportsData.find(productContext->name); - it != exportsData.end() && it.key() == productContext->name; ++it) { - if (it.value().multiplexId == productContext->multiplexConfigurationId) { - createNonPresentModule(productContext->name, QStringLiteral("disabled"), - it.value().exportItem); - break; - } - } - } - - checkDependencyParameterDeclarations(productContext); - copyGroupsFromModulesToProduct(*productContext); - - ModuleDependencies reverseModuleDeps; - for (Item * const child : item->children()) { - if (child->type() == ItemType::Group) { - if (reverseModuleDeps.empty()) - reverseModuleDeps = setupReverseModuleDependencies(item); - handleGroup(productContext, child, reverseModuleDeps); - } - } - productContext->project->result->productInfos.insert(item, productContext->info); -} - -static Item *rootPrototype(Item *item) -{ - Item *modulePrototype = item; - while (modulePrototype->prototype()) - modulePrototype = modulePrototype->prototype(); - return modulePrototype; -} - -class DependencyParameterDeclarationCheck -{ -public: - DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem, - const QHash<const Item *, Item::PropertyDeclarationMap> &decls) - : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls) - { - } - - void operator()(const QVariantMap ¶meters) const - { - check(parameters, QualifiedId()); - } - -private: - void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const - { - for (auto it = parameters.begin(); it != parameters.end(); ++it) { - if (it.value().type() == QVariant::Map) { - check(it.value().toMap(), QualifiedId(moduleName) << it.key()); - } else { - const auto &deps = m_productItem->modules(); - auto m = std::find_if(deps.begin(), deps.end(), - [&moduleName] (const Item::Module &module) { - return module.name == moduleName; - }); - - if (m == deps.end()) { - const QualifiedId fullName = QualifiedId(moduleName) << it.key(); - throw ErrorInfo(Tr::tr("Cannot set parameter '%1', " - "because '%2' does not have a dependency on '%3'.") - .arg(fullName.toString(), m_productName, moduleName.toString()), - m_productItem->location()); - } - - auto decls = m_parameterDeclarations.value(rootPrototype(m->item)); - - if (!decls.contains(it.key())) { - const QualifiedId fullName = QualifiedId(moduleName) << it.key(); - throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.") - .arg(fullName.toString()), m_productItem->location()); - } - } - } - } - - bool moduleExists(const QualifiedId &name) const - { - const auto &deps = m_productItem->modules(); - return any_of(deps, [&name](const Item::Module &module) { - return module.name == name; - }); - } - - const QString &m_productName; - const Item *m_productItem; - const QHash<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations; -}; - -void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext *productContext) const -{ - DependencyParameterDeclarationCheck dpdc(productContext->name, productContext->item, - m_parameterDeclarations); - for (const Item::Module &dep : productContext->item->modules()) { - if (!dep.parameters.empty()) - dpdc(dep.parameters); - } -} - -void ModuleLoader::handleModuleSetupError(ModuleLoader::ProductContext *productContext, - const Item::Module &module, const ErrorInfo &error) -{ - if (module.required) { - handleProductError(error, productContext); - } else { - qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString() - << "found, but not usable in product" << productContext->name - << error.toString(); - createNonPresentModule(module.name.toString(), QStringLiteral("failed validation"), - module.item); - } -} - -void ModuleLoader::initProductProperties(const ProductContext &product) -{ - QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, - product.multiplexConfigurationId); - buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); - product.item->setProperty(StringConstants::buildDirectoryProperty(), - VariantValue::create(buildDir)); - const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); - product.item->setProperty(StringConstants::sourceDirectoryProperty(), - VariantValue::create(sourceDir)); -} - -void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem, - const Set<QString> &referencedFilePaths) -{ - qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); - - Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); - if (!checkItemCondition(projectItem)) - return; - if (propertiesItem) { - propertiesItem->setScope(projectItem); - if (!checkItemCondition(propertiesItem)) - return; - } - - Item *loadedItem; - QString subProjectFilePath; - try { - const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); - const QString relativeFilePath - = m_evaluator->stringValue(projectItem, StringConstants::filePathProperty()); - subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); - if (referencedFilePaths.contains(subProjectFilePath)) - throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") - .arg(relativeFilePath), projectItem->location()); - loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location()); - } catch (const ErrorInfo &error) { - if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - return; - } - - loadedItem = wrapInProjectIfNecessary(loadedItem); - const bool inheritProperties = m_evaluator->boolValue( - projectItem, StringConstants::inheritPropertiesProperty()); - - if (inheritProperties) - copyProperties(projectItem->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())); - } - } - - Item::addChild(projectItem, loadedItem); - projectItem->setScope(projectContext->scope); - handleProject(projectContext->result, projectContext->topLevelProject, loadedItem, - Set<QString>(referencedFilePaths) << subProjectFilePath); -} - -QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, - ModuleLoader::ProductContext &dummyContext) -{ - QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), - relativePath); - if (FileInfo(absReferencePath).isDir()) { - QString qbsFilePath; - - QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); - while (dit.hasNext()) { - if (!qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " - "qbs file.").arg(absReferencePath), referencingLocation); - } - qbsFilePath = dit.next(); - } - if (qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") - .arg(absReferencePath), referencingLocation); - } - absReferencePath = qbsFilePath; - } - if (referencedFilePaths.contains(absReferencePath)) - throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), - referencingLocation); - Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation); - if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { - ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") - .arg(subItem->typeName())); - error.append(Tr::tr("Item is defined here."), subItem->location()); - error.append(Tr::tr("File is referenced here."), referencingLocation); - throw error; - } - subItem->setScope(dummyContext.project->scope); - subItem->setParent(dummyContext.project->item); - QList<Item *> loadedItems; - loadedItems << subItem; - if (subItem->type() == ItemType::Product) { - handleProfileItems(subItem, dummyContext.project); - loadedItems << multiplexProductItem(&dummyContext, subItem); - } - return loadedItems; -} - -void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies) -{ - checkCancelation(); - propagateModulesFromParent(productContext, groupItem, reverseDepencencies); - checkItemCondition(groupItem); - for (Item * const child : groupItem->children()) { - if (child->type() == ItemType::Group) - handleGroup(productContext, child, reverseDepencencies); - } -} - -void ModuleLoader::handleAllPropertyOptionsItems(Item *item) -{ - QList<Item *> childItems = item->children(); - auto childIt = childItems.begin(); - while (childIt != childItems.end()) { - Item * const child = *childIt; - if (child->type() == ItemType::PropertyOptions) { - handlePropertyOptions(child); - childIt = childItems.erase(childIt); - } else { - handleAllPropertyOptionsItems(child); - ++childIt; - } - } - item->setChildren(childItems); -} - -void ModuleLoader::handlePropertyOptions(Item *optionsItem) -{ - const QString name = m_evaluator->stringValue(optionsItem, StringConstants::nameProperty()); - if (name.isEmpty()) { - throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"), - optionsItem->location()); - } - const QString description = m_evaluator->stringValue( - optionsItem, StringConstants::descriptionProperty()); - const auto removalVersion = Version::fromString(m_evaluator->stringValue(optionsItem, - StringConstants::removalVersionProperty())); - PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name); - if (!decl.isValid()) { - decl.setName(name); - decl.setType(PropertyDeclaration::Variant); - } - decl.setDescription(description); - if (removalVersion.isValid()) { - DeprecationInfo di(removalVersion, description); - decl.setDeprecationInfo(di); - } - const ValuePtr property = optionsItem->parent()->property(name); - if (!property && !decl.isExpired()) { - throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'") - .arg(name), optionsItem->location()); - } - if (property && decl.isExpired()) { - ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but " - "is still present.") - .arg(name).arg(removalVersion.toString()), - property->location()); - e.append(Tr::tr("Removal version for '%1' specified here.").arg(name), - optionsItem->location()); - m_logger.printWarning(e); - } - optionsItem->parent()->setPropertyDeclaration(name, decl); -} - -static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) -{ - if (value->type() == Value::ItemValueType) { - const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value); - const Item * const valueItem = itemValue->item(); - Item * const subItem = dst->itemProperty(name, itemValue)->item(); - for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin(); - it != valueItem->properties().constEnd(); ++it) - mergeProperty(subItem, it.key(), it.value()); - } else { - // If the property already exists set up the base value. - if (value->type() == Value::JSSourceValueType) { - const auto jsValue = static_cast<JSSourceValue *>(value.get()); - if (jsValue->isBuiltinDefaultValue()) - return; - const ValuePtr baseValue = dst->property(name); - if (baseValue) { - QBS_CHECK(baseValue->type() == Value::JSSourceValueType); - const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>( - baseValue->clone()); - jsValue->setBaseValue(jsBaseValue); - std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives(); - jsValue->clearAlternatives(); - for (JSSourceValue::Alternative &a : alternatives) { - a.value->setBaseValue(jsBaseValue); - jsValue->addAlternative(a); - } - } - } - dst->setProperty(name, value); - } -} - -bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext) -{ - class ScopeHandler { - public: - ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) - : m_exportItem(exportItem) - { - if (!*cachedScopeItem) - *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope); - Item * const scope = *cachedScopeItem; - QBS_CHECK(productContext.item->file()); - scope->setFile(productContext.item->file()); - scope->setScope(productContext.item); - productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); - productContext.scope->copyProperty(StringConstants::productVar(), scope); - QBS_CHECK(!exportItem->scope()); - exportItem->setScope(scope); - } - ~ScopeHandler() { m_exportItem->setScope(nullptr); } - - private: - Item * const m_exportItem; - } scopeHandler(exportItem, productContext, &m_tempScopeItem); - return checkItemCondition(exportItem); -} - -ProbeConstPtr ModuleLoader::findOldProjectProbe( - const QString &globalId, - bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const -{ - if (m_parameters.forceProbeExecution()) - return {}; - - for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) { - if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) - return oldProbe; - } - - return {}; -} - -ProbeConstPtr ModuleLoader::findOldProductProbe( - const QString &productName, - bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const -{ - if (m_parameters.forceProbeExecution()) - return {}; - - for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) { - if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) - return oldProbe; - } - - return {}; -} - -ProbeConstPtr ModuleLoader::findCurrentProbe( - const CodeLocation &location, - bool condition, - const QVariantMap &initialProperties) const -{ - const QList<ProbeConstPtr> &cachedProbes = m_currentProbes.value(location); - for (const ProbeConstPtr &probe : cachedProbes) { - if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No)) - return probe; - } - return {}; -} - -bool ModuleLoader::probeMatches(const ProbeConstPtr &probe, bool condition, - const QVariantMap &initialProperties, const QString &configureScript, - CompareScript compareScript) const -{ - return probe->condition() == condition - && probe->initialProperties() == initialProperties - && (compareScript == CompareScript::No - || (probe->configureScript() == configureScript - && !probe->needsReconfigure(m_lastResolveTime))); -} - -void ModuleLoader::printProfilingInfo() -{ - if (!m_parameters.logElapsedTime()) - return; - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Project file loading and parsing took %1.") - .arg(elapsedTimeString(m_reader->elapsedTime())); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Preparing products took %1.") - .arg(elapsedTimeString(m_elapsedTimePrepareProducts)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Setting up product dependencies took %1.") - .arg(elapsedTimeString(m_elapsedTimeProductDependencies)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("Setting up transitive product dependencies took %1.") - .arg(elapsedTimeString(m_elapsedTimeTransitiveDependencies)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Handling products took %1.") - .arg(elapsedTimeString(m_elapsedTimeHandleProducts)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("Running Probes took %1.") - .arg(elapsedTimeString(m_elapsedTimeProbes)); - m_logger.qbsLog(LoggerInfo, true) << "\t\t" - << Tr::tr("%1 probes encountered, %2 configure scripts executed, " - "%3 re-used from current run, %4 re-used from earlier run.") - .arg(m_probesEncountered).arg(m_probesRun).arg(m_probesCachedCurrent) - .arg(m_probesCachedOld); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Property checking took %1.") - .arg(elapsedTimeString(m_elapsedTimePropertyChecking)); -} - -static void mergeParameters(QVariantMap &dst, const QVariantMap &src) -{ - for (auto it = src.begin(); it != src.end(); ++it) { - if (it.value().type() == QVariant::Map) { - QVariant &vdst = dst[it.key()]; - QVariantMap mdst = vdst.toMap(); - mergeParameters(mdst, it.value().toMap()); - vdst = mdst; - } else { - dst[it.key()] = it.value(); - } - } -} - -static void adjustParametersScopes(Item *item, Item *scope) -{ - if (item->type() == ItemType::ModuleParameters) { - item->setScope(scope); - return; - } - - for (const auto &value : item->properties()) { - if (value->type() != Value::ItemValueType) - continue; - adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope); - } -} - -bool ModuleLoader::mergeExportItems(const ProductContext &productContext) -{ - std::vector<Item *> exportItems; - QList<Item *> children = productContext.item->children(); - for (int i = 0; i < children.size();) { - Item * const child = children.at(i); - if (child->type() == ItemType::Export) { - exportItems.push_back(child); - children.removeAt(i); - } else { - ++i; - } - } - - // Note that we do not return if there are no Export items: The "merged" item becomes the - // "product module", which always needs to exist, regardless of whether the product sources - // actually contain an Export item or not. - if (!exportItems.empty()) - productContext.item->setChildren(children); - - Item *merged = Item::create(productContext.item->pool(), ItemType::Export); - const QString &nameKey = StringConstants::nameProperty(); - const ValuePtr nameValue = VariantValue::create(productContext.name); - merged->setProperty(nameKey, nameValue); - Set<FileContextConstPtr> filesWithExportItem; - ProductModuleInfo pmi; - bool hasDependenciesOnProductType = false; - for (Item * const exportItem : qAsConst(exportItems)) { - checkCancelation(); - if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) - throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), - exportItem->location()); - exportItem->setProperty(nameKey, nameValue); - if (!checkExportItemCondition(exportItem, productContext)) - continue; - filesWithExportItem += exportItem->file(); - for (Item * const child : exportItem->children()) { - if (child->type() == ItemType::Parameters) { - adjustParametersScopes(child, child); - mergeParameters(pmi.defaultParameters, - m_evaluator->scriptValue(child).toVariant().toMap()); - } else { - if (child->type() == ItemType::Depends) { - bool productTypesIsSet; - m_evaluator->stringValue(child, StringConstants::productTypesProperty(), - QString(), &productTypesIsSet); - if (productTypesIsSet) - hasDependenciesOnProductType = true; - } - Item::addChild(merged, child); - } - } - const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); - for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { - const PropertyDeclaration &newDecl = it.value(); - const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); - if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { - ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " - "'%1' with different type.").arg(it.key()), exportItem->location()); - handlePropertyError(error, m_parameters, m_logger); - } - merged->setPropertyDeclaration(newDecl.name(), newDecl); - } - for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); - it != exportItem->properties().constEnd(); ++it) { - mergeProperty(merged, it.key(), it.value()); - } - } - merged->setFile(exportItems.empty() - ? productContext.item->file() : exportItems.back()->file()); - merged->setLocation(exportItems.empty() - ? productContext.item->location() : exportItems.back()->location()); - Item::addChild(productContext.item, merged); - merged->setupForBuiltinType(m_logger); - pmi.exportItem = merged; - pmi.multiplexId = productContext.multiplexConfigurationId; - productContext.project->topLevelProject->productModules.insert(productContext.name, pmi); - if (hasDependenciesOnProductType) - m_exportsWithDeferredDependsItems.insert(merged); - return !exportItems.empty(); -} - -Item *ModuleLoader::loadItemFromFile(const QString &filePath, - const CodeLocation &referencingLocation) -{ - Item *item; - try { - item = m_reader->readFile(filePath); - } catch (const ErrorInfo &e) { - if (e.hasLocation()) - throw; - throw ErrorInfo(e.toString(), referencingLocation); - } - handleAllPropertyOptionsItems(item); - return item; -} - -void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext) -{ - const std::vector<Item *> profileItems = collectProfileItems(item, projectContext); - for (Item * const profileItem : profileItems) { - try { - handleProfile(profileItem); - } catch (const ErrorInfo &e) { - handlePropertyError(e, m_parameters, m_logger); - } - } -} - -std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext) -{ - QList<Item *> childItems = item->children(); - std::vector<Item *> profileItems; - Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr; - for (auto it = childItems.begin(); it != childItems.end();) { - Item * const childItem = *it; - if (childItem->type() == ItemType::Profile) { - if (!scope) { - const ItemValuePtr itemValue = ItemValue::create(item); - scope = Item::create(m_pool, ItemType::Scope); - scope->setProperty(StringConstants::productVar(), itemValue); - scope->setFile(item->file()); - scope->setScope(projectContext->scope); - } - childItem->setScope(scope); - profileItems.push_back(childItem); - it = childItems.erase(it); - } else { - if (childItem->type() == ItemType::Product) { - for (Item * const profileItem : collectProfileItems(childItem, projectContext)) - profileItems.push_back(profileItem); - } - ++it; - } - } - if (!profileItems.empty()) - item->setChildren(childItems); - return profileItems; -} - -void ModuleLoader::evaluateProfileValues(const QualifiedId &namePrefix, Item *item, - Item *profileItem, QVariantMap &values) -{ - const Item::PropertyMap &props = item->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QualifiedId name = namePrefix; - name << it.key(); - switch (it.value()->type()) { - case Value::ItemValueType: - evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(), - profileItem, values); - break; - case Value::VariantValueType: - values.insert(name.join(QLatin1Char('.')), - std::static_pointer_cast<VariantValue>(it.value())->value()); - break; - case Value::JSSourceValueType: - item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type - if (item != profileItem) - item->setScope(profileItem); - values.insert(name.join(QLatin1Char('.')), - m_evaluator->value(item, it.key()).toVariant()); - break; - } - } -} - -void ModuleLoader::handleProfile(Item *profileItem) -{ - QVariantMap values; - evaluateProfileValues(QualifiedId(), profileItem, profileItem, values); - const bool condition = values.take(StringConstants::conditionProperty()).toBool(); - if (!condition) - return; - const QString profileName = values.take(StringConstants::nameProperty()).toString(); - if (profileName.isEmpty()) - throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location()); - if (profileName == Profile::fallbackName()) { - throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.") - .arg(profileName), profileItem->location()); - } - if (m_localProfiles.contains(profileName)) { - throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName), - profileItem->location()); - } - m_localProfiles.insert(profileName, values); -} - -void ModuleLoader::collectNameFromOverride(const QString &overrideString) -{ - static const auto extract = [](const QString &prefix, const QString &overrideString) { - if (!overrideString.startsWith(prefix)) - return QString(); - const int startPos = prefix.length(); - const int endPos = overrideString.lastIndexOf(StringConstants::dot()); - if (endPos == -1) - return QString(); - return overrideString.mid(startPos, endPos - startPos); - }; - const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString); - if (!projectName.isEmpty()) { - m_projectNamesUsedInOverrides.insert(projectName); - return; - } - const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString); - if (!productName.isEmpty()) { - m_productNamesUsedInOverrides.insert(productName.left( - productName.indexOf(StringConstants::dot()))); - return; - } -} - -void ModuleLoader::checkProjectNamesInOverrides(const ModuleLoader::TopLevelProjectContext &tlp) -{ - for (const QString &projectNameInOverride : m_projectNamesUsedInOverrides) { - if (m_disabledProjects.contains(projectNameInOverride)) - continue; - bool found = false; - for (const ProjectContext * const p : tlp.projects) { - if (p->name == projectNameInOverride) { - found = true; - break; - } - } - if (!found) { - handlePropertyError(Tr::tr("Unknown project '%1' in property override.") - .arg(projectNameInOverride), m_parameters, m_logger); - } - } -} - -void ModuleLoader::checkProductNamesInOverrides() -{ - for (const QString &productNameInOverride : m_productNamesUsedInOverrides) { - if (m_erroneousProducts.contains(productNameInOverride)) - continue; - bool found = false; - for (const auto &kv : m_productsByName) { - // In an override string such as "a.b.c:d, we cannot tell whether we have a product - // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take - // care not to emit false positives here. - if (kv.first == productNameInOverride - || kv.first.startsWith(productNameInOverride + StringConstants::dot())) { - found = true; - break; - } - } - if (!found) { - handlePropertyError(Tr::tr("Unknown product '%1' in property override.") - .arg(productNameInOverride), m_parameters, m_logger); - } - } -} - -void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *product) -{ - product->searchPaths = readExtraSearchPaths(product->item); - Settings settings(m_parameters.settingsDirectory()); - const QVariantMap profileContents = product->project->result->profileConfigs - .value(product->profileName).toMap(); - const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths(); - const QStringList ¤tSearchPaths = m_reader->allSearchPaths(); - for (const QString &p : prefsSearchPaths) { - if (!currentSearchPaths.contains(p) && FileInfo(p).exists()) - product->searchPaths << p; - } - - // Existing module provider search paths are re-used if and only if the provider configuration - // at setup time was the same as the current one for the respective module provider. - if (!m_moduleProviderInfo.empty()) { - const QVariantMap configForProduct = moduleProviderConfig(*product); - for (const ModuleProviderInfo &c : m_moduleProviderInfo) { - if (configForProduct.value(c.name.toString()).toMap() == c.config) { - qCDebug(lcModuleLoader) << "re-using search paths" << c.searchPaths - << "from module provider" << c.name - << "for product" << product->name; - product->knownModuleProviders.insert(c.name); - product->searchPaths << c.searchPaths; - } - } - } -} - -ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo( - const ModuleLoader::ProductContext &product) const -{ - const bool isShadowProduct = product.name.startsWith(shadowProductPrefix()); - return std::make_pair(isShadowProduct, isShadowProduct - ? product.name.mid(shadowProductPrefix().size()) : QString()); -} - -void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject) -{ - for (ProjectContext * const project : topLevelProject.projects) { - for (ProductContext &product : project->products) - m_productsByName.insert({ product.name, &product }); - } -} - -void ModuleLoader::collectProductsByType(const ModuleLoader::TopLevelProjectContext &topLevelProject) -{ - for (ProjectContext * const project : topLevelProject.projects) { - for (ProductContext &product : project->products) { - try { - const FileTags productTags - = m_evaluator->fileTagsValue(product.item, StringConstants::typeProperty()); - for (const FileTag &tag : productTags) - m_productsByType.insert({ tag, &product}); - } catch (const ErrorInfo &) { - qCDebug(lcModuleLoader) << "product" << product.name << "has complex type " - " and won't get an entry in the type map"; - } - } - } -} - -void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies) -{ - QBS_CHECK(groupItem->type() == ItemType::Group); - QHash<QualifiedId, Item *> moduleInstancesForGroup; - - // Step 1: Instantiate the product's modules for the group. - for (Item::Module m : groupItem->parent()->modules()) { - Item *targetItem = moduleInstanceItem(groupItem, m.name); - targetItem->setPrototype(m.item); - - Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope); - moduleScope->setFile(groupItem->file()); - moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids - moduleScope->setScope(groupItem); - targetItem->setScope(moduleScope); - - targetItem->setFile(m.item->file()); - - // "parent" should point to the group/artifact parent - targetItem->setParent(groupItem->parent()); - - targetItem->setOuterItem(m.item); - - m.item = targetItem; - groupItem->addModule(m); - moduleInstancesForGroup.insert(m.name, targetItem); - } - - // Step 2: Make the inter-module references point to the instances created in step 1. - for (const Item::Module &module : groupItem->modules()) { - Item::Modules adaptedModules; - const Item::Modules &oldModules = module.item->prototype()->modules(); - for (Item::Module depMod : oldModules) { - depMod.item = moduleInstancesForGroup.value(depMod.name); - adaptedModules << depMod; - if (depMod.name.front() == module.name.front()) - continue; - const ItemValuePtr &modulePrefix = groupItem->itemProperty(depMod.name.front()); - QBS_CHECK(modulePrefix); - module.item->setProperty(depMod.name.front(), modulePrefix); - } - module.item->setModules(adaptedModules); - } - - const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(groupItem); - if (propsSetInGroup.empty()) - return; - productContext->info.modulePropertiesSetInGroups - .insert(std::make_pair(groupItem, propsSetInGroup)); - - // Step 3: Adapt defining items in values. This is potentially necessary if module properties - // get assigned on the group level. - for (const Item::Module &module : groupItem->modules()) { - const QualifiedIdSet &dependents = reverseDepencencies.value(module.name); - Item::Modules dependentModules; - dependentModules.reserve(int(dependents.size())); - for (const QualifiedId &depName : dependents) { - Item * const itemOfDependent = moduleInstancesForGroup.value(depName); - QBS_CHECK(itemOfDependent); - Item::Module depMod; - depMod.name = depName; - depMod.item = itemOfDependent; - dependentModules << depMod; - } - adjustDefiningItemsInGroupModuleInstances(module, dependentModules); - } -} - -static Item *createReplacementForDefiningItem(const Item *definingItem, ItemType type) -{ - Item *replacement = Item::create(definingItem->pool(), type); - replacement->setLocation(definingItem->location()); - definingItem->copyProperty(StringConstants::nameProperty(), replacement); - return replacement; -} - -void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, - const Item::Modules &dependentModules) -{ - if (!module.item->isPresentModule()) - return; - - // There are three cases: - // a) The defining item is the "main" module instance, i.e. the one instantiated in the - // product directly (or a parent group). - // b) The defining item refers to the module prototype (or the replacement of it - // created in the module merger [for products] or in this function [for parent groups]). - // c) The defining item is a different instance of the module, i.e. it was instantiated - // in some other module. - - QHash<Item *, Item *> definingItemReplacements; - - Item *modulePrototype = rootPrototype(module.item->prototype()); - QBS_CHECK(modulePrototype->type() == ItemType::Module - || modulePrototype->type() == ItemType::Export); - - const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations(); - for (const auto &decl : propDecls) { - const QString &propName = decl.name(); - - // Module properties assigned in the group are not relevant here, as nothing - // gets inherited in that case. In particular, setting a list property - // overwrites the value from the product's (or parent group's) instance completely, - // rather than appending to it (concatenation happens via outer.concat()). - ValueConstPtr propValue = module.item->ownProperty(propName); - if (propValue) - continue; - - // Find the nearest prototype instance that has the value assigned. - // The result is either an instance of a parent group (or the parent group's - // parent group and so on) or the instance of the product or the module prototype. - // In the latter case, we don't have to do anything. - const Item *instanceWithProperty = module.item; - int prototypeChainLen = 0; - do { - instanceWithProperty = instanceWithProperty->prototype(); - QBS_CHECK(instanceWithProperty); - ++prototypeChainLen; - propValue = instanceWithProperty->ownProperty(propName); - } while (!propValue); - QBS_CHECK(propValue); - - if (propValue->type() != Value::JSSourceValueType) - continue; - - bool hasDefiningItem = false; - for (ValueConstPtr v = propValue; v && !hasDefiningItem; v = v->next()) - hasDefiningItem = v->definingItem(); - if (!hasDefiningItem) - continue; - - const ValuePtr clonedValue = propValue->clone(); - for (ValuePtr v = clonedValue; v; v = v->next()) { - QBS_CHECK(v->definingItem()); - - Item *& replacement = definingItemReplacements[v->definingItem()]; - static const QString caseA = QStringLiteral("__group_case_a"); - if (v->definingItem() == instanceWithProperty - || v->definingItem()->variantProperty(caseA)) { - // Case a) - // For values whose defining item is the product's (or parent group's) instance, - // we take its scope and replace references to module instances with those from the - // group's instance. This handles cases like the following: - // Product { - // name: "theProduct" - // aModule.listProp: [name, otherModule.stringProp] - // Group { name: "theGroup"; otherModule.stringProp: name } - // ... - // } - // In the above example, aModule.listProp is set to ["theProduct", "theGroup"] - // (plus potential values from the prototype and other module instances, - // which are different Value objects in the "next chain"). - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - v->definingItem()->type()); - Item * const scope = Item::create(v->definingItem()->pool(), ItemType::Scope); - scope->setProperties(module.item->scope()->properties()); - Item * const scopeScope - = Item::create(v->definingItem()->pool(), ItemType::Scope); - scopeScope->setProperties(v->definingItem()->scope()->scope()->properties()); - scope->setScope(scopeScope); - replacement->setScope(scope); - const Item::PropertyMap &groupScopeProperties - = module.item->scope()->scope()->properties(); - for (auto propIt = groupScopeProperties.begin(); - propIt != groupScopeProperties.end(); ++propIt) { - if (propIt.value()->type() == Value::ItemValueType) - scopeScope->setProperty(propIt.key(), propIt.value()); - } - } - replacement->setPropertyDeclaration(propName, decl); - replacement->setProperty(propName, v); - replacement->setProperty(caseA, VariantValue::invalidValue()); - } else if (v->definingItem()->type() == ItemType::Module) { - // Case b) - // For values whose defining item is the module prototype, we change the scope to - // the group's instance, analogous to what we do in - // ModuleMerger::appendPrototypeValueToNextChain(). - QBS_CHECK(!decl.isScalar()); - QBS_CHECK(!v->next()); - Item *& replacement = definingItemReplacements[v->definingItem()]; - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - ItemType::Module); - replacement->setScope(module.item); - } - QBS_CHECK(!replacement->hasOwnProperty(caseA)); - qCDebug(lcModuleLoader).noquote().nospace() - << "replacing defining item for prototype; module is " - << module.name.toString() << module.item - << ", property is " << propName - << ", old defining item was " << v->definingItem() - << " with scope" << v->definingItem()->scope() - << ", new defining item is" << replacement - << " with scope" << replacement->scope(); - if (v->type() == Value::JSSourceValueType) { - qCDebug(lcModuleLoader) << "value source code is" - << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString(); - } - replacement->setPropertyDeclaration(propName, decl); - replacement->setProperty(propName, v); - } else { - // Look for instance scopes of other module instances in defining items and - // replace the affected values. - // This is case c) as introduced above. See ModuleMerger::replaceItemInScopes() - // for a detailed explanation. - - QBS_CHECK(v->definingItem()->scope() && v->definingItem()->scope()->scope()); - bool found = false; - for (const Item::Module &depMod : dependentModules) { - const Item *depModPrototype = depMod.item->prototype(); - for (int i = 1; i < prototypeChainLen; ++i) - depModPrototype = depModPrototype->prototype(); - if (v->definingItem()->scope()->scope() != depModPrototype) - continue; - - found = true; - Item *& replacement = definingItemReplacements[v->definingItem()]; - if (!replacement) { - replacement = createReplacementForDefiningItem(v->definingItem(), - v->definingItem()->type()); - replacement->setProperties(v->definingItem()->properties()); - for (const auto &decl : v->definingItem()->propertyDeclarations()) - replacement->setPropertyDeclaration(decl.name(), decl); - replacement->setPrototype(v->definingItem()->prototype()); - replacement->setScope(Item::create(v->definingItem()->pool(), - ItemType::Scope)); - replacement->scope()->setScope(depMod.item); - } - QBS_CHECK(!replacement->hasOwnProperty(caseA)); - qCDebug(lcModuleLoader) << "reset instance scope of module" - << depMod.name.toString() << "in property" - << propName << "of module" << module.name; - } - QBS_CHECK(found); - } - QBS_CHECK(replacement); - v->setDefiningItem(replacement); - } - module.item->setProperty(propName, clonedValue); - } -} - -void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item, - ProductContext *productContext) -{ - QBS_CHECK(m_dependencyResolvingPass == 1 || m_dependencyResolvingPass == 2); - - if (!productContext || m_dependencyResolvingPass == 1) { - const Item::Module baseModule = loadBaseModule(dependsContext->product, item); - item->addModule(baseModule); - } - - // Resolve all Depends items. - ItemModuleList loadedModules; - QList<Item *> dependsItemPerLoadedModule; - ProductDependencies productDependencies; - const auto handleDependsItem = [&](Item *child) { - if (child->type() != ItemType::Depends) - return; - - int lastModulesCount = loadedModules.size(); - try { - resolveDependsItem(dependsContext, child->parent(), child, &loadedModules, - &productDependencies); - } catch (const ErrorInfo &e) { - if (!productContext) - throw; - handleProductError(e, productContext); - } - for (int i = lastModulesCount; i < loadedModules.size(); ++i) - dependsItemPerLoadedModule.push_back(child); - - }; - if (productContext && m_dependencyResolvingPass == 2) { - for (const auto &deferData : productContext->deferredDependsItems) { - dependsContext->exportingProductItem = deferData.first; - for (Item * const dependsItem : deferData.second) - handleDependsItem(dependsItem); - } - } else { - for (Item * const child : item->children()) - handleDependsItem(child); - } - QBS_CHECK(loadedModules.size() == dependsItemPerLoadedModule.size()); - - Item *lastDependsItem = nullptr; - for (Item * const dependsItem : dependsItemPerLoadedModule) { - if (dependsItem == lastDependsItem) - continue; - adjustParametersScopes(dependsItem, dependsItem); - forwardParameterDeclarations(dependsItem, loadedModules); - lastDependsItem = dependsItem; - } - - for (int i = 0; i < loadedModules.size(); ++i) { - Item::Module &module = loadedModules[i]; - mergeParameters(module.parameters, extractParameters(dependsItemPerLoadedModule.at(i))); - item->addModule(module); - - const QString moduleName = module.name.toString(); - std::for_each(productDependencies.begin(), productDependencies.end(), - [&module, &moduleName] (ModuleLoaderResult::ProductInfo::Dependency &dep) { - if (dep.name == moduleName) - dep.parameters = module.parameters; - }); - } - - dependsContext->productDependencies->insert( - dependsContext->productDependencies->end(), - productDependencies.cbegin(), productDependencies.cend()); -} - -class RequiredChainManager -{ -public: - RequiredChainManager(std::vector<bool> &requiredChain, bool required) - : m_requiredChain(requiredChain) - { - m_requiredChain.push_back(required); - } - - ~RequiredChainManager() { m_requiredChain.pop_back(); } - -private: - std::vector<bool> &m_requiredChain; -}; - -void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem, - Item *dependsItem, ItemModuleList *moduleResults, - ProductDependencies *productResults) -{ - checkCancelation(); - if (!checkItemCondition(dependsItem)) { - qCDebug(lcModuleLoader) << "Depends item disabled, ignoring."; - return; - } - bool nameIsSet; - const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), - QString(), &nameIsSet); - bool submodulesPropertySet; - const QStringList submodules = m_evaluator->stringListValue( - dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet); - if (submodules.empty() && submodulesPropertySet) { - qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list."; - return; - } - if (Q_UNLIKELY(submodules.size() > 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()); - } - const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled() - && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty()) - ? FallbackMode::Enabled : FallbackMode::Disabled; - - QList<QualifiedId> moduleNames; - const QualifiedId nameParts = QualifiedId::fromString(name); - if (submodules.empty()) { - // Ignore explicit dependencies on the base module, which has already been loaded. - if (name == StringConstants::qbsModule()) - return; - - moduleNames << nameParts; - } else { - for (const QString &submodule : submodules) - moduleNames << nameParts + QualifiedId::fromString(submodule); - } - - Item::Module result; - bool productTypesIsSet; - m_evaluator->stringValue(dependsItem, StringConstants::productTypesProperty(), - QString(), &productTypesIsSet); - if (m_dependencyResolvingPass == 1 && productTypesIsSet) { - qCDebug(lcModuleLoader) << "queuing product" << dependsContext->product->name - << "for a second dependencies resolving pass"; - m_productsWithDeferredDependsItems[dependsContext->product].insert( - DeferredDependsContext(dependsContext->exportingProductItem, parentItem)); - return; - } - - const bool isRequired = !productTypesIsSet - && m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty()) - && !contains(m_requiredChain, false); - const Version minVersion = Version::fromString( - m_evaluator->stringValue(dependsItem, - StringConstants::versionAtLeastProperty())); - const Version maxVersion = Version::fromString( - m_evaluator->stringValue(dependsItem, StringConstants::versionBelowProperty())); - const VersionRange versionRange(minVersion, maxVersion); - QStringList multiplexConfigurationIds = m_evaluator->stringListValue( - dependsItem, - StringConstants::multiplexConfigurationIdsProperty()); - if (multiplexConfigurationIds.empty()) - multiplexConfigurationIds << QString(); - - for (const QualifiedId &moduleName : qAsConst(moduleNames)) { - // Don't load the same module twice. Duplicate Depends statements can easily - // happen due to inheritance. - const auto it = std::find_if(moduleResults->begin(), moduleResults->end(), - [moduleName](const Item::Module &m) { return m.name == moduleName; }); - if (it != moduleResults->end()) { - if (isRequired) - it->required = true; - it->versionRange.narrowDown(versionRange); - continue; - } - - QVariantMap defaultParameters; - Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem, - parentItem, dependsItem->location(), dependsItem->id(), - moduleName, multiplexConfigurationIds.first(), fallbackMode, - isRequired, &result.isProduct, &defaultParameters); - if (!moduleItem) { - const QString productName = ResolvedProduct::fullDisplayName( - dependsContext->product->name, - dependsContext->product->multiplexConfigurationId); - if (!multiplexConfigurationIds.first().isEmpty()) { - const QString depName = ResolvedProduct::fullDisplayName( - moduleName.toString(), multiplexConfigurationIds.first()); - throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not " - "fulfilled.").arg(productName, depName)); - } - ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.") - .arg(moduleName.toString(), productName), dependsItem->location()); - throw e; - } - if (result.isProduct && !m_dependsChain.empty() && !m_dependsChain.back().isProduct) { - throw ErrorInfo(Tr::tr("Invalid dependency on product '%1': Modules cannot depend on " - "products. You may want to turn your module into a product and " - "add the dependency in that product's Export item.") - .arg(moduleName.toString()), dependsItem->location()); - } - qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString(); - result.name = moduleName; - result.item = moduleItem; - result.required = isRequired; - result.parameters = defaultParameters; - result.versionRange = versionRange; - moduleResults->push_back(result); - if (result.isProduct) { - qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString(); - bool profilesPropertyWasSet = false; - QStringList profiles = m_evaluator->stringListValue(dependsItem, - StringConstants::profilesProperty(), - &profilesPropertyWasSet); - if (profiles.empty()) { - if (profilesPropertyWasSet) - profiles.push_back(StringConstants::star()); - else - profiles.push_back(QString()); - } - for (const QString &profile : qAsConst(profiles)) { - for (const QString &multiplexId : qAsConst(multiplexConfigurationIds)) { - ModuleLoaderResult::ProductInfo::Dependency dependency; - dependency.name = moduleName.toString(); - dependency.profile = profile; - dependency.multiplexConfigurationId = multiplexId; - dependency.isRequired = isRequired; - productResults->push_back(dependency); - } - } - } - } -} - -void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem, - const ItemModuleList &modules) -{ - for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - forwardParameterDeclarations(it.key(), - std::static_pointer_cast<ItemValue>(it.value())->item(), - modules); - } -} - -void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, - const ItemModuleList &modules) -{ - auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) { - return m.name == moduleName; - }); - if (it != modules.end()) { - item->setPropertyDeclarations(m_parameterDeclarations.value(rootPrototype(it->item))); - } else { - for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - forwardParameterDeclarations(QualifiedId(moduleName) << it.key(), - std::static_pointer_cast<ItemValue>(it.value())->item(), - modules); - } - } -} - -void ModuleLoader::resolveParameterDeclarations(const Item *module) -{ - Item::PropertyDeclarationMap decls; - const auto &moduleChildren = module->children(); - for (Item *param : moduleChildren) { - if (param->type() != ItemType::Parameter) - continue; - const auto paramDecls = param->propertyDeclarations(); - for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) - decls.insert(it.key(), it.value()); - } - m_parameterDeclarations.insert(module, decls); -} - -static bool isItemValue(const ValuePtr &v) -{ - return v->type() == Value::ItemValueType; -} - -static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties) -{ - Item::PropertyMap result; - auto itEnd = properties.end(); - for (auto it = properties.begin(); it != itEnd; ++it) { - if (isItemValue(it.value())) - result.insert(it.key(), it.value()); - } - return result; -} - -static QVariantMap safeToVariant(const QScriptValue &v) -{ - QVariantMap result; - QScriptValueIterator it(v); - while (it.hasNext()) { - it.next(); - QScriptValue u = it.value(); - if (u.isError()) - throw ErrorInfo(u.toString()); - result[it.name()] = (u.isObject() && !u.isArray() && !u.isRegExp()) - ? safeToVariant(u) : it.value().toVariant(); - } - return result; -} - -QVariantMap ModuleLoader::extractParameters(Item *dependsItem) const -{ - QVariantMap result; - const Item::PropertyMap &itemProperties = filterItemProperties( - rootPrototype(dependsItem)->properties()); - if (itemProperties.empty()) - return result; - - auto origProperties = dependsItem->properties(); - dependsItem->setProperties(itemProperties); - QScriptValue sv = m_evaluator->scriptValue(dependsItem); - try { - result = safeToVariant(sv); - } catch (const ErrorInfo &exception) { - auto ei = exception; - ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location()); - throw ei; - } - dependsItem->setProperties(origProperties); - return result; -} - -[[noreturn]] static void throwModuleNamePrefixError(const QualifiedId &shortName, - const QualifiedId &longName, const CodeLocation &codeLocation) -{ - throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the " - "name of module '%2', which is not allowed") - .arg(shortName.toString(), longName.toString()), codeLocation); -} - -Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName) -{ - QBS_CHECK(!moduleName.empty()); - Item *instance = containerItem; - for (int i = 0; i < moduleName.size(); ++i) { - const QString &moduleNameSegment = moduleName.at(i); - const ValuePtr v = instance->ownProperty(moduleName.at(i)); - if (v && v->type() == Value::ItemValueType) { - instance = std::static_pointer_cast<ItemValue>(v)->item(); - } else { - const ItemType itemType = i < moduleName.size() - 1 ? ItemType::ModulePrefix - : ItemType::ModuleInstance; - auto newItem = Item::create(m_pool, itemType); - instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); - instance = newItem; - } - if (i < moduleName.size() - 1) { - if (instance->type() == ItemType::ModuleInstance) { - QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1)); - throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation()); - } - QBS_CHECK(instance->type() == ItemType::ModulePrefix); - } - } - QBS_CHECK(instance != containerItem); - return instance; -} - -ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext, - const QString &name, const QString &multiplexId, bool &productNameMatch) -{ - auto &exportsData = productContext->project->topLevelProject->productModules; - const auto firstIt = exportsData.find(name); - productNameMatch = firstIt != exportsData.end(); - for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) { - if (it.value().multiplexId == multiplexId) - return &it.value(); - } - if (multiplexId.isEmpty() && firstIt != exportsData.end()) - return &firstIt.value(); - return nullptr; -} - -ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext, - const QString &name) -{ - auto itEnd = projectContext->products.end(); - auto it = std::find_if(projectContext->products.begin(), itEnd, - [&name] (const ProductContext &ctx) { - return ctx.name == name; - }); - return it == itEnd ? nullptr : &*it; -} - -ModuleLoader::ProductContext *ModuleLoader::product(TopLevelProjectContext *tlpContext, - const QString &name) -{ - ProductContext *result = nullptr; - for (auto prj : tlpContext->projects) { - result = product(prj, name); - if (result) - break; - } - return result; -} - -class ModuleLoader::DependsChainManager -{ -public: - DependsChainManager(std::vector<DependsChainEntry> &dependsChain, const QualifiedId &module, - const CodeLocation &dependsLocation) - : m_dependsChain(dependsChain) - { - const bool alreadyInChain = std::any_of(dependsChain.cbegin(), dependsChain.cend(), - [&module](const DependsChainEntry &e) { - return e.name == module; - }); - if (alreadyInChain) { - ErrorInfo error; - error.append(Tr::tr("Cyclic dependencies detected:")); - for (const DependsChainEntry &e : qAsConst(m_dependsChain)) - error.append(e.name.toString(), e.location); - error.append(module.toString(), dependsLocation); - throw error; - } - m_dependsChain.emplace_back(module, dependsLocation); - } - - ~DependsChainManager() { m_dependsChain.pop_back(); } - -private: - std::vector<DependsChainEntry> &m_dependsChain; -}; - -static bool isBaseModule(const QualifiedId &moduleName) -{ - return moduleName.size() == 1 && moduleName.front() == StringConstants::qbsModule(); -} - -class DelayedPropertyChanger -{ -public: - ~DelayedPropertyChanger() - { - applyNow(); - } - - void setLater(Item *item, const QString &name, const ValuePtr &value) - { - QBS_CHECK(m_item == nullptr); - m_item = item; - m_name = name; - m_value = value; - } - - void removeLater(Item *item, const QString &name) - { - QBS_CHECK(m_item == nullptr); - m_item = item; - m_name = name; - } - - void applyNow() - { - if (!m_item || m_name.isEmpty()) - return; - if (m_value) - m_item->setProperty(m_name, m_value); - else - m_item->removeProperty(m_name); - m_item = nullptr; - m_name.clear(); - m_value.reset(); - } - -private: - Item *m_item = nullptr; - QString m_name; - ValuePtr m_value; -}; - -Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem, - Item *item, const CodeLocation &dependsItemLocation, - const QString &moduleId, const QualifiedId &moduleName, - const QString &multiplexId, FallbackMode fallbackMode, - bool isRequired, bool *isProductDependency, - QVariantMap *defaultParameters) -{ - qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId; - - RequiredChainManager requiredChainManager(m_requiredChain, isRequired); - DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation); - - Item *moduleInstance = moduleId.isEmpty() - ? moduleInstanceItem(item, moduleName) - : moduleInstanceItem(item, QStringList(moduleId)); - if (moduleInstance->scope()) - return moduleInstance; // already handled - - if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) { - for (const Item::Module &m : item->modules()) { - if (m.name.front() == moduleName.front()) - throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation); - } - } - QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance); - - // Prepare module instance for evaluating Module.condition. - DelayedPropertyChanger delayedPropertyChanger; - const QString &qbsModuleName = StringConstants::qbsModule(); - if (!isBaseModule(moduleName)) { - ItemValuePtr qbsProp = productContext->item->itemProperty(qbsModuleName); - if (qbsProp) { - ValuePtr qbsModuleValue = moduleInstance->ownProperty(qbsModuleName); - if (qbsModuleValue) - delayedPropertyChanger.setLater(moduleInstance, qbsModuleName, qbsModuleValue); - else - delayedPropertyChanger.removeLater(moduleInstance, qbsModuleName); - moduleInstance->setProperty(qbsModuleName, qbsProp); - } - } - - Item *modulePrototype = nullptr; - ProductModuleInfo * const pmi = productModule(productContext, moduleName.toString(), - multiplexId, *isProductDependency); - if (pmi) { - m_dependsChain.back().isProduct = true; - modulePrototype = pmi->exportItem; - if (defaultParameters) - *defaultParameters = pmi->defaultParameters; - } else if (!*isProductDependency) { - modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, - moduleName, fallbackMode, isRequired, moduleInstance); - } - delayedPropertyChanger.applyNow(); - if (!modulePrototype) - return nullptr; - - instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype, - moduleName, pmi); - return moduleInstance; -} - -struct PrioritizedItem -{ - PrioritizedItem(Item *item, int priority, int searchPathIndex) - : item(item), priority(priority), searchPathIndex(searchPathIndex) - { - } - - Item *item = nullptr; - int priority = 0; - int searchPathIndex = 0; -}; - -static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates, - const QString &moduleName) -{ - auto maxIt = std::max_element(candidates.begin(), candidates.end(), - [] (const PrioritizedItem &a, const PrioritizedItem &b) { - if (a.priority < b.priority) - return true; - if (a.priority > b.priority) - return false; - return a.searchPathIndex > b.searchPathIndex; - }); - - size_t nmax = std::count_if(candidates.begin(), candidates.end(), - [maxIt] (const PrioritizedItem &i) { - return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex; - }); - - if (nmax > 1) { - ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.") - .arg(moduleName)); - for (size_t i = 0; i < candidates.size(); ++i) { - const auto candidate = candidates.at(i); - if (candidate.priority == maxIt->priority) { - //: The %1 denotes the number of the candidate. - e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location()); - } - } - throw e; - } - - return maxIt->item; -} - -Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, - const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - FallbackMode fallbackMode, bool isRequired, Item *moduleInstance) -{ - bool triedToLoadModule = false; - const QString fullName = moduleName.toString(); - std::vector<PrioritizedItem> candidates; - const QStringList &searchPaths = m_reader->allSearchPaths(); - bool matchingDirectoryFound = false; - for (int i = 0; i < searchPaths.size(); ++i) { - const QString &path = searchPaths.at(i); - const QString dirPath = findExistingModulePath(path, moduleName); - if (dirPath.isEmpty()) - continue; - matchingDirectoryFound = true; - QStringList moduleFileNames = m_moduleDirListCache.value(dirPath); - if (moduleFileNames.empty()) { - QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards()); - while (dirIter.hasNext()) - moduleFileNames += dirIter.next(); - - m_moduleDirListCache.insert(dirPath, moduleFileNames); - } - for (const QString &filePath : qAsConst(moduleFileNames)) { - triedToLoadModule = true; - Item *module = loadModuleFile(productContext, fullName, isBaseModule(moduleName), - filePath, &triedToLoadModule, moduleInstance); - if (module) - candidates.emplace_back(module, 0, i); - if (!triedToLoadModule) - m_moduleDirListCache[dirPath].removeOne(filePath); - } - } - - if (candidates.empty()) { - if (!matchingDirectoryFound) { - bool moduleAlreadyKnown = false; - ModuleProviderResult result; - for (QualifiedId providerName = moduleName; !providerName.empty(); - providerName.pop_back()) { - if (!productContext->knownModuleProviders.insert(providerName).second) { - moduleAlreadyKnown = true; - break; - } - qCDebug(lcModuleLoader) << "Module" << moduleName.toString() - << "not found, checking for module providers"; - result = findModuleProvider(providerName, *productContext, - ModuleProviderLookup::Regular, dependsItemLocation); - if (result.providerFound) - break; - } - if (fallbackMode == FallbackMode::Enabled && !result.providerFound - && !moduleAlreadyKnown) { - qCDebug(lcModuleLoader) << "Specific module provider not found for" - << moduleName.toString() << ", setting up fallback."; - result = findModuleProvider(moduleName, *productContext, - ModuleProviderLookup::Fallback, dependsItemLocation); - } - if (result.providerAddedSearchPaths) { - qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() - << "with newly added search paths from module provider"; - return searchAndLoadModuleFile(productContext, dependsItemLocation, moduleName, - fallbackMode, isRequired, moduleInstance); - } - } - if (!isRequired) - return createNonPresentModule(fullName, QStringLiteral("not found"), nullptr); - if (Q_UNLIKELY(triedToLoadModule)) - throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), - dependsItemLocation); - return nullptr; - } - - Item *moduleItem; - if (candidates.size() == 1) { - moduleItem = candidates.at(0).item; - } else { - for (auto &candidate : candidates) { - candidate.priority = m_evaluator->intValue(candidate.item, - StringConstants::priorityProperty(), - candidate.priority); - } - moduleItem = chooseModuleCandidate(candidates, fullName); - } - - const auto it = productContext->unknownProfilePropertyErrors.find(moduleItem); - if (it != productContext->unknownProfilePropertyErrors.cend()) { - const QString fullProductName = ResolvedProduct::fullDisplayName - (productContext->name, productContext->multiplexConfigurationId); - ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values " - "in profile '%3':").arg(fullName, fullProductName, - productContext->profileName)); - for (const ErrorInfo &e : it->second) - error.append(e.toString()); - handlePropertyError(error, m_parameters, m_logger); - } - return moduleItem; -} - -// 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::VariantList: - return QVariant::List; - case PropertyDeclaration::Variant: - break; - } - 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(QLatin1Char('.')))); - } - return c; -} - -static Item *findDeepestModuleInstance(Item *instance) -{ - while (instance->prototype() && instance->prototype()->type() == ItemType::ModuleInstance) - instance = instance->prototype(); - return instance; -} - -Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName, - bool isBaseModule, const QString &filePath, bool *triedToLoad, Item *moduleInstance) -{ - checkCancelation(); - - qCDebug(lcModuleLoader) << "loadModuleFile" << fullModuleName << "from" << filePath; - - Item * const module = getModulePrototype(productContext, fullModuleName, filePath, triedToLoad); - if (!module) - return nullptr; - - const auto key = std::make_pair(module, productContext); - const auto it = m_modulePrototypeEnabledInfo.find(key); - if (it != m_modulePrototypeEnabledInfo.end()) { - qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; - return it.value() ? module : nullptr; - } - - // Set the name before evaluating any properties. EvaluatorScriptClass reads the module name. - module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName)); - - Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); - Item *origDeepestModuleInstancePrototype = deepestModuleInstance->prototype(); - deepestModuleInstance->setPrototype(module); - bool enabled = checkItemCondition(moduleInstance, module); - deepestModuleInstance->setPrototype(origDeepestModuleInstancePrototype); - if (!enabled) { - qCDebug(lcModuleLoader) << "condition of module" << fullModuleName << "is false"; - m_modulePrototypeEnabledInfo.insert(key, false); - return nullptr; - } - - if (isBaseModule) - setupBaseModulePrototype(module); - else - resolveParameterDeclarations(module); - - m_modulePrototypeEnabledInfo.insert(key, true); - return module; -} - -Item *ModuleLoader::getModulePrototype(ProductContext *productContext, - const QString &fullModuleName, const QString &filePath, bool *triedToLoad) -{ - auto &prototypeList = m_modulePrototypes[filePath]; - for (const auto &prototype : prototypeList) { - if (prototype.second == productContext->profileName) { - qCDebug(lcModuleLoader) << "prototype cache hit (level 1)"; - return prototype.first; - } - } - Item * const module = loadItemFromFile(filePath, CodeLocation()); - prototypeList.emplace_back(module, productContext->profileName); - if (module->type() != ItemType::Module) { - qCDebug(lcModuleLoader).nospace() - << "Alleged module " << fullModuleName << " has type '" - << module->typeName() << "', so it's not a module after all."; - *triedToLoad = false; - return nullptr; - } - - // Module properties that are defined in the profile are used as default values. - // This is the reason we need to have different items per profile. - const QVariantMap profileModuleProperties - = productContext->moduleProperties.value(fullModuleName).toMap(); - for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); - vmit != profileModuleProperties.end(); ++vmit) - { - if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) { - productContext->unknownProfilePropertyErrors[module].emplace_back - (Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); - continue; - } - const PropertyDeclaration decl = module->propertyDeclaration(vmit.key()); - VariantValuePtr v = VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), - QStringList(fullModuleName), vmit.key())); - module->setProperty(vmit.key(), v); - } - - return module; -} - -Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) -{ - const QualifiedId baseModuleName(StringConstants::qbsModule()); - Item::Module baseModuleDesc; - baseModuleDesc.name = baseModuleName; - baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(), - baseModuleName, QString(), FallbackMode::Disabled, true, - &baseModuleDesc.isProduct, nullptr); - if (productContext->item) { - const Item * const qbsInstanceItem - = moduleInstanceItem(productContext->item, baseModuleName); - const Item::PropertyMap &props = qbsInstanceItem->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - if (it.value()->type() == Value::VariantValueType) - baseModuleDesc.item->setProperty(it.key(), it.value()); - } - } - QBS_CHECK(!baseModuleDesc.isProduct); - if (Q_UNLIKELY(!baseModuleDesc.item)) - throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); - return baseModuleDesc; -} - -void ModuleLoader::setupBaseModulePrototype(Item *prototype) -{ - prototype->setProperty(QStringLiteral("hostPlatform"), - VariantValue::create(QString::fromStdString( - HostOsInfo::hostOSIdentifier()))); - prototype->setProperty(QStringLiteral("libexecPath"), - VariantValue::create(m_parameters.libexecPath())); - - const Version qbsVersion = LanguageInfo::qbsVersion(); - prototype->setProperty(QStringLiteral("versionMajor"), - VariantValue::create(qbsVersion.majorVersion())); - prototype->setProperty(QStringLiteral("versionMinor"), - VariantValue::create(qbsVersion.minorVersion())); - prototype->setProperty(QStringLiteral("versionPatch"), - VariantValue::create(qbsVersion.patchLevel())); -} - -static void collectItemsWithId_impl(Item *item, QList<Item *> *result) -{ - if (!item->id().isEmpty()) - result->push_back(item); - for (Item * const child : item->children()) - collectItemsWithId_impl(child, result); -} - -static QList<Item *> collectItemsWithId(Item *item) -{ - QList<Item *> result; - collectItemsWithId_impl(item, &result); - return result; -} - -static std::vector<std::pair<QualifiedId, ItemValuePtr>> instanceItemProperties(Item *item) -{ - std::vector<std::pair<QualifiedId, ItemValuePtr>> result; - QualifiedId name; - std::function<void(Item *)> f = [&] (Item *item) { - for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { - if (it.value()->type() != Value::ItemValueType) - continue; - ItemValuePtr itemValue = std::static_pointer_cast<ItemValue>(it.value()); - if (!itemValue->item()) - continue; - name.push_back(it.key()); - if (itemValue->item()->type() == ItemType::ModulePrefix) - f(itemValue->item()); - else - result.emplace_back(name, itemValue); - name.removeLast(); - } - }; - f(item); - return result; -} - -void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct, - Item *instanceScope, Item *moduleInstance, Item *modulePrototype, - const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo) -{ - Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); - deepestModuleInstance->setPrototype(modulePrototype); - const QString fullName = moduleName.toString(); - const QString generalOverrideKey = QStringLiteral("modules.") + fullName; - const QString perProductOverrideKey = StringConstants::productsOverridePrefix() - + productContext->name + QLatin1Char('.') + fullName; - for (Item *instance = moduleInstance; instance; instance = instance->prototype()) { - overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree()); - if (fullName == QStringLiteral("qbs")) - overrideItemProperties(instance, fullName, m_parameters.overriddenValuesTree()); - overrideItemProperties(instance, perProductOverrideKey, - m_parameters.overriddenValuesTree()); - if (instance == deepestModuleInstance) - break; - } - - moduleInstance->setFile(modulePrototype->file()); - moduleInstance->setLocation(modulePrototype->location()); - QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance); - - // create module scope - Item *moduleScope = Item::create(m_pool, ItemType::Scope); - QBS_CHECK(instanceScope->file()); - moduleScope->setFile(instanceScope->file()); - moduleScope->setScope(instanceScope); - QBS_CHECK(productContext->project->scope); - productContext->project->scope->copyProperty(StringConstants::projectVar(), moduleScope); - if (productContext->scope) - productContext->scope->copyProperty(StringConstants::productVar(), moduleScope); - else - QBS_CHECK(fullName == StringConstants::qbsModule()); // Dummy product. - - if (productModuleInfo) { - exportingProduct = productModuleInfo->exportItem->parent(); - QBS_CHECK(exportingProduct); - QBS_CHECK(exportingProduct->type() == ItemType::Product); - } - - if (exportingProduct) { - // TODO: For consistency with modules, it should be the other way around, i.e. - // "exportingProduct" and just "product". - moduleScope->setProperty(StringConstants::productVar(), - ItemValue::create(exportingProduct)); - moduleScope->setProperty(QStringLiteral("importingProduct"), - ItemValue::create(productContext->item)); - - moduleScope->setProperty(StringConstants::projectVar(), - ItemValue::create(exportingProduct->parent())); - - PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(), - PropertyDeclaration::String, QString(), - PropertyDeclaration::PropertyNotAvailableInConfig); - moduleInstance->setPropertyDeclaration(pd.name(), pd); - ValuePtr v = exportingProduct - ->property(StringConstants::sourceDirectoryProperty())->clone(); - moduleInstance->setProperty(pd.name(), v); - } - moduleInstance->setScope(moduleScope); - - QHash<Item *, Item *> prototypeInstanceMap; - prototypeInstanceMap[modulePrototype] = moduleInstance; - - // create instances for every child of the prototype - createChildInstances(moduleInstance, modulePrototype, &prototypeInstanceMap); - - // create ids from from the prototype in the instance - if (modulePrototype->file()->idScope()) { - const auto items = collectItemsWithId(modulePrototype); - for (Item * const itemWithId : items) { - Item *idProto = itemWithId; - Item *idInstance = prototypeInstanceMap.value(idProto); - QBS_ASSERT(idInstance, continue); - ItemValuePtr idInstanceValue = ItemValue::create(idInstance); - moduleScope->setProperty(itemWithId->id(), idInstanceValue); - } - } - - // For foo.bar in modulePrototype create an item foo in moduleInstance. - for (const auto &iip : instanceItemProperties(modulePrototype)) { - if (iip.second->item()->properties().empty()) - continue; - qCDebug(lcModuleLoader) << "The prototype of " << moduleName - << " sets properties on " << iip.first.toString(); - Item *item = moduleInstanceItem(moduleInstance, iip.first); - item->setPrototype(iip.second->item()); - if (iip.second->createdByPropertiesBlock()) { - ItemValuePtr itemValue = moduleInstance->itemProperty(iip.first.front()); - for (int i = 1; i < iip.first.size(); ++i) - itemValue = itemValue->item()->itemProperty(iip.first.at(i)); - itemValue->setCreatedByPropertiesBlock(true); - } - } - - // Resolve dependencies of this module instance. - DependsContext dependsContext; - dependsContext.product = productContext; - dependsContext.exportingProductItem = exportingProduct; - QBS_ASSERT(moduleInstance->modules().empty(), moduleInstance->removeModules()); - if (productModuleInfo) { - dependsContext.productDependencies = &productContext->productModuleDependencies[fullName]; - resolveDependencies(&dependsContext, moduleInstance); - } else if (!isBaseModule(moduleName)) { - dependsContext.productDependencies = &productContext->info.usedProducts; - resolveDependencies(&dependsContext, moduleInstance); - } - - // Check readonly properties. - const auto end = moduleInstance->properties().cend(); - for (auto it = moduleInstance->properties().cbegin(); it != end; ++it) { - const PropertyDeclaration &pd = moduleInstance->propertyDeclaration(it.key()); - if (!pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) - continue; - throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), - moduleInstance->property(pd.name())->location()); - } -} - -void ModuleLoader::createChildInstances(Item *instance, Item *prototype, - QHash<Item *, Item *> *prototypeInstanceMap) const -{ - instance->childrenReserve(instance->children().size() + prototype->children().size()); - - for (Item * const childPrototype : prototype->children()) { - Item *childInstance = Item::create(m_pool, childPrototype->type()); - prototypeInstanceMap->insert(childPrototype, childInstance); - childInstance->setPrototype(childPrototype); - childInstance->setFile(childPrototype->file()); - childInstance->setId(childPrototype->id()); - childInstance->setLocation(childPrototype->location()); - childInstance->setScope(instance->scope()); - Item::addChild(instance, childInstance); - createChildInstances(childInstance, childPrototype, prototypeInstanceMap); - } -} - -void ModuleLoader::resolveProbes(ProductContext *productContext, Item *item) -{ - AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr); - EvalContextSwitcher evalContextSwitcher(m_evaluator->engine(), EvalContext::ProbeExecution); - for (Item * const child : item->children()) - if (child->type() == ItemType::Probe) - resolveProbe(productContext, item, child); -} - -void ModuleLoader::resolveProbe(ProductContext *productContext, Item *parent, Item *probe) -{ - qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString(); - ++m_probesEncountered; - const QString &probeId = probeGlobalId(probe); - if (Q_UNLIKELY(probeId.isEmpty())) - throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location()); - const JSSourceValueConstPtr configureScript - = probe->sourceProperty(StringConstants::configureProperty()); - QBS_CHECK(configureScript); - if (Q_UNLIKELY(configureScript->sourceCode() == StringConstants::undefinedValue())) - throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); - using ProbeProperty = std::pair<QString, QScriptValue>; - QList<ProbeProperty> probeBindings; - QVariantMap initialProperties; - for (Item *obj = probe; obj; obj = obj->prototype()) { - const Item::PropertyMap &props = obj->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - const QString &name = it.key(); - if (name == StringConstants::configureProperty()) - continue; - const QScriptValue value = m_evaluator->value(probe, name); - probeBindings += ProbeProperty(name, value); - if (name != StringConstants::conditionProperty()) - initialProperties.insert(name, value.toVariant()); - } - } - ScriptEngine * const engine = m_evaluator->engine(); - QScriptValue configureScope; - const bool condition = m_evaluator->boolValue(probe, StringConstants::conditionProperty()); - const QString &sourceCode = configureScript->sourceCode().toString(); - ProbeConstPtr resolvedProbe; - if (parent->type() == ItemType::Project - || productContext->name.startsWith(shadowProductPrefix())) { - resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); - } else { - const QString &uniqueProductName = productContext->uniqueName(); - resolvedProbe - = findOldProductProbe(uniqueProductName, condition, initialProperties, sourceCode); - } - if (!resolvedProbe) { - resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties); - if (resolvedProbe) { - qCDebug(lcModuleLoader) << "probe results cached from current run"; - ++m_probesCachedCurrent; - } - } else { - qCDebug(lcModuleLoader) << "probe results cached from earlier run"; - ++m_probesCachedOld; - } - std::vector<QString> importedFilesUsedInConfigure; - if (!condition) { - qCDebug(lcModuleLoader) << "Probe disabled; skipping"; - } else if (!resolvedProbe) { - ++m_probesRun; - qCDebug(lcModuleLoader) << "configure script needs to run"; - const Evaluator::FileContextScopes fileCtxScopes - = m_evaluator->fileContextScopes(configureScript->file()); - engine->currentContext()->pushScope(fileCtxScopes.fileScope); - engine->currentContext()->pushScope(fileCtxScopes.importScope); - configureScope = engine->newObject(); - for (const ProbeProperty &b : qAsConst(probeBindings)) - configureScope.setProperty(b.first, b.second); - engine->currentContext()->pushScope(configureScope); - engine->clearRequestedProperties(); - QScriptValue sv = engine->evaluate(configureScript->sourceCodeForEvaluation()); - engine->currentContext()->popScope(); - engine->currentContext()->popScope(); - engine->currentContext()->popScope(); - engine->releaseResourcesOfScriptObjects(); - if (Q_UNLIKELY(engine->hasErrorOrException(sv))) - throw ErrorInfo(engine->lastErrorString(sv), configureScript->location()); - importedFilesUsedInConfigure = engine->importedFilesUsedInScript(); - } else { - importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed(); - } - QVariantMap properties; - for (const ProbeProperty &b : qAsConst(probeBindings)) { - QVariant newValue; - if (resolvedProbe) { - newValue = resolvedProbe->properties().value(b.first); - } else { - if (condition) { - QScriptValue v = configureScope.property(b.first); - m_evaluator->convertToPropertyType(probe->propertyDeclaration( - b.first), probe->location(), v); - if (Q_UNLIKELY(engine->hasErrorOrException(v))) - throw ErrorInfo(engine->lastError(v)); - newValue = v.toVariant(); - } else { - newValue = initialProperties.value(b.first); - } - } - if (newValue != b.second.toVariant()) - probe->setProperty(b.first, VariantValue::create(newValue)); - if (!resolvedProbe) - properties.insert(b.first, newValue); - } - if (!resolvedProbe) { - resolvedProbe = Probe::create(probeId, probe->location(), condition, - sourceCode, properties, initialProperties, - importedFilesUsedInConfigure); - m_currentProbes[probe->location()] << resolvedProbe; - } - productContext->info.probes << resolvedProbe; -} - -void ModuleLoader::checkCancelation() const -{ - if (m_progressObserver && m_progressObserver->canceled()) { - throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") - .arg(TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree()))); - } -} - -bool ModuleLoader::checkItemCondition(Item *item, Item *itemToDisable) -{ - if (m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return true; - m_disabledItems += itemToDisable ? itemToDisable : item; - return false; -} - -QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) -{ - QStringList result; - const QStringList paths = m_evaluator->stringListValue( - item, StringConstants::qbsSearchPathsProperty(), wasSet); - const JSSourceValueConstPtr prop = item->sourceProperty( - StringConstants::qbsSearchPathsProperty()); - - // Value can come from within a project file or as an overridden value from the user - // (e.g command line). - const QString basePath = FileInfo::path(prop ? prop->file()->filePath() - : m_parameters.projectFilePath()); - for (const QString &path : paths) - result += FileInfo::resolvePath(basePath, path); - return result; -} - -void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) -{ - if (!sourceProject) - return; - const QList<PropertyDeclaration> builtinProjectProperties = BuiltinDeclarations::instance() - .declarationsForType(ItemType::Project).properties(); - Set<QString> builtinProjectPropertyNames; - for (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 there are exceptions. - if (it.key() == StringConstants::qbsSearchPathsProperty() - || it.key() == StringConstants::profileProperty() - || it.key() == StringConstants::buildDirectoryProperty() - || it.key() == StringConstants::sourceDirectoryProperty() - || it.key() == StringConstants::minimumQbsVersionProperty()) { - const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); - QBS_ASSERT(v, continue); - if (v->sourceCode() == StringConstants::undefinedValue()) - sourceProject->copyProperty(it.key(), targetProject); - continue; - } - - if (builtinProjectPropertyNames.contains(it.key())) - continue; - - if (targetProject->hasOwnProperty(it.key())) - continue; // Ignore stuff the target project already has. - - targetProject->setPropertyDeclaration(it.key(), it.value()); - sourceProject->copyProperty(it.key(), targetProject); - } -} - -Item *ModuleLoader::wrapInProjectIfNecessary(Item *item) -{ - if (item->type() == ItemType::Project) - return item; - Item *prj = Item::create(item->pool(), ItemType::Project); - Item::addChild(prj, item); - prj->setFile(item->file()); - prj->setLocation(item->location()); - prj->setupForBuiltinType(m_logger); - return prj; -} - -QString ModuleLoader::findExistingModulePath(const QString &searchPath, - const QualifiedId &moduleName) -{ - QString dirPath = searchPath + QStringLiteral("/modules"); - for (const QString &moduleNamePart : moduleName) { - dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); - if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) - return {}; - } - return dirPath; -} - -QVariantMap ModuleLoader::moduleProviderConfig(ModuleLoader::ProductContext &product) -{ - if (product.moduleProviderConfigRetrieved) - return product.theModuleProviderConfig; - const ItemValueConstPtr configItemValue - = product.item->itemProperty(StringConstants::moduleProviders()); - if (configItemValue) { - const std::function<void(const Item *, QualifiedId)> collectMap - = [this, &product, &collectMap](const Item *item, QualifiedId name) { - const Item::PropertyMap &props = item->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QVariant value; - switch (it.value()->type()) { - case Value::ItemValueType: - collectMap(static_cast<ItemValue *>(it.value().get())->item(), - QualifiedId(name += it.key())); - return; - case Value::JSSourceValueType: - value = m_evaluator->value(item, it.key()).toVariant(); - break; - case Value::VariantValueType: - value = static_cast<VariantValue *>(it.value().get())->value(); - break; - } - QVariantMap m = product.theModuleProviderConfig.value(name.toString()).toMap(); - m.insert(it.key(), value); - product.theModuleProviderConfig.insert(name.toString(), m); - } - }; - collectMap(configItemValue->item(), QualifiedId()); - } - for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) { - if (!it.key().startsWith(QStringLiteral("moduleProviders."))) - continue; - const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size()); - const QVariantMap providerConfigFromBuildConfig = it.value().toMap(); - if (providerConfigFromBuildConfig.empty()) - continue; - QVariantMap currentMapForProvider = product.theModuleProviderConfig.value(provider).toMap(); - for (auto propIt = providerConfigFromBuildConfig.begin(); - propIt != providerConfigFromBuildConfig.end(); ++propIt) { - currentMapForProvider.insert(propIt.key(), propIt.value()); - } - product.theModuleProviderConfig.insert(provider, currentMapForProvider); - } - product.moduleProviderConfigRetrieved = true; - return product.theModuleProviderConfig; -} - -ModuleLoader::ModuleProviderResult ModuleLoader::findModuleProvider(const QualifiedId &name, - ModuleLoader::ProductContext &product, ModuleProviderLookup lookupType, - const CodeLocation &dependsItemLocation) -{ - for (const QString &path : m_reader->allSearchPaths()) { - QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers")); - switch (lookupType) { - case ModuleProviderLookup::Regular: - for (const QString &component : name) - fullPath = FileInfo::resolvePath(fullPath, component); - break; - case ModuleProviderLookup::Fallback: - fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback")); - break; - } - const QString providerFile = FileInfo::resolvePath(fullPath, - QStringLiteral("provider.qbs")); - if (!FileInfo::exists(providerFile)) { - qCDebug(lcModuleLoader) << "No module provider found at" << providerFile; - continue; - } - QTemporaryFile dummyItemFile; - if (!dummyItemFile.open()) { - throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider " - "for dependency '%1': %2").arg(name.toString(), - dummyItemFile.errorString())); - } - m_tempQbsFiles << dummyItemFile.fileName(); - qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile; - const QString projectBuildDir = product.project->item->variantProperty( - StringConstants::buildDirectoryProperty())->value().toString(); - const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); - const QVariant moduleConfig = moduleProviderConfig(product).value(name.toString()); - QTextStream stream(&dummyItemFile); - stream.setCodec("UTF-8"); - stream << "import qbs.FileInfo" << endl; - stream << "import qbs.Utilities" << endl; - stream << "import '" << providerFile << "' as Provider" << endl; - stream << "Provider {" << endl; - stream << " name: " << toJSLiteral(name.toString()) << endl; - stream << " property var config: (" << toJSLiteral(moduleConfig) << ')' << endl; - stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, " - " Utilities.getHash(JSON.stringify(config)))" << endl; - stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl; - stream << " property stringList searchPaths: (relativeSearchPaths || [])" - " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })" - << endl; - stream << "}" << endl; - stream.flush(); - Item * const providerItem = loadItemFromFile(dummyItemFile.fileName(), dependsItemLocation); - if (providerItem->type() != ItemType::ModuleProvider) { - throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', " - "but '%3' was expected.") - .arg(providerFile, providerItem->typeName(), - BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider))); - } - providerItem->setParent(product.item); - const QVariantMap configMap = moduleConfig.toMap(); - for (auto it = configMap.begin(); it != configMap.end(); ++it) { - const PropertyDeclaration decl = providerItem->propertyDeclaration(it.key()); - if (!decl.isValid()) { - throw ErrorInfo(Tr::tr("No such property '%1' in module provider '%2'.") - .arg(it.key(), name.toString())); - } - providerItem->setProperty(it.key(), VariantValue::create(it.value())); - } - EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider); - const QStringList searchPaths - = m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths")); - const auto addToGlobalInfo = [=] { - m_moduleProviderInfo.emplace_back(ModuleProviderInfo(name, moduleConfig.toMap(), - searchPaths, m_parameters.dryRun())); - }; - if (searchPaths.empty()) { - qCDebug(lcModuleLoader) << "Module provider did run, but did not set up " - "any modules."; - addToGlobalInfo(); - return {true, false}; - } - qCDebug(lcModuleLoader) << "Module provider added" << searchPaths.size() - << "new search path(s)"; - - // (1) is needed so the immediate new look-up works. - // (2) is needed so the next use of SearchPathManager considers the new paths. - // (3) is needed for the code that removes the product-specific search paths when - // product handling is done. - // (4) is needed for possible re-use in subsequent products and builds. - m_reader->pushExtraSearchPaths(searchPaths); // (1) - product.searchPaths << searchPaths; // (2) - product.newlyAddedModuleProviderSearchPaths.push_back(searchPaths); // (3) - addToGlobalInfo(); // (4) - return {true, true}; - } - return {}; -} - -void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) -{ - for (Item * const child : item->children()) { - child->setScope(scope); - setScopeForDescendants(child, scope); - } -} - -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->propertyDeclaration(it.key()); - if (!decl.isValid()) { - ErrorInfo error(Tr::tr("Unknown property: %1.%2").arg(buildConfigKey, it.key())); - handlePropertyError(error, m_parameters, m_logger); - continue; - } - item->setProperty(it.key(), - VariantValue::create(convertToPropertyType(it.value(), decl.type(), - QStringList(buildConfigKey), it.key()))); - } -} - -void ModuleLoader::collectAllModules(Item *item, std::vector<Item::Module> *modules) -{ - for (const Item::Module &m : item->modules()) { - if (moduleRepresentsDisabledProduct(m)) - m.item->removeModules(); - auto it = std::find_if(modules->begin(), modules->end(), - [m] (const Item::Module &m2) { return m.name == m2.name; }); - if (it != modules->end()) { - // If a module is required somewhere, it is required in the top-level item. - if (m.required) - it->required = true; - it->versionRange.narrowDown(m.versionRange); - continue; - } - modules->push_back(m); - collectAllModules(m.item, modules); - } -} - -std::vector<Item::Module> ModuleLoader::allModules(Item *item) -{ - std::vector<Item::Module> lst; - collectAllModules(item, &lst); - return lst; -} - -bool ModuleLoader::moduleRepresentsDisabledProduct(const Item::Module &module) -{ - if (!module.isProduct) - return false; - const Item *exportItem = module.item->prototype(); - while (exportItem && exportItem->type() != ItemType::Export) - exportItem = exportItem->prototype(); - QBS_CHECK(exportItem); - Item * const productItem = exportItem->parent(); - QBS_CHECK(productItem->type() == ItemType::Product); - return m_disabledItems.contains(productItem) || !checkItemCondition(productItem); -} - -void ModuleLoader::addProductModuleDependencies(ProductContext *productContext, const QString &name) -{ - auto deps = productContext->productModuleDependencies.at(name); - QList<ModuleLoaderResult::ProductInfo::Dependency> depsToAdd; - const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty(); - for (auto &dep : deps) { - const auto productRange = m_productsByName.equal_range(dep.name); - std::vector<const ProductContext *> dependencies; - bool hasNonMultiplexedDependency = false; - for (auto it = productRange.first; it != productRange.second; ++it) { - if (!it->second->multiplexConfigurationId.isEmpty()) { - dependencies.push_back(it->second); - if (productIsMultiplexed && dep.profile.isEmpty()) - break; - } else { - hasNonMultiplexedDependency = true; - break; - } - } - - if (hasNonMultiplexedDependency) { - depsToAdd.push_back(dep); - continue; - } - - for (std::size_t i = 0; i < dependencies.size(); ++i) { - const bool profileMatch = dep.profile.isEmpty() - || dep.profile == StringConstants::star() - || dep.profile == dependencies.at(i)->profileName; - if (i == 0) { - if (productIsMultiplexed && dep.profile.isEmpty()) { - const ValuePtr &multiplexConfigIdProp = productContext->item->property( - StringConstants::multiplexConfigurationIdProperty()); - dep.multiplexConfigurationId = std::static_pointer_cast<VariantValue>( - multiplexConfigIdProp)->value().toString(); - depsToAdd.push_back(dep); - break; - } else if (profileMatch) { - dep.multiplexConfigurationId = dependencies.at(i)->multiplexConfigurationId; - depsToAdd.push_back(dep); - } - } else if (profileMatch) { - ModuleLoaderResult::ProductInfo::Dependency newDependency = dep; - newDependency.multiplexConfigurationId - = dependencies.at(i)->multiplexConfigurationId; - depsToAdd << newDependency; - } - } - } - productContext->info.usedProducts.insert(productContext->info.usedProducts.end(), - depsToAdd.cbegin(), depsToAdd.cend()); -} - -static void collectProductModuleDependencies(Item *item, Set<QualifiedId> &allDeps) -{ - for (const Item::Module &m : item->modules()) { - if (m.isProduct && allDeps.insert(m.name).second) - collectProductModuleDependencies(m.item, allDeps); - } -} - -void ModuleLoader::addProductModuleDependencies(ModuleLoader::ProductContext *ctx) -{ - Set<QualifiedId> deps; - collectProductModuleDependencies(ctx->item, deps); - for (const QualifiedId &dep : deps) - addProductModuleDependencies(ctx, dep.toString()); -} - -void ModuleLoader::addTransitiveDependencies(ProductContext *ctx) -{ - AccumulatingTimer timer(m_parameters.logElapsedTime() - ? &m_elapsedTimeTransitiveDependencies : nullptr); - qCDebug(lcModuleLoader) << "addTransitiveDependencies"; - - std::vector<Item::Module> transitiveDeps = allModules(ctx->item); - std::sort(transitiveDeps.begin(), transitiveDeps.end()); - for (const Item::Module &m : ctx->item->modules()) { - auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m); - QBS_CHECK(it != transitiveDeps.end() && it->name == m.name); - transitiveDeps.erase(it); - } - for (const Item::Module &module : qAsConst(transitiveDeps)) { - if (module.isProduct) { - ctx->item->addModule(module); - } else { - Item::Module dep; - dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(), - module.name, QString(), FallbackMode::Disabled, - module.required, &dep.isProduct, &dep.parameters); - if (!dep.item) { - throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive " - "dependencies for product '%2'.").arg(module.name.toString(), - ctx->name), - ctx->item->location()); - } - dep.name = module.name; - dep.required = module.required; - dep.versionRange = module.versionRange; - ctx->item->addModule(dep); - } - } -} - -Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module) -{ - qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")." - << "Creating dummy module for presence check."; - if (!module) { - module = Item::create(m_pool, ItemType::ModuleInstance); - module->setFile(FileContext::create()); - module->setProperty(StringConstants::nameProperty(), VariantValue::create(name)); - } - module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue()); - return module; -} - -void ModuleLoader::handleProductError(const ErrorInfo &error, - ModuleLoader::ProductContext *productContext) -{ - const bool alreadyHadError = productContext->info.delayedError.hasError(); - if (!alreadyHadError) { - productContext->info.delayedError.append(Tr::tr("Error while handling product '%1':") - .arg(productContext->name), - productContext->item->location()); - } - if (error.isInternalError()) { - if (alreadyHadError) { - qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString() - << "in product" << productContext->name; - return; - } - for (const auto &kv : productContext->productModuleDependencies) { - const auto rangeForName = m_productsByName.equal_range(kv.first); - for (auto rangeIt = rangeForName.first; rangeIt != rangeForName.second; ++rangeIt) { - const ProductContext * const dep = rangeIt->second; - if (dep->info.delayedError.hasError()) { - qCDebug(lcModuleLoader()) << "ignoring internal error" << error.toString() - << "in product" << productContext->name - << "assumed to be caused by erroneous dependency" - << dep->name; - return; - } - } - } - } - const auto errorItems = error.items(); - for (const ErrorItem &ei : errorItems) - productContext->info.delayedError.append(ei.description(), ei.codeLocation()); - productContext->project->result->productInfos.insert(productContext->item, - productContext->info); - m_disabledItems << productContext->item; - m_erroneousProducts.insert(productContext->name); -} - -static void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix, - QualifiedIdSet &properties) -{ - const Item::PropertyMap &props = iv->item()->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - switch (it.value()->type()) { - case Value::JSSourceValueType: - properties << (QualifiedId(prefix) << it.key()); - break; - case Value::ItemValueType: - if (iv->item()->type() == ItemType::ModulePrefix) { - gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), - QualifiedId(prefix) << it.key(), properties); - } - break; - default: - break; - } - } -} - -QualifiedIdSet ModuleLoader::gatherModulePropertiesSetInGroup(const Item *group) -{ - QualifiedIdSet propsSetInGroup; - const Item::PropertyMap &props = group->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - if (it.value()->type() == Value::ItemValueType) { - gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), - QualifiedId(it.key()), propsSetInGroup); - } - } - return propsSetInGroup; -} - -void ModuleLoader::markModuleTargetGroups(Item *group, const Item::Module &module) -{ - QBS_CHECK(group->type() == ItemType::Group); - if (m_evaluator->boolValue(group, StringConstants::filesAreTargetsProperty())) { - group->setProperty(StringConstants::modulePropertyInternal(), - VariantValue::create(module.name.toString())); - } - for (Item * const child : group->children()) - markModuleTargetGroups(child, module); -} - -void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext, - const Item::Module &module, - const Item *modulePrototype) -{ - for (Item * const child : modulePrototype->children()) { - if (child->type() == ItemType::Group) { - Item * const clonedGroup = child->clone(); - clonedGroup->setScope(productContext.scope); - setScopeForDescendants(clonedGroup, productContext.scope); - Item::addChild(productContext.item, clonedGroup); - markModuleTargetGroups(clonedGroup, module); - } - } -} - -void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext) -{ - for (const Item::Module &module : productContext.item->modules()) { - Item *prototype = module.item; - bool modulePassedValidation; - while ((modulePassedValidation = prototype->isPresentModule()) && prototype->prototype()) - prototype = prototype->prototype(); - if (modulePassedValidation) - copyGroupsFromModuleToProduct(productContext, module, prototype); - } -} - -QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const -{ - return ResolvedProduct::uniqueName(name, multiplexConfigurationId); -} - -QString ModuleLoader::ProductContext::uniqueName() const -{ - return ResolvedProduct::uniqueName(name, multiplexConfigurationId); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h deleted file mode 100644 index 2a7a89727..000000000 --- a/src/lib/corelib/language/moduleloader.h +++ /dev/null @@ -1,486 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QBS_MODULELOADER_H -#define QBS_MODULELOADER_H - -#include "filetags.h" -#include "forward_decls.h" -#include "item.h" -#include "itempool.h" -#include "moduleproviderinfo.h" -#include <logging/logger.h> -#include <tools/filetime.h> -#include <tools/qttools.h> -#include <tools/set.h> -#include <tools/setupprojectparameters.h> -#include <tools/version.h> - -#include <QtCore/qmap.h> -#include <QtCore/qstringlist.h> -#include <QtCore/qvariant.h> - -#include <map> -#include <memory> -#include <unordered_map> -#include <utility> -#include <vector> - -namespace qbs { - -class CodeLocation; -class Settings; - -namespace Internal { - -class Evaluator; -class Item; -class ItemReader; -class ProgressObserver; -class QualifiedId; - -using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>; - -struct ModuleLoaderResult -{ - ModuleLoaderResult() - : itemPool(new ItemPool), root(nullptr) - {} - - struct ProductInfo - { - struct Dependency - { - QString name; - QString profile; // "*" <=> Match all profiles. - QString multiplexConfigurationId; - QVariantMap parameters; - bool limitToSubProject = false; - bool isRequired = true; - - QString uniqueName() const; - }; - - std::vector<ProbeConstPtr> probes; - std::vector<Dependency> usedProducts; - ModulePropertiesPerGroup modulePropertiesSetInGroups; - ErrorInfo delayedError; - }; - - std::shared_ptr<ItemPool> itemPool; - Item *root; - QHash<Item *, ProductInfo> productInfos; - std::vector<ProbeConstPtr> projectProbes; - ModuleProviderInfoList moduleProviderInfo; - Set<QString> qbsFiles; - QVariantMap profileConfigs; -}; - -/* - * Loader stage II. Responsible for - * - loading modules and module dependencies, - * - project references, - * - Probe items. - */ -class ModuleLoader -{ -public: - ModuleLoader(Evaluator *evaluator, Logger &logger); - ~ModuleLoader(); - - void setProgressObserver(ProgressObserver *progressObserver); - void setSearchPaths(const QStringList &searchPaths); - void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes); - void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); - void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } - void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo); - Evaluator *evaluator() const { return m_evaluator; } - - ModuleLoaderResult load(const SetupProjectParameters ¶meters); - -private: - class ProductSortByDependencies; - - class ContextBase - { - public: - ContextBase() - : item(nullptr), scope(nullptr) - {} - - Item *item; - Item *scope; - QString name; - }; - - class ProjectContext; - - using ProductDependencies = std::vector<ModuleLoaderResult::ProductInfo::Dependency>; - - // This is the data we need to store at the point where a dependency is deferred - // in order to properly resolve the dependency in pass 2. - struct DeferredDependsContext { - DeferredDependsContext(Item *exportingProduct, Item *parent) - : exportingProductItem(exportingProduct), parentItem(parent) {} - Item *exportingProductItem = nullptr; - Item *parentItem = nullptr; - bool operator==(const DeferredDependsContext &other) const - { - return exportingProductItem == other.exportingProductItem - && parentItem == other.parentItem; - } - bool operator<(const DeferredDependsContext &other) const - { - return parentItem < other.parentItem; - } - }; - - class ProductContext : public ContextBase - { - public: - ProjectContext *project = nullptr; - ModuleLoaderResult::ProductInfo info; - QString profileName; - QString multiplexConfigurationId; - QVariantMap moduleProperties; - std::map<QString, ProductDependencies> productModuleDependencies; - std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors; - QStringList searchPaths; - - std::vector<QStringList> newlyAddedModuleProviderSearchPaths; - Set<QualifiedId> knownModuleProviders; - QVariantMap theModuleProviderConfig; - bool moduleProviderConfigRetrieved = false; - - // The key corresponds to DeferredDependsContext.exportingProductItem, which is the - // only value from that data structure that we still need here. - std::unordered_map<Item *, std::vector<Item *>> deferredDependsItems; - - QString uniqueName() const; - }; - - class TopLevelProjectContext; - - class ProjectContext : public ContextBase - { - public: - TopLevelProjectContext *topLevelProject = nullptr; - ModuleLoaderResult *result = nullptr; - std::vector<ProductContext> products; - std::vector<QStringList> searchPathsStack; - }; - - struct ProductModuleInfo - { - Item *exportItem = nullptr; - QString multiplexId; - QVariantMap defaultParameters; - }; - - class TopLevelProjectContext - { - Q_DISABLE_COPY(TopLevelProjectContext) - public: - TopLevelProjectContext() = default; - ~TopLevelProjectContext() { qDeleteAll(projects); } - - std::vector<ProjectContext *> projects; - QMultiHash<QString, ProductModuleInfo> productModules; - std::vector<ProbeConstPtr> probes; - QString buildDirectory; - }; - - class DependsContext - { - public: - ProductContext *product = nullptr; - Item *exportingProductItem = nullptr; - ProductDependencies *productDependencies = nullptr; - }; - - void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, - const QString &buildDirectory, const Set<QString> &referencedFilePaths); - void handleProject(ModuleLoaderResult *loadResult, - TopLevelProjectContext *topLevelProjectContext, Item *projectItem, - const Set<QString> &referencedFilePaths); - - using MultiplexRow = std::vector<VariantValuePtr>; - using MultiplexTable = std::vector<MultiplexRow>; - - struct MultiplexInfo - { - std::vector<QString> properties; - MultiplexTable table; - bool aggregate = false; - VariantValuePtr multiplexedType; - - QString toIdString(size_t row) const; - }; - - void dump(const MultiplexInfo &mpi); - static MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values); - MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem); - QList<Item *> multiplexProductItem(ProductContext *dummyContext, Item *productItem); - void normalizeDependencies(ProductContext *product, - const DeferredDependsContext &dependsContext); - void adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp); - void adjustDependenciesForMultiplexing(const ProductContext &product); - void adjustDependenciesForMultiplexing(const ProductContext &product, Item *dependsItem); - - void prepareProduct(ProjectContext *projectContext, Item *productItem); - void setupProductDependencies(ProductContext *productContext, - const Set<DeferredDependsContext> &deferredDependsContext); - void handleProduct(ProductContext *productContext); - void checkDependencyParameterDeclarations(const ProductContext *productContext) const; - void handleModuleSetupError(ProductContext *productContext, const Item::Module &module, - const ErrorInfo &error); - void initProductProperties(const ProductContext &product); - void handleSubProject(ProjectContext *projectContext, Item *projectItem, - const Set<QString> &referencedFilePaths); - QList<Item *> loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, - ProductContext &dummyContext); - void handleAllPropertyOptionsItems(Item *item); - void handlePropertyOptions(Item *optionsItem); - - using ModuleDependencies = QHash<QualifiedId, QualifiedIdSet>; - void setupReverseModuleDependencies(const Item::Module &module, ModuleDependencies &deps, - QualifiedIdSet &seenModules); - ModuleDependencies setupReverseModuleDependencies(const Item *product); - void handleGroup(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies); - void propagateModulesFromParent(ProductContext *productContext, Item *groupItem, - const ModuleDependencies &reverseDepencencies); - void adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, - const Item::Modules &dependentModules); - - bool mergeExportItems(const ProductContext &productContext); - void resolveDependencies(DependsContext *dependsContext, Item *item, - ProductContext *productContext = nullptr); - class ItemModuleList; - void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem, - ItemModuleList *moduleResults, ProductDependencies *productResults); - void forwardParameterDeclarations(const Item *dependsItem, const ItemModuleList &modules); - void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, - const ItemModuleList &modules); - void resolveParameterDeclarations(const Item *module); - QVariantMap extractParameters(Item *dependsItem) const; - Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName); - static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name, - const QString &multiplexId, bool &productNameMatch); - static ProductContext *product(ProjectContext *projectContext, const QString &name); - static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name); - - enum class FallbackMode { Enabled, Disabled }; - Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item, - const CodeLocation &dependsItemLocation, const QString &moduleId, - const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode, - bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters); - Item *searchAndLoadModuleFile(ProductContext *productContext, - const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - FallbackMode fallbackMode, bool isRequired, Item *moduleInstance); - Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName, - bool isBaseModule, const QString &filePath, bool *triedToLoad, Item *moduleInstance); - Item *getModulePrototype(ProductContext *productContext, const QString &fullModuleName, - const QString &filePath, bool *triedToLoad); - Item::Module loadBaseModule(ProductContext *productContext, Item *item); - void setupBaseModulePrototype(Item *prototype); - template <typename T, typename F> - T callWithTemporaryBaseModule(ProductContext *productContext, const F &func); - void instantiateModule(ProductContext *productContext, Item *exportingProductItem, - Item *instanceScope, Item *moduleInstance, Item *modulePrototype, - const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo); - void createChildInstances(Item *instance, Item *prototype, - QHash<Item *, Item *> *prototypeInstanceMap) const; - void resolveProbes(ProductContext *productContext, Item *item); - void resolveProbe(ProductContext *productContext, Item *parent, Item *probe); - void checkCancelation() const; - bool checkItemCondition(Item *item, Item *itemToDisable = nullptr); - QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr); - void copyProperties(const Item *sourceProject, Item *targetProject); - Item *wrapInProjectIfNecessary(Item *item); - static QString findExistingModulePath(const QString &searchPath, - const QualifiedId &moduleName); - - enum class ModuleProviderLookup { Regular, Fallback }; - struct ModuleProviderResult - { - ModuleProviderResult() = default; - ModuleProviderResult(bool ran, bool added) - : providerFound(ran), providerAddedSearchPaths(added) {} - bool providerFound = false; - bool providerAddedSearchPaths = false; - }; - ModuleProviderResult findModuleProvider(const QualifiedId &name, ProductContext &product, - ModuleProviderLookup lookupType, const CodeLocation &dependsItemLocation); - QVariantMap moduleProviderConfig(ProductContext &product); - - static void setScopeForDescendants(Item *item, Item *scope); - void overrideItemProperties(Item *item, const QString &buildConfigKey, - const QVariantMap &buildConfig); - void addProductModuleDependencies(ProductContext *ctx, const QString &name); - void addProductModuleDependencies(ProductContext *ctx); - void addTransitiveDependencies(ProductContext *ctx); - Item *createNonPresentModule(const QString &name, const QString &reason, Item *module); - void copyGroupsFromModuleToProduct(const ProductContext &productContext, - const Item::Module &module, const Item *modulePrototype); - void copyGroupsFromModulesToProduct(const ProductContext &productContext); - void markModuleTargetGroups(Item *group, const Item::Module &module); - bool checkExportItemCondition(Item *exportItem, const ProductContext &productContext); - ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const; - ProbeConstPtr findOldProductProbe(const QString &productName, bool condition, - const QVariantMap &initialProperties, - const QString &sourceCode) const; - ProbeConstPtr findCurrentProbe(const CodeLocation &location, bool condition, - const QVariantMap &initialProperties) const; - - enum class CompareScript { No, Yes }; - bool probeMatches(const ProbeConstPtr &probe, bool condition, - const QVariantMap &initialProperties, const QString &configureScript, - CompareScript compareScript) const; - - void printProfilingInfo(); - void handleProductError(const ErrorInfo &error, ProductContext *productContext); - QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group); - Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation); - void collectProductsByName(const TopLevelProjectContext &topLevelProject); - void collectProductsByType(const TopLevelProjectContext &topLevelProject); - - void handleProfileItems(Item *item, ProjectContext *projectContext); - std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext); - void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, - QVariantMap &values); - void handleProfile(Item *profileItem); - void collectNameFromOverride(const QString &overrideString); - void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp); - void checkProductNamesInOverrides(); - void setSearchPathsForProduct(ProductContext *product); - - Item::Modules modulesSortedByDependency(const Item *productItem); - void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules); - void collectAllModules(Item *item, std::vector<Item::Module> *modules); - std::vector<Item::Module> allModules(Item *item); - bool moduleRepresentsDisabledProduct(const Item::Module &module); - - using ShadowProductInfo = std::pair<bool, QString>; - ShadowProductInfo getShadowProductInfo(const ProductContext &product) const; - - ItemPool *m_pool; - Logger &m_logger; - ProgressObserver *m_progressObserver; - ItemReader *m_reader; - Evaluator *m_evaluator; - QMap<QString, QStringList> m_moduleDirListCache; - - // The keys are file paths, the values are module prototype items accompanied by a profile. - std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> m_modulePrototypes; - - // The keys are module prototypes and products, the values specify whether the module's - // condition is true for that product. - QHash<std::pair<Item *, ProductContext *>, bool> m_modulePrototypeEnabledInfo; - - QHash<const Item *, Item::PropertyDeclarationMap> m_parameterDeclarations; - Set<Item *> m_disabledItems; - std::vector<bool> m_requiredChain; - - struct DependsChainEntry - { - DependsChainEntry(const QualifiedId &name, const CodeLocation &location) - : name(name), location(location) - { - } - - QualifiedId name; - CodeLocation location; - bool isProduct = false; - }; - class DependsChainManager; - std::vector<DependsChainEntry> m_dependsChain; - - QHash<QString, QList<ProbeConstPtr>> m_oldProjectProbes; - QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes; - FileTime m_lastResolveTime; - QHash<CodeLocation, QList<ProbeConstPtr>> m_currentProbes; - QVariantMap m_storedProfiles; - QVariantMap m_localProfiles; - std::multimap<QString, const ProductContext *> m_productsByName; - std::multimap<FileTag, const ProductContext *> m_productsByType; - - std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems; - Set<Item *> m_exportsWithDeferredDependsItems; - - ModuleProviderInfoList m_moduleProviderInfo; - Set<QString> m_tempQbsFiles; - - SetupProjectParameters m_parameters; - std::unique_ptr<Settings> m_settings; - Version m_qbsVersion; - Item *m_tempScopeItem = nullptr; - - qint64 m_elapsedTimeProbes = 0; - qint64 m_elapsedTimePrepareProducts = 0; - qint64 m_elapsedTimeProductDependencies = 0; - qint64 m_elapsedTimeTransitiveDependencies = 0; - qint64 m_elapsedTimeHandleProducts = 0; - qint64 m_elapsedTimePropertyChecking = 0; - quint64 m_probesEncountered = 0; - quint64 m_probesRun = 0; - quint64 m_probesCachedCurrent = 0; - quint64 m_probesCachedOld = 0; - Set<QString> m_projectNamesUsedInOverrides; - Set<QString> m_productNamesUsedInOverrides; - Set<QString> m_disabledProjects; - Set<QString> m_erroneousProducts; - - int m_dependencyResolvingPass = 0; -}; - -} // 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/modulemerger.cpp b/src/lib/corelib/language/modulemerger.cpp deleted file mode 100644 index 053e90d53..000000000 --- a/src/lib/corelib/language/modulemerger.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "modulemerger.h" - -#include "value.h" - -#include <logging/translator.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -namespace qbs { -namespace Internal { - -ModuleMerger::ModuleMerger(Logger &logger, Item *root, Item::Module &moduleToMerge) - : m_logger(logger) - , m_rootItem(root) - , m_mergedModule(moduleToMerge) - , m_required(moduleToMerge.required) - , m_isBaseModule(moduleToMerge.name.first() == StringConstants::qbsModule()) - , m_versionRange(moduleToMerge.versionRange) -{ - QBS_CHECK(moduleToMerge.item->type() == ItemType::ModuleInstance); -} - -void ModuleMerger::replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace) -{ - QBS_CHECK(!moduleName.empty()); - QBS_CHECK(containerItem != m_mergedModule.item); - const QString moduleNamePrefix = moduleName.takeFirst(); - const Item::PropertyMap &properties = containerItem->properties(); - for (auto it = properties.begin(); it != properties.end(); ++it) { - if (it.key() != moduleNamePrefix) - continue; - Value * const val = it.value().get(); - QBS_CHECK(val); - QBS_CHECK(val->type() == Value::ItemValueType); - const auto itemVal = static_cast<ItemValue *>(val); - if (moduleName.empty()) { - QBS_CHECK(itemVal->item() == toReplace); - itemVal->setItem(m_mergedModule.item); - } else { - replaceItemInValues(moduleName, itemVal->item(), toReplace); - } - } -} - -void ModuleMerger::replaceItemInScopes(Item *toReplace) -{ - // In insertProperties(), we potentially call setDefiningItem() with the "wrong" - // (to-be-replaced) module instance as an argument. If such module instances - // are dependencies of other modules, they have the depending module's instance - // as their "instance scope", which is the scope of their scope. This function takes - // care that the "wrong" definingItem of values in sub-modules still has the "right" - // instance scope, namely our merged module instead of some other instance. - for (const Item::Module &module : toReplace->modules()) { - for (const ValuePtr &property : module.item->properties()) { - ValuePtr v = property; - do { - if (v->definingItem() && v->definingItem()->scope() - && v->definingItem()->scope()->scope() == toReplace) { - v->definingItem()->scope()->setScope(m_mergedModule.item); - } - v = v->next(); - } while (v); - } - } -} - -void ModuleMerger::start() -{ - Item::Module m; - m.item = m_rootItem; - const Item::PropertyMap props = dfs(m, Item::PropertyMap()); - if (m_required) - m_mergedModule.required = true; - m_mergedModule.versionRange.narrowDown(m_versionRange); - Item::PropertyMap mergedProps = m_mergedModule.item->properties(); - - Item *moduleProto = m_mergedModule.item->prototype(); - while (moduleProto->prototype()) - moduleProto = moduleProto->prototype(); - - for (auto it = props.constBegin(); it != props.constEnd(); ++it) { - appendPrototypeValueToNextChain(moduleProto, it.key(), it.value()); - mergedProps[it.key()] = it.value(); - } - m_mergedModule.item->setProperties(mergedProps); - - for (Item *moduleInstanceContainer : qAsConst(m_moduleInstanceContainers)) { - Item::Modules modules; - for (const Item::Module &dep : moduleInstanceContainer->modules()) { - const bool isTheModule = dep.name == m_mergedModule.name; - Item::Module m = dep; - if (isTheModule && m.item != m_mergedModule.item) { - QBS_CHECK(m.item->type() == ItemType::ModuleInstance); - replaceItemInValues(m.name, moduleInstanceContainer, m.item); - replaceItemInScopes(m.item); - m.item = m_mergedModule.item; - if (m_required) - m.required = true; - m.versionRange.narrowDown(m_versionRange); - } - modules << m; - } - moduleInstanceContainer->setModules(modules); - } -} - -Item::PropertyMap ModuleMerger::dfs(const Item::Module &m, Item::PropertyMap props) -{ - Item *moduleInstance = nullptr; - size_t numberOfOutprops = m.item->modules().size(); - for (const Item::Module &dep : m.item->modules()) { - if (dep.name == m_mergedModule.name) { - --numberOfOutprops; - moduleInstance = dep.item; - insertProperties(&props, moduleInstance, ScalarProperties); - m_moduleInstanceContainers << m.item; - if (dep.required) - m_required = true; - m_versionRange.narrowDown(dep.versionRange); - break; - } - } - - std::vector<Item::PropertyMap> outprops; - outprops.reserve(numberOfOutprops); - for (const Item::Module &dep : m.item->modules()) { - if (dep.item != moduleInstance) - outprops.push_back(dfs(dep, props)); - } - - if (!outprops.empty()) { - props = outprops.front(); - for (size_t i = 1; i < outprops.size(); ++i) - mergeOutProps(&props, outprops.at(i)); - } - - if (moduleInstance) - insertProperties(&props, moduleInstance, ListProperties); - - const bool isNonPresentModule = m.item->type() != ItemType::Product - && !m.item->isPresentModule(); - return isNonPresentModule ? Item::PropertyMap() : props; -} - -void ModuleMerger::mergeOutProps(Item::PropertyMap *dst, const Item::PropertyMap &src) -{ - for (auto it = src.constBegin(); it != src.constEnd(); ++it) { - ValuePtr &v = (*dst)[it.key()]; - if (!v) { - v = it.value(); - QBS_ASSERT(it.value(), continue); - continue; - } - if (v->type() != Value::JSSourceValueType) - continue; - if (it.value()->type() != Value::JSSourceValueType) - continue; - // possible conflict - const JSSourceValuePtr dstVal = std::static_pointer_cast<JSSourceValue>(v); - JSSourceValuePtr srcVal = std::static_pointer_cast<JSSourceValue>(it.value()); - - const PropertyDeclaration pd = m_decls.value(srcVal); - QBS_CHECK(pd.isValid()); - - if (pd.isScalar()) { - if (dstVal->sourceCode() != srcVal->sourceCode()) { - m_logger.qbsWarning() << Tr::tr("Conflicting scalar values at %1 and %2.").arg( - dstVal->location().toString(), - srcVal->location().toString()); - // TODO: yield error with a hint how to solve the conflict. - } - v = it.value(); - } else { - lastInNextChain(dstVal)->setNext(srcVal); - } - } -} - -void ModuleMerger::insertProperties(Item::PropertyMap *dst, Item *srcItem, PropertiesType type) -{ - Set<const Item *> &seenInstances = type == ScalarProperties - ? m_seenInstancesTopDown : m_seenInstancesBottomUp; - Item *origSrcItem = srcItem; - do { - if (seenInstances.insert(srcItem).second) { - for (Item::PropertyMap::const_iterator it = srcItem->properties().constBegin(); - it != srcItem->properties().constEnd(); ++it) { - const ValuePtr &srcVal = it.value(); - if (srcVal->type() == Value::ItemValueType) - continue; - if (it.key() == StringConstants::qbsSourceDirPropertyInternal()) - continue; - const PropertyDeclaration srcDecl = srcItem->propertyDeclaration(it.key()); - if (!srcDecl.isValid() || srcDecl.isScalar() != (type == ScalarProperties)) - continue; - - // Scalar variant values could stem from product multiplexing, in which case - // the merged qbs module instance needs to get that value. - if (srcVal->type() == Value::VariantValueType - && (!srcDecl.isScalar() || !m_isBaseModule)) { - continue; - } - - ValuePtr &v = (*dst)[it.key()]; - if (v && type == ScalarProperties) - continue; - ValuePtr clonedVal = srcVal->clone(); - m_decls[clonedVal] = srcDecl; - clonedVal->setDefiningItem(origSrcItem); - if (v) { - QBS_CHECK(!clonedVal->next()); - clonedVal->setNext(v); - } - v = clonedVal; - } - } - srcItem = srcItem->prototype(); - } while (srcItem && srcItem->type() == ItemType::ModuleInstance); -} - -void ModuleMerger::appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, - const ValuePtr &sv) -{ - const PropertyDeclaration pd = m_mergedModule.item->propertyDeclaration(propertyName); - if (pd.isScalar()) - return; - if (!m_clonedModulePrototype) { - m_clonedModulePrototype = Item::create(moduleProto->pool(), ItemType::Module); - m_clonedModulePrototype->setScope(m_mergedModule.item); - m_clonedModulePrototype->setLocation(moduleProto->location()); - moduleProto->copyProperty(StringConstants::nameProperty(), m_clonedModulePrototype); - } - const ValuePtr &protoValue = moduleProto->property(propertyName); - QBS_CHECK(protoValue); - const ValuePtr clonedValue = protoValue->clone(); - lastInNextChain(sv)->setNext(clonedValue); - clonedValue->setDefiningItem(m_clonedModulePrototype); - m_clonedModulePrototype->setPropertyDeclaration(propertyName, pd); - m_clonedModulePrototype->setProperty(propertyName, clonedValue); -} - -ValuePtr ModuleMerger::lastInNextChain(const ValuePtr &v) -{ - ValuePtr n = v; - while (n->next()) - n = n->next(); - return n; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h index fef9d9765..c35ed220a 100644 --- a/src/lib/corelib/language/moduleproviderinfo.h +++ b/src/lib/corelib/language/moduleproviderinfo.h @@ -55,9 +55,12 @@ class ModuleProviderInfo { public: ModuleProviderInfo() = default; - ModuleProviderInfo(const QualifiedId &name, const QVariantMap &config, - const QStringList &searchPaths, bool transientOutput) - : name(name), config(config), searchPaths(searchPaths), transientOutput(transientOutput) + ModuleProviderInfo(QualifiedId name, QVariantMap config, + QStringList searchPaths, bool transientOutput) + : name(std::move(name)) + , config(std::move(config)) + , searchPaths(std::move(searchPaths)) + , transientOutput(transientOutput) {} static QString outputBaseDirName() { return QStringLiteral("genmodules"); } @@ -73,16 +76,39 @@ public: template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { - pool.serializationOp<opType>(reinterpret_cast<QStringList &>(name), config, searchPaths); + pool.serializationOp<opType>( + reinterpret_cast<QStringList &>(name), config, providerFile, searchPaths); } QualifiedId name; QVariantMap config; + QString providerFile; + bool isEager{true}; QStringList searchPaths; + QHash<QString, QStringList> searchPathsByModule; bool transientOutput = false; // Not to be serialized. }; -using ModuleProviderInfoList = std::vector<ModuleProviderInfo>; +using ModuleProvidersCacheKey = std::tuple< + QString /*name*/, + QString /*moduleName*/, + QVariantMap /*config*/, + QVariantMap /*qbsModule*/, + int /*lookup*/ +>; +using ModuleProvidersCache = QHash<ModuleProvidersCacheKey, ModuleProviderInfo>; + +// Persistent info stored between sessions +class StoredModuleProviderInfo +{ +public: + ModuleProvidersCache providers; + + template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp<opType>(providers); + } +}; } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/preparescriptobserver.cpp b/src/lib/corelib/language/preparescriptobserver.cpp index 632cbfb51..11536e74d 100644 --- a/src/lib/corelib/language/preparescriptobserver.cpp +++ b/src/lib/corelib/language/preparescriptobserver.cpp @@ -48,8 +48,6 @@ #include <tools/stlutils.h> #include <tools/stringconstants.h> -#include <QtScript/qscriptvalue.h> - namespace qbs { namespace Internal { @@ -58,22 +56,23 @@ PrepareScriptObserver::PrepareScriptObserver(ScriptEngine *engine, UnobserveMode { } -void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) +void PrepareScriptObserver::onPropertyRead(const JSValue &object, const QString &name, + const JSValue &value) { - const auto objectId = object.objectId(); + JSContext * const ctx = engine()->context(); + const auto objectId = jsObjectId(object); const auto projectIt = m_projectObjectIds.find(objectId); if (projectIt != m_projectObjectIds.cend()) { engine()->addPropertyRequestedInScript( - Property(projectIt->second, QString(), name, value.toVariant(), + Property(projectIt->second, QString(), name, getJsVariant(ctx, value), Property::PropertyInProject)); return; } if (m_importIds.contains(objectId)) { - engine()->addImportRequestedInScript(object.objectId()); + engine()->addImportRequestedInScript(jsObjectId(object)); return; } - const auto exportsIt = m_exportsObjectIds.find(value.objectId()); + const auto exportsIt = m_exportsObjectIds.find(jsObjectId(object)); if (exportsIt != m_exportsObjectIds.cend()) { engine()->addRequestedExport(exportsIt->second); return; @@ -81,13 +80,14 @@ void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QSt const auto it = m_parameterObjects.find(objectId); if (it != m_parameterObjects.cend()) { engine()->addPropertyRequestedInScript( - Property(it->second.first, it->second.second, name, value.toVariant(), + Property(it->second.first, it->second.second, name, getJsVariant(ctx, value), Property::PropertyInParameters)); } if (name == StringConstants::fileTagsProperty() && m_artifactIds.contains(objectId)) { - const Artifact * const artifact = attachedPointer<Artifact>(object); + const Artifact * const artifact = attachedPointer<Artifact>(object, + engine()->dataWithPtrClass()); QBS_CHECK(artifact); - const Property p(artifact->product->uniqueName(), QString(), name, value.toVariant(), + const Property p(artifact->product->uniqueName(), QString(), name, getJsVariant(ctx, value), Property::PropertyInArtifact); engine()->addPropertyRequestedFromArtifact(artifact, p); } diff --git a/src/lib/corelib/language/preparescriptobserver.h b/src/lib/corelib/language/preparescriptobserver.h index 36e395efc..5dc54cbb6 100644 --- a/src/lib/corelib/language/preparescriptobserver.h +++ b/src/lib/corelib/language/preparescriptobserver.h @@ -44,6 +44,8 @@ #include <tools/set.h> +#include <quickjs.h> + #include <QtCore/qstring.h> #include <unordered_map> @@ -69,7 +71,7 @@ public: } void addArtifactId(qint64 artifactId) { m_artifactIds.insert(artifactId); } - bool addImportId(qint64 importId) { return m_importIds.insert(importId).second; } + bool addImportId(quintptr importId) { return m_importIds.insert(importId).second; } void clearImportIds() { m_importIds.clear(); } void addParameterObjectId(qint64 id, const QString &productName, const QString &depName, const QualifiedId &moduleName) @@ -79,14 +81,13 @@ public: m_parameterObjects.insert(std::make_pair(id, value)); } -private: - void onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) override; + void onPropertyRead(const JSValue &object, const QString &name, const JSValue &value) override; +private: std::unordered_map<qint64, QString> m_projectObjectIds; std::unordered_map<qint64, std::pair<QString, QString>> m_parameterObjects; std::unordered_map<qint64, const ResolvedProduct *> m_exportsObjectIds; - Set<qint64> m_importIds; + Set<quintptr> m_importIds; Set<qint64> m_artifactIds; }; diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp deleted file mode 100644 index d5b84af16..000000000 --- a/src/lib/corelib/language/projectresolver.cpp +++ /dev/null @@ -1,1875 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "projectresolver.h" - -#include "artifactproperties.h" -#include "builtindeclarations.h" -#include "evaluator.h" -#include "filecontext.h" -#include "item.h" -#include "language.h" -#include "propertymapinternal.h" -#include "resolvedfilecontext.h" -#include "scriptengine.h" -#include "value.h" - -#include <jsextensions/jsextensions.h> -#include <jsextensions/moduleproperties.h> -#include <logging/categories.h> -#include <logging/translator.h> -#include <tools/error.h> -#include <tools/fileinfo.h> -#include <tools/joblimits.h> -#include <tools/jsliterals.h> -#include <tools/profiling.h> -#include <tools/progressobserver.h> -#include <tools/scripttools.h> -#include <tools/qbsassert.h> -#include <tools/qttools.h> -#include <tools/setupprojectparameters.h> -#include <tools/stlutils.h> -#include <tools/stringconstants.h> - -#include <QtCore/qdir.h> -#include <QtCore/qregexp.h> - -#include <algorithm> -#include <memory> -#include <queue> - -namespace qbs { -namespace Internal { - -extern bool debugProperties; - -static const FileTag unknownFileTag() -{ - static const FileTag tag("unknown-file-tag"); - return tag; -} - -struct ProjectResolver::ProjectContext -{ - ProjectContext *parentContext = nullptr; - ResolvedProjectPtr project; - std::vector<FileTaggerConstPtr> fileTaggers; - std::vector<RulePtr> rules; - JobLimits jobLimits; - ResolvedModulePtr dummyModule; -}; - -struct ProjectResolver::ProductContext -{ - ResolvedProductPtr product; - QString buildDirectory; - Item *item = nullptr; - using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>; - QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter; - ProjectResolver::FileLocations sourceArtifactLocations; - GroupConstPtr currentGroup; -}; - -struct ProjectResolver::ModuleContext -{ - ResolvedModulePtr module; - JobLimits jobLimits; -}; - -class CancelException { }; - - -ProjectResolver::ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult, - SetupProjectParameters setupParameters, Logger &logger) - : m_evaluator(evaluator) - , m_logger(logger) - , m_engine(m_evaluator->engine()) - , m_progressObserver(nullptr) - , m_setupParams(std::move(setupParameters)) - , m_loadResult(std::move(loadResult)) -{ - QBS_CHECK(FileInfo::isAbsolute(m_setupParams.buildRoot())); -} - -ProjectResolver::~ProjectResolver() = default; - -void ProjectResolver::setProgressObserver(ProgressObserver *observer) -{ - m_progressObserver = observer; -} - -static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) -{ - const std::vector<ResolvedProductPtr> allProducts = project->allProducts(); - for (size_t i = 0; i < allProducts.size(); ++i) { - const ResolvedProductConstPtr product1 = allProducts.at(i); - const QString productName = product1->uniqueName(); - for (size_t j = i + 1; j < allProducts.size(); ++j) { - const ResolvedProductConstPtr product2 = allProducts.at(j); - if (product2->uniqueName() == productName) { - ErrorInfo error; - error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); - 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() -{ - TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"), - m_setupParams.logElapsedTime()); - qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath(); - - m_productContext = nullptr; - m_moduleContext = nullptr; - m_elapsedTimeModPropEval = m_elapsedTimeAllPropEval = m_elapsedTimeGroups = 0; - TopLevelProjectPtr tlp; - try { - tlp = resolveTopLevelProject(); - printProfilingInfo(); - } catch (const CancelException &) { - throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.") - .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); - } - return tlp; -} - -void ProjectResolver::checkCancelation() const -{ - if (m_progressObserver && m_progressObserver->canceled()) - throw CancelException(); -} - -QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propertyWasSet) const -{ - QString result; - if (value && value->type() == Value::JSSourceValueType) { - const JSSourceValueConstPtr sourceValue = std::static_pointer_cast<const JSSourceValue>( - value); - result = sourceCodeForEvaluation(sourceValue); - if (propertyWasSet) - *propertyWasSet = !sourceValue->isBuiltinDefaultValue(); - } else { - if (propertyWasSet) - *propertyWasSet = false; - } - return result; -} - -QString ProjectResolver::verbatimValue(Item *item, const QString &name, bool *propertyWasSet) const -{ - return verbatimValue(item->property(name), propertyWasSet); -} - -void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) -{ - Q_UNUSED(item); - Q_UNUSED(projectContext); -} - -static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) -{ - Set<QString> subProjectNames; - Set<ResolvedProjectPtr> projectsInNeedOfNameChange; - for (const ResolvedProjectPtr &p : qAsConst(parentProject->subProjects)) { - if (!subProjectNames.insert(p->name).second) - projectsInNeedOfNameChange << p; - makeSubProjectNamesUniqe(p); - } - while (!projectsInNeedOfNameChange.empty()) { - auto it = projectsInNeedOfNameChange.begin(); - while (it != projectsInNeedOfNameChange.end()) { - const ResolvedProjectPtr p = *it; - p->name += QLatin1Char('_'); - if (subProjectNames.insert(p->name).second) { - it = projectsInNeedOfNameChange.erase(it); - } else { - ++it; - } - } - } -} - -TopLevelProjectPtr ProjectResolver::resolveTopLevelProject() -{ - if (m_progressObserver) - m_progressObserver->setMaximum(m_loadResult.productInfos.size()); - const TopLevelProjectPtr project = TopLevelProject::create(); - project->buildDirectory = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), - TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree())); - project->buildSystemFiles = m_loadResult.qbsFiles; - project->profileConfigs = m_loadResult.profileConfigs; - project->probes = m_loadResult.projectProbes; - project->moduleProviderInfo = m_loadResult.moduleProviderInfo; - ProjectContext projectContext; - projectContext.project = project; - - resolveProject(m_loadResult.root, &projectContext); - ErrorInfo accumulatedErrors; - for (const ErrorInfo &e : m_queuedErrors) - appendError(accumulatedErrors, e); - if (accumulatedErrors.hasError()) - throw accumulatedErrors; - - project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); - project->overriddenValues = m_setupParams.overriddenValues(); - project->canonicalFilePathResults = m_engine->canonicalFilePathResults(); - project->fileExistsResults = m_engine->fileExistsResults(); - project->directoryEntriesResults = m_engine->directoryEntriesResults(); - project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); - project->environment = m_engine->environment(); - project->buildSystemFiles.unite(m_engine->imports()); - makeSubProjectNamesUniqe(project); - resolveProductDependencies(projectContext); - collectExportedProductDependencies(); - checkForDuplicateProductNames(project); - - for (const ResolvedProductPtr &product : project->allProducts()) { - if (!product->enabled) - continue; - - applyFileTaggers(product); - matchArtifactProperties(product, product->allEnabledFiles()); - - // Let a positive value of qbs.install imply the file tag "installable". - for (const SourceArtifactPtr &artifact : product->allFiles()) { - if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) - artifact->fileTags += "installable"; - } - } - project->warningsEncountered = m_logger.warnings(); - return project; -} - -void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - - if (projectContext->parentContext) - projectContext->project->enabled = projectContext->parentContext->project->enabled; - projectContext->project->location = item->location(); - try { - resolveProjectFully(item, projectContext); - } catch (const ErrorInfo &error) { - if (!projectContext->project->enabled) { - qCDebug(lcProjectResolver) << "error resolving project" - << projectContext->project->location << error.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } -} - -void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - projectContext->project->enabled = projectContext->project->enabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - projectContext->project->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - if (projectContext->project->name.isEmpty()) - projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item? - QVariantMap projectProperties; - if (!projectContext->project->enabled) { - projectProperties.insert(StringConstants::profileProperty(), - m_evaluator->stringValue(item, - StringConstants::profileProperty())); - projectContext->project->setProjectProperties(projectProperties); - return; - } - - projectContext->dummyModule = ResolvedModule::create(); - - for (Item::PropertyDeclarationMap::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->value(item, it.key()).toVariant()); - } - projectContext->project->setProjectProperties(projectProperties); - - static const ItemFuncMap mapping = { - { ItemType::Project, &ProjectResolver::resolveProject }, - { ItemType::SubProject, &ProjectResolver::resolveSubProject }, - { ItemType::Product, &ProjectResolver::resolveProduct }, - { ItemType::Probe, &ProjectResolver::ignoreItem }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } - }; - - for (Item * const child : item->children()) { - try { - callItemFunction(mapping, child, projectContext); - } catch (const ErrorInfo &e) { - m_queuedErrors.push_back(e); - } - } - - for (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(ItemType::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(ItemType::PropertiesInSubProject); - if (propertiesItem) { - subProjectContext.project->name - = m_evaluator->stringValue(propertiesItem, StringConstants::nameProperty()); - } -} - -class ProjectResolver::ProductContextSwitcher -{ -public: - ProductContextSwitcher(ProjectResolver *resolver, ProductContext *newContext, - ProgressObserver *progressObserver) - : m_resolver(resolver), m_progressObserver(progressObserver) - { - QBS_CHECK(!m_resolver->m_productContext); - m_resolver->m_productContext = newContext; - } - - ~ProductContextSwitcher() - { - if (m_progressObserver) - m_progressObserver->incrementProgressValue(); - m_resolver->m_productContext = nullptr; - } - -private: - ProjectResolver * const m_resolver; - ProgressObserver * const m_progressObserver; -}; - -void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - m_evaluator->clearPropertyDependencies(); - ProductContext productContext; - productContext.item = item; - ResolvedProductPtr product = ResolvedProduct::create(); - product->enabled = projectContext->project->enabled; - product->moduleProperties = PropertyMapInternal::create(); - product->project = projectContext->project; - productContext.product = product; - product->location = item->location(); - ProductContextSwitcher contextSwitcher(this, &productContext, m_progressObserver); - try { - resolveProductFully(item, projectContext); - } catch (const ErrorInfo &e) { - QString mainErrorString = !product->name.isEmpty() - ? Tr::tr("Error while handling product '%1':").arg(product->name) - : Tr::tr("Error while handling product:"); - ErrorInfo fullError(mainErrorString, item->location()); - appendError(fullError, e); - if (!product->enabled) { - qCDebug(lcProjectResolver) << fullError.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw fullError; - m_logger.printWarning(fullError); - m_logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") - .arg(product->name), item->location())); - product->enabled = false; - } -} - -void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectContext) -{ - const ResolvedProductPtr product = m_productContext->product; - m_productItemMap.insert(product, item); - projectContext->project->products.push_back(product); - product->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - - // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. - m_productContext->buildDirectory - = m_evaluator->stringValue(item, StringConstants::buildDirectoryProperty()); - product->multiplexConfigurationId - = m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty()); - qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName(); - m_productsByName.insert(product->uniqueName(), product); - product->enabled = product->enabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item]; - if (pi.delayedError.hasError()) { - ErrorInfo errorInfo; - - // First item is "main error", gets prepended again in the catch clause. - const QList<ErrorItem> &items = pi.delayedError.items(); - for (int i = 1; i < items.size(); ++i) - errorInfo.append(items.at(i)); - - pi.delayedError.clear(); - throw errorInfo; - } - gatherProductTypes(product.get(), item); - product->targetName = m_evaluator->stringValue(item, StringConstants::targetNameProperty()); - product->sourceDirectory = m_evaluator->stringValue( - item, StringConstants::sourceDirectoryProperty()); - product->destinationDirectory = m_evaluator->stringValue( - item, StringConstants::destinationDirProperty()); - - if (product->destinationDirectory.isEmpty()) { - product->destinationDirectory = m_productContext->buildDirectory; - } else { - product->destinationDirectory = FileInfo::resolvePath( - product->topLevelProject()->buildDirectory, - product->destinationDirectory); - } - product->probes = pi.probes; - createProductConfig(product.get()); - product->productProperties.insert(StringConstants::destinationDirProperty(), - product->destinationDirectory); - ModuleProperties::init(m_evaluator->scriptValue(item), product.get()); - - QList<Item *> subItems = item->children(); - const ValuePtr filesProperty = item->property(StringConstants::filesProperty()); - if (filesProperty) { - Item *fakeGroup = Item::create(item->pool(), ItemType::Group); - fakeGroup->setFile(item->file()); - fakeGroup->setLocation(item->location()); - fakeGroup->setScope(item); - fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name)); - fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty); - fakeGroup->setProperty(StringConstants::excludeFilesProperty(), - item->property(StringConstants::excludeFilesProperty())); - fakeGroup->setProperty(StringConstants::overrideTagsProperty(), - VariantValue::falseValue()); - fakeGroup->setupForBuiltinType(m_logger); - subItems.prepend(fakeGroup); - } - - static const ItemFuncMap mapping = { - { ItemType::Depends, &ProjectResolver::ignoreItem }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Group, &ProjectResolver::resolveGroup }, - { ItemType::Product, &ProjectResolver::resolveShadowProduct }, - { ItemType::Export, &ProjectResolver::resolveExport }, - { ItemType::Probe, &ProjectResolver::ignoreItem }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } - }; - - for (Item * const child : qAsConst(subItems)) - callItemFunction(mapping, child, projectContext); - - for (const ProjectContext *p = projectContext; p; p = p->parentContext) { - JobLimits tempLimits = p->jobLimits; - product->jobLimits = tempLimits.update(product->jobLimits); - } - - resolveModules(item, projectContext); - - for (const FileTag &t : qAsConst(product->fileTags)) - m_productsByType[t].push_back(product); -} - -void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext) -{ - JobLimits jobLimits; - for (const Item::Module &m : item->modules()) - resolveModule(m.name, m.item, m.isProduct, m.parameters, jobLimits, projectContext); - for (int i = 0; i < jobLimits.count(); ++i) { - const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i); - if (m_productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1) - m_productContext->product->jobLimits.setJobLimit(moduleJobLimit); - } -} - -void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, - const QVariantMap ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext) -{ - checkCancelation(); - if (!item->isPresentModule()) - return; - - ModuleContext * const oldModuleContext = m_moduleContext; - ModuleContext moduleContext; - moduleContext.module = ResolvedModule::create(); - m_moduleContext = &moduleContext; - - const ResolvedModulePtr &module = moduleContext.module; - module->name = moduleName.toString(); - module->isProduct = isProduct; - module->product = m_productContext->product.get(); - module->setupBuildEnvironmentScript.initialize( - scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty())); - module->setupRunEnvironmentScript.initialize( - scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty())); - - for (const Item::Module &m : item->modules()) { - if (m.item->isPresentModule()) - module->moduleDependencies += m.name.toString(); - } - - m_productContext->product->modules.push_back(module); - if (!parameters.empty()) - m_productContext->product->moduleParameters[module] = parameters; - - static const ItemFuncMap mapping { - { ItemType::Group, &ProjectResolver::ignoreItem }, - { ItemType::Rule, &ProjectResolver::resolveRule }, - { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, - { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, - { ItemType::Scanner, &ProjectResolver::resolveScanner }, - { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }, - { ItemType::Depends, &ProjectResolver::ignoreItem }, - { ItemType::Parameter, &ProjectResolver::ignoreItem }, - { ItemType::Properties, &ProjectResolver::ignoreItem }, - { ItemType::Probe, &ProjectResolver::ignoreItem } - }; - for (Item *child : item->children()) - callItemFunction(mapping, child, projectContext); - for (int i = 0; i < moduleContext.jobLimits.count(); ++i) { - const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i); - const int oldLimit = jobLimits.getLimit(newJobLimit.pool()); - if (oldLimit == -1 || oldLimit > newJobLimit.limit()) - jobLimits.setJobLimit(newJobLimit); - } - - m_moduleContext = oldModuleContext; -} - -void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item) -{ - product->fileTags = m_evaluator->fileTagsValue(item, StringConstants::typeProperty()); - for (const Item::Module &m : item->modules()) { - if (m.item->isPresentModule()) { - product->fileTags += m_evaluator->fileTagsValue(m.item, - StringConstants::additionalProductTypesProperty()); - } - } - item->setProperty(StringConstants::typeProperty(), - VariantValue::create(sorted(product->fileTags.toStringList()))); -} - -SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct, - const QString &fileName, const GroupPtr &group, bool wildcard, - const CodeLocation &filesLocation, FileLocations *fileLocations, - ErrorInfo *errorInfo) -{ - const QString &baseDir = FileInfo::path(group->location.filePath()); - const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName)); - if (!wildcard && !FileInfo(absFilePath).exists()) { - if (errorInfo) - errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation); - rproduct->missingSourceFiles << absFilePath; - return {}; - } - if (group->enabled && fileLocations) { - CodeLocation &loc = (*fileLocations)[std::make_pair(group->targetOfModule, absFilePath)]; - if (loc.isValid()) { - if (errorInfo) { - errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath)); - errorInfo->append(Tr::tr("First occurrence is here."), loc); - errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation); - } - return {}; - } - loc = filesLocation; - } - SourceArtifactPtr artifact = SourceArtifactInternal::create(); - artifact->absoluteFilePath = absFilePath; - artifact->fileTags = group->fileTags; - artifact->overrideFileTags = group->overrideTags; - artifact->properties = group->properties; - artifact->targetOfModule = group->targetOfModule; - (wildcard ? group->wildcards->files : group->files).push_back(artifact); - return artifact; -} - -static QualifiedIdSet propertiesToEvaluate(const QList<QualifiedId> &initialProps, - const PropertyDependencies &deps) -{ - QList<QualifiedId> remainingProps = initialProps; - QualifiedIdSet allProperties; - while (!remainingProps.empty()) { - const QualifiedId prop = remainingProps.takeFirst(); - const auto insertResult = allProperties.insert(prop); - if (!insertResult.second) - continue; - for (const QualifiedId &directDep : deps.value(prop)) - remainingProps.push_back(directDep); - } - return allProperties; -} - -QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group, - const QVariantMap ¤tValues) -{ - // Step 1: Retrieve the properties directly set in the group - const ModulePropertiesPerGroup &mp = m_loadResult.productInfos.value(m_productContext->item) - .modulePropertiesSetInGroups; - const auto it = mp.find(group); - if (it == mp.end()) - return {}; - const QualifiedIdSet &propsSetInGroup = it->second; - - // Step 2: Gather all properties that depend on these properties. - const QualifiedIdSet &propsToEval - = propertiesToEvaluate(propsSetInGroup.toList(), m_evaluator->propertyDependencies()); - - // Step 3: Evaluate all these properties and replace their values in the map - QVariantMap modulesMap = currentValues; - QHash<QString, QStringList> propsPerModule; - for (auto fullPropName : propsToEval) { - const QString moduleName - = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString(); - propsPerModule[moduleName] << fullPropName.last(); - } - EvalCacheEnabler cachingEnabler(m_evaluator); - m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory); - for (const Item::Module &module : group->modules()) { - const QString &fullModName = module.name.toString(); - const QStringList propsForModule = propsPerModule.take(fullModName); - if (propsForModule.empty()) - continue; - QVariantMap reusableValues = modulesMap.value(fullModName).toMap(); - for (const QString &prop : qAsConst(propsForModule)) - reusableValues.remove(prop); - modulesMap.insert(fullModName, - evaluateProperties(module.item, module.item, reusableValues, true, true)); - } - m_evaluator->clearPathPropertiesBaseDir(); - return modulesMap; -} - -void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - const bool parentEnabled = m_productContext->currentGroup - ? m_productContext->currentGroup->enabled - : m_productContext->product->enabled; - const bool isEnabled = parentEnabled - && m_evaluator->boolValue(item, StringConstants::conditionProperty()); - try { - resolveGroupFully(item, projectContext, isEnabled); - } catch (const ErrorInfo &error) { - if (!isEnabled) { - qCDebug(lcProjectResolver) << "error resolving group at" << item->location() - << error.toString(); - return; - } - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw; - m_logger.printWarning(error); - } -} - -void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectContext *projectContext, - bool isEnabled) -{ - AccumulatingTimer groupTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeGroups : nullptr); - - const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) { - PropertyMapPtr moduleProperties; - bool newPropertyMapRequired = false; - if (existingProps) - moduleProperties = existingProps->propertyMap(); - if (!moduleProperties) { - newPropertyMapRequired = true; - moduleProperties = m_productContext->currentGroup - ? m_productContext->currentGroup->properties - : m_productContext->product->moduleProperties; - } - const QVariantMap newModuleProperties - = resolveAdditionalModuleProperties(item, moduleProperties->value()); - if (!newModuleProperties.empty()) { - if (newPropertyMapRequired) - moduleProperties = PropertyMapInternal::create(); - moduleProperties->setValue(newModuleProperties); - } - return moduleProperties; - }; - - QStringList files = m_evaluator->stringListValue(item, StringConstants::filesProperty()); - bool fileTagsSet; - const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty(), - &fileTagsSet); - const QStringList fileTagsFilter - = m_evaluator->stringListValue(item, StringConstants::fileTagsFilterProperty()); - if (!fileTagsFilter.empty()) { - if (Q_UNLIKELY(!files.empty())) - throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), - item->location()); - - if (!isEnabled) - return; - - ProductContext::ArtifactPropertiesInfo &apinfo - = m_productContext->artifactPropertiesPerFilter[fileTagsFilter]; - if (apinfo.first) { - const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(), - [item](const CodeLocation &loc) { - return item->location().filePath() == loc.filePath(); - }); - if (it != apinfo.second.cend()) { - ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); - error.append(Tr::tr("First item"), *it); - error.append(Tr::tr("Second item"), item->location()); - throw error; - } - } else { - apinfo.first = ArtifactProperties::create(); - apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); - m_productContext->product->artifactProperties.push_back(apinfo.first); - } - apinfo.second.push_back(item->location()); - apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get())); - apinfo.first->addExtraFileTags(fileTags); - return; - } - QStringList patterns; - for (int i = files.size(); --i >= 0;) { - if (FileInfo::isPattern(files[i])) - patterns.push_back(files.takeAt(i)); - } - GroupPtr group = ResolvedGroup::create(); - bool prefixWasSet = false; - group->prefix = m_evaluator->stringValue(item, StringConstants::prefixProperty(), QString(), - &prefixWasSet); - if (!prefixWasSet && m_productContext->currentGroup) - group->prefix = m_productContext->currentGroup->prefix; - if (!group->prefix.isEmpty()) { - for (auto it = files.rbegin(), end = files.rend(); it != end; ++it) - it->prepend(group->prefix); - } - group->location = item->location(); - group->enabled = isEnabled; - group->properties = getGroupPropertyMap(nullptr); - group->fileTags = fileTags; - group->overrideTags = m_evaluator->boolValue(item, StringConstants::overrideTagsProperty()); - if (group->overrideTags && fileTagsSet) { - if (group->fileTags.empty() ) - group->fileTags.insert(unknownFileTag()); - } else if (m_productContext->currentGroup) { - group->fileTags.unite(m_productContext->currentGroup->fileTags); - } - - const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location(); - const VariantValueConstPtr moduleProp = item->variantProperty( - StringConstants::modulePropertyInternal()); - if (moduleProp) - group->targetOfModule = moduleProp->value().toString(); - ErrorInfo fileError; - if (!patterns.empty()) { - group->wildcards = std::make_unique<SourceWildCards>(); - SourceWildCards *wildcards = group->wildcards.get(); - wildcards->group = group.get(); - wildcards->excludePatterns = m_evaluator->stringListValue( - item, StringConstants::excludeFilesProperty()); - wildcards->patterns = patterns; - const Set<QString> files = wildcards->expandPatterns(group, - FileInfo::path(item->file()->filePath()), - projectContext->project->topLevelProject()->buildDirectory); - for (const QString &fileName : files) - createSourceArtifact(m_productContext->product, fileName, group, true, filesLocation, - &m_productContext->sourceArtifactLocations, &fileError); - } - - for (const QString &fileName : qAsConst(files)) { - createSourceArtifact(m_productContext->product, fileName, group, false, filesLocation, - &m_productContext->sourceArtifactLocations, &fileError); - } - if (fileError.hasError()) { - if (group->enabled) { - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw ErrorInfo(fileError); - m_logger.printWarning(fileError); - } else { - qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString(); - } - } - group->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - if (group->name.isEmpty()) - group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.size()); - m_productContext->product->groups.push_back(group); - - class GroupContextSwitcher { - public: - GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup) - : m_context(context), m_oldGroup(context.currentGroup) { - m_context.currentGroup = newGroup; - } - ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; } - private: - ProductContext &m_context; - const GroupConstPtr m_oldGroup; - }; - GroupContextSwitcher groupSwitcher(*m_productContext, group); - for (Item * const childItem : item->children()) - resolveGroup(childItem, projectContext); -} - -void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem) -{ - ExportedModule &m = m_productContext->product->exportedModule; - const QVariantList prefixList = m.propertyValues.take( - StringConstants::prefixMappingProperty()).toList(); - const QString shadowProductName = m_evaluator->stringValue( - shadowProductItem, StringConstants::nameProperty()); - const QString shadowProductBuildDir = m_evaluator->stringValue( - shadowProductItem, StringConstants::buildDirectoryProperty()); - QVariantMap prefixMap; - for (const QVariant &v : prefixList) { - const QVariantMap o = v.toMap(); - prefixMap.insert(o.value(QStringLiteral("prefix")).toString(), - o.value(QStringLiteral("replacement")).toString()); - } - const auto valueRefersToImportingProduct - = [shadowProductName, shadowProductBuildDir](const QString &value) { - return value.toLower().contains(shadowProductName.toLower()) - || value.contains(shadowProductBuildDir); - }; - static const auto stringMapper = [](const QVariantMap &mappings, const QString &value) - -> QString { - for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) { - if (value.startsWith(it.key())) - return it.value().toString() + value.mid(it.key().size()); - } - return value; - }; - const auto stringListMapper = [&valueRefersToImportingProduct]( - const QVariantMap &mappings, const QStringList &value) -> QStringList { - QStringList result; - result.reserve(value.size()); - for (const QString &s : value) { - if (!valueRefersToImportingProduct(s)) - result.push_back(stringMapper(mappings, s)); - } - return result; - }; - const std::function<QVariant(const QVariantMap &, const QVariant &)> mapper - = [&stringListMapper, &mapper]( - const QVariantMap &mappings, const QVariant &value) -> QVariant { - switch (static_cast<QMetaType::Type>(value.type())) { - case QMetaType::QString: - return stringMapper(mappings, value.toString()); - case QMetaType::QStringList: - return stringListMapper(mappings, value.toStringList()); - case QMetaType::QVariantMap: { - QVariantMap m = value.toMap(); - for (auto it = m.begin(); it != m.end(); ++it) - it.value() = mapper(mappings, it.value()); - return m; - } - default: - return value; - } - }; - for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - for (ExportedModuleDependency &dep : m.moduleDependencies) { - for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it) - it.value() = mapper(prefixMap, it.value()); - } -} - -void ProjectResolver::collectExportedProductDependencies() -{ - ResolvedProductPtr dummyProduct = ResolvedProduct::create(); - dummyProduct->enabled = false; - for (const auto &exportingProductInfo : qAsConst(m_productExportInfo)) { - const ResolvedProductPtr exportingProduct = exportingProductInfo.first; - if (!exportingProduct->enabled) - continue; - Item * const importingProductItem = exportingProductInfo.second; - std::vector<QString> directDepNames; - for (const Item::Module &m : importingProductItem->modules()) { - if (m.name.toString() == exportingProduct->name) { - for (const Item::Module &dep : m.item->modules()) { - if (dep.isProduct) - directDepNames.push_back(dep.name.toString()); - } - break; - } - } - const ModuleLoaderResult::ProductInfo &importingProductInfo - = m_loadResult.productInfos.value(importingProductItem); - const ProductDependencyInfos &depInfos - = getProductDependencies(dummyProduct, importingProductInfo); - for (const auto &dep : depInfos.dependencies) { - if (dep.product == exportingProduct) - continue; - - // Filter out indirect dependencies. - // TODO: Depends items using "profile" or "productTypes" will not work. - if (!contains(directDepNames, dep.product->name)) - continue; - - if (!contains(exportingProduct->exportedModule.productDependencies, - dep.product->uniqueName())) { - exportingProduct->exportedModule.productDependencies.push_back( - dep.product->uniqueName()); - } - if (!dep.parameters.isEmpty()) { - exportingProduct->exportedModule.dependencyParameters.insert(dep.product, - dep.parameters); - } - } - auto &productDeps = exportingProduct->exportedModule.productDependencies; - std::sort(productDeps.begin(), productDeps.end()); - } -} - -void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectContext *) -{ - if (!m_productContext->product->enabled) - return; - for (const auto &m : item->modules()) { - if (m.name.toString() != m_productContext->product->name) - continue; - collectPropertiesForExportItem(m.item); - for (const auto &dep : m.item->modules()) - collectPropertiesForModuleInExportItem(dep); - break; - } - try { - adaptExportedPropertyValues(item); - } catch (const ErrorInfo &) {} - m_productExportInfo.emplace_back(m_productContext->product, item); -} - -void ProjectResolver::setupExportedProperties(const Item *item, const QString &namePrefix, - std::vector<ExportedProperty> &properties) -{ - const auto &props = item->properties(); - for (auto it = props.cbegin(); it != props.cend(); ++it) { - const QString qualifiedName = namePrefix.isEmpty() - ? it.key() : namePrefix + QLatin1Char('.') + it.key(); - if ((item->type() == ItemType::Export || item->type() == ItemType::Properties) - && qualifiedName == StringConstants::prefixMappingProperty()) { - continue; - } - const ValuePtr &v = it.value(); - if (v->type() == Value::ItemValueType) { - setupExportedProperties(std::static_pointer_cast<ItemValue>(v)->item(), - qualifiedName, properties); - continue; - } - ExportedProperty exportedProperty; - exportedProperty.fullName = qualifiedName; - exportedProperty.type = item->propertyDeclaration(it.key()).type(); - if (v->type() == Value::VariantValueType) { - exportedProperty.sourceCode = toJSLiteral( - std::static_pointer_cast<VariantValue>(v)->value()); - } else { - QBS_CHECK(v->type() == Value::JSSourceValueType); - const JSSourceValue * const sv = static_cast<JSSourceValue *>(v.get()); - exportedProperty.sourceCode = sv->sourceCode().toString(); - } - const ItemDeclaration itemDecl - = BuiltinDeclarations::instance().declarationsForType(item->type()); - PropertyDeclaration propertyDecl; - const auto itemProperties = itemDecl.properties(); - for (const PropertyDeclaration &decl : itemProperties) { - if (decl.name() == it.key()) { - propertyDecl = decl; - exportedProperty.isBuiltin = true; - break; - } - } - - // Do not add built-in properties that were left at their default value. - if (!exportedProperty.isBuiltin || m_evaluator->isNonDefaultValue(item, it.key())) - properties.push_back(exportedProperty); - } - - // Order the list of properties, so the output won't look so random. - static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool { - const int p1ComponentCount = p1.fullName.count(QLatin1Char('.')); - const int p2ComponentCount = p2.fullName.count(QLatin1Char('.')); - if (p1.isBuiltin && !p2.isBuiltin) - return true; - if (!p1.isBuiltin && p2.isBuiltin) - return false; - if (p1ComponentCount < p2ComponentCount) - return true; - if (p1ComponentCount > p2ComponentCount) - return false; - return p1.fullName < p2.fullName; - }; - std::sort(properties.begin(), properties.end(), less); -} - -static bool usesImport(const ExportedProperty &prop, const QRegExp ®ex) -{ - return regex.indexIn(prop.sourceCode) != -1; -} - -static bool usesImport(const ExportedItem &item, const QRegExp ®ex) -{ - return any_of(item.properties, - [regex](const ExportedProperty &p) { return usesImport(p, regex); }) - || any_of(item.children, - [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); -} - -static bool usesImport(const ExportedModule &module, const QString &name) -{ - // Imports are used in three ways: - // (1) var f = new TextFile(...); - // (2) var path = FileInfo.joinPaths(...) - // (3) var obj = DataCollection; - const QString pattern = QStringLiteral("\\b%1\\b"); - - const QRegExp regex(pattern.arg(name)); // std::regex is much slower - return any_of(module.m_properties, - [regex](const ExportedProperty &p) { return usesImport(p, regex); }) - || any_of(module.children, - [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); -} - -static QString getLineAtLocation(const CodeLocation &loc, const QString &content) -{ - int pos = 0; - int currentLine = 1; - while (currentLine < loc.line()) { - while (content.at(pos++) != QLatin1Char('\n')) - ; - ++currentLine; - } - const int eolPos = content.indexOf(QLatin1Char('\n'), pos); - return content.mid(pos, eolPos - pos); -} - -void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *) -{ - ExportedModule &exportedModule = m_productContext->product->exportedModule; - setupExportedProperties(exportItem, QString(), exportedModule.m_properties); - static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) { - return p1.fullName < p2.fullName; - }; - std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc); - for (const Item * const child : exportItem->children()) - exportedModule.children.push_back(resolveExportChild(child, exportedModule)); - for (const JsImport &jsImport : exportItem->file()->jsImports()) { - if (usesImport(exportedModule, jsImport.scopeName)) { - exportedModule.importStatements << getLineAtLocation(jsImport.location, - exportItem->file()->content()); - } - } - const auto builtInImports = JsExtensions::extensionNames(); - for (const QString &builtinImport: builtInImports) { - if (usesImport(exportedModule, builtinImport)) - exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport; - } - exportedModule.importStatements.sort(); -} - -// TODO: This probably wouldn't be necessary if we had item serialization. -std::unique_ptr<ExportedItem> ProjectResolver::resolveExportChild(const Item *item, - const ExportedModule &module) -{ - std::unique_ptr<ExportedItem> exportedItem(new ExportedItem); - - // This is the type of the built-in base item. It may turn out that we need to support - // derived items under Export. In that case, we probably need a new Item member holding - // the original type name. - exportedItem->name = item->typeName(); - - for (const Item * const child : item->children()) - exportedItem->children.push_back(resolveExportChild(child, module)); - setupExportedProperties(item, QString(), exportedItem->properties); - return exportedItem; -} - - -QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value, - const PropertyDeclaration &decl) const -{ - QString &scriptFunction = m_scriptFunctions[std::make_pair(value->sourceCode(), - decl.functionArgumentNames())]; - if (!scriptFunction.isNull()) - return scriptFunction; - const QString args = decl.functionArgumentNames().join(QLatin1Char(',')); - if (value->hasFunctionForm()) { - // Insert the argument list. - scriptFunction = value->sourceCodeForEvaluation(); - scriptFunction.insert(10, args); - // Remove the function application "()" that has been - // added in ItemReaderASTVisitor::visitStatement. - scriptFunction.chop(2); - } else { - scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ") - + value->sourceCode().toString() + QLatin1String(";})"); - } - return scriptFunction; -} - -QString ProjectResolver::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const -{ - QString &code = m_sourceCode[value->sourceCode()]; - if (!code.isNull()) - return code; - code = value->sourceCodeForEvaluation(); - return code; -} - -ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const -{ - JSSourceValuePtr value = item->sourceProperty(name); - ScriptFunctionPtr &script = m_scriptFunctionMap[value ? value->location() : CodeLocation()]; - if (!script.get()) { - script = ScriptFunction::create(); - const PropertyDeclaration decl = item->propertyDeclaration(name); - script->sourceCode = sourceCodeAsFunction(value, decl); - 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(*ctx); - return result; -} - -void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - - RulePtr rule = Rule::create(); - - // read artifacts - bool hasArtifactChildren = false; - for (Item * const child : item->children()) { - if (Q_UNLIKELY(child->type() != ItemType::Artifact)) { - throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), - child->location()); - } - hasArtifactChildren = true; - resolveRuleArtifact(rule, child); - } - - rule->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); - rule->prepareScript.initialize(scriptFunctionValue(item, StringConstants::prepareProperty())); - rule->outputArtifactsScript.initialize(scriptFunctionValue( - item, StringConstants::outputArtifactsProperty())); - rule->outputFileTags = m_evaluator->fileTagsValue( - item, StringConstants::outputFileTagsProperty()); - if (rule->outputArtifactsScript.isValid()) { - if (hasArtifactChildren) - throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " - "that contain Artifact items."), - item->location()); - } - if (!hasArtifactChildren && rule->outputFileTags.empty()) { - throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty " - "outputFileTags property."), item->location()); - } - rule->multiplex = m_evaluator->boolValue(item, StringConstants::multiplexProperty()); - rule->alwaysRun = m_evaluator->boolValue(item, StringConstants::alwaysRunProperty()); - rule->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); - rule->inputsFromDependencies - = m_evaluator->fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); - bool requiresInputsSet = false; - rule->requiresInputs = m_evaluator->boolValue(item, StringConstants::requiresInputsProperty(), - &requiresInputsSet); - if (!requiresInputsSet) - rule->requiresInputs = rule->declaresInputs(); - rule->auxiliaryInputs - = m_evaluator->fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); - rule->excludedInputs - = m_evaluator->fileTagsValue(item, StringConstants::excludedInputsProperty()); - if (rule->excludedInputs.empty()) { - rule->excludedInputs = m_evaluator->fileTagsValue( - item, StringConstants::excludedAuxiliaryInputsProperty()); - } - rule->explicitlyDependsOn - = m_evaluator->fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); - rule->explicitlyDependsOnFromDependencies = m_evaluator->fileTagsValue( - item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); - rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; - if (!rule->multiplex && !rule->declaresInputs()) { - throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."), - item->location()); - } - if (!rule->multiplex && !rule->requiresInputs) { - throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."), - item->location()); - } - if (!rule->declaresInputs() && rule->requiresInputs) { - throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule " - "does not declare any input tags."), item->location()); - } - if (m_productContext) { - rule->product = m_productContext->product.get(); - m_productContext->product->rules.push_back(rule); - } else { - projectContext->rules.push_back(rule); - } -} - -void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item) -{ - RuleArtifactPtr artifact = RuleArtifact::create(); - rule->artifacts.push_back(artifact); - artifact->location = item->location(); - - if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty())) - artifact->filePathLocation = sourceProperty->location(); - - artifact->filePath = verbatimValue(item, StringConstants::filePathProperty()); - artifact->fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); - artifact->alwaysUpdated = m_evaluator->boolValue(item, - StringConstants::alwaysUpdatedProperty()); - - QualifiedIdSet 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, - std::static_pointer_cast<ItemValue>(it.value())->item(), - QStringList(it.key()), &seenBindings); - } - } -} - -void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, - Item *item, - const QStringList &namePrefix, - QualifiedIdSet *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, - std::static_pointer_cast<ItemValue>(it.value())->item(), name, - seenBindings); - } else if (it.value()->type() == Value::JSSourceValueType) { - const auto insertResult = seenBindings->insert(name); - if (!insertResult.second) - continue; - JSSourceValuePtr sourceValue = std::static_pointer_cast<JSSourceValue>(it.value()); - RuleArtifact::Binding rab; - rab.name = name; - rab.code = sourceCodeForEvaluation(sourceValue); - rab.location = sourceValue->location(); - ruleArtifact->bindings.push_back(rab); - } else { - QBS_ASSERT(!"unexpected value type", continue); - } - } -} - -void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) -{ - checkCancelation(); - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - std::vector<FileTaggerConstPtr> &fileTaggers = m_productContext - ? m_productContext->product->fileTaggers - : projectContext->fileTaggers; - const QStringList patterns = m_evaluator->stringListValue(item, - StringConstants::patternsProperty()); - if (patterns.empty()) - throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); - - const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); - if (fileTags.empty()) - throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); - - for (const QString &pattern : patterns) { - if (pattern.isEmpty()) - throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); - } - - const int priority = m_evaluator->intValue(item, StringConstants::priorityProperty()); - fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); -} - -void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) - return; - const QString jobPool = m_evaluator->stringValue(item, StringConstants::jobPoolProperty()); - if (jobPool.isEmpty()) - throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.") - .arg(StringConstants::jobPoolProperty()), item->location()); - bool jobCountWasSet; - const int jobCount = m_evaluator->intValue(item, StringConstants::jobCountProperty(), -1, - &jobCountWasSet); - if (!jobCountWasSet) { - throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.") - .arg(StringConstants::jobCountProperty()), item->location()); - } - if (jobCount < 0) { - throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.") - .arg(StringConstants::jobCountProperty()), item->location()); - } - JobLimits &jobLimits = m_moduleContext - ? m_moduleContext->jobLimits - : m_productContext ? m_productContext->product->jobLimits - : projectContext->jobLimits; - JobLimit jobLimit(jobPool, jobCount); - const int oldLimit = jobLimits.getLimit(jobPool); - if (oldLimit == -1 || oldLimit > jobCount) - jobLimits.setJobLimit(jobLimit); -} - -void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext) -{ - checkCancelation(); - if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) { - qCDebug(lcProjectResolver) << "scanner condition is false"; - return; - } - - ResolvedScannerPtr scanner = ResolvedScanner::create(); - scanner->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; - scanner->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); - scanner->recursive = m_evaluator->boolValue(item, StringConstants::recursiveProperty()); - scanner->searchPathsScript.initialize(scriptFunctionValue( - item, StringConstants::searchPathsProperty())); - scanner->scanScript.initialize(scriptFunctionValue(item, StringConstants::scanProperty())); - m_productContext->product->scanners.push_back(scanner); -} - -ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies( - const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo) -{ - ProductDependencyInfos result; - result.dependencies.reserve(productInfo.usedProducts.size()); - for (const auto &dependency : productInfo.usedProducts) { - QBS_CHECK(!dependency.name.isEmpty()); - if (dependency.profile == StringConstants::star()) { - for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) { - if (p->name != dependency.name || p == product || !p->enabled - || (dependency.limitToSubProject && !product->isInParentProject(p))) { - continue; - } - result.dependencies.emplace_back(p, dependency.parameters); - } - } else { - ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName()); - const QString depDisplayName = ResolvedProduct::fullDisplayName(dependency.name, - dependency.multiplexConfigurationId); - if (!usedProduct) { - throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.") - .arg(product->fullDisplayName(), depDisplayName), - product->location); - } - if (!dependency.profile.isEmpty() && usedProduct->profile() != dependency.profile) { - usedProduct.reset(); - for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) { - if (p->name == dependency.name && p->profile() == dependency.profile) { - usedProduct = p; - break; - } - } - if (!usedProduct) { - throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist " - "for the requested profile '%3'.") - .arg(product->fullDisplayName(), depDisplayName, - dependency.profile), - product->location); - } - } - if (!usedProduct->enabled) { - if (!dependency.isRequired) - continue; - ErrorInfo e; - e.append(Tr::tr("Product '%1' depends on '%2',") - .arg(product->name, usedProduct->name), product->location); - e.append(Tr::tr("but product '%1' is disabled.").arg(usedProduct->name), - usedProduct->location); - if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) - throw e; - result.hasDisabledDependency = true; - } - result.dependencies.emplace_back(usedProduct, dependency.parameters); - } - } - return result; -} - -void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector<SourceArtifactPtr> &artifacts) -{ - for (const SourceArtifactPtr &artifact : artifacts) { - for (const ArtifactPropertiesConstPtr &artifactProperties : product->artifactProperties) { - if (!artifact->isTargetOfModule() - && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) { - artifact->properties = artifactProperties->propertyMap(); - } - } - } -} - -void ProjectResolver::printProfilingInfo() -{ - if (!m_setupParams.logElapsedTime()) - return; - m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("All property evaluation took %1.") - .arg(elapsedTimeString(m_elapsedTimeAllPropEval)); - m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Module property evaluation took %1.") - .arg(elapsedTimeString(m_elapsedTimeModPropEval)); - m_logger.qbsLog(LoggerInfo, true) << "\t" - << Tr::tr("Resolving groups (without module property " - "evaluation) took %1.") - .arg(elapsedTimeString(m_elapsedTimeGroups)); -} - -class TempScopeSetter -{ -public: - TempScopeSetter(Item * item, Item *newScope) : m_item(item), m_oldScope(item->scope()) - { - item->setScope(newScope); - } - ~TempScopeSetter() { m_item->setScope(m_oldScope); } -private: - Item * const m_item; - Item * const m_oldScope; -}; - -void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance) -{ - if (!productModuleInstance->isPresentModule()) - return; - Item * const exportItem = productModuleInstance->prototype(); - QBS_CHECK(exportItem && exportItem->type() == ItemType::Export); - TempScopeSetter tempScopeSetter(exportItem, productModuleInstance->scope()); - const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance() - .declarationsForType(ItemType::Export).properties(); - ExportedModule &exportedModule = m_productContext->product->exportedModule; - const auto &props = exportItem->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - const auto match - = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); }; - if (it.key() != StringConstants::prefixMappingProperty() && - std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) { - continue; - } - if (it.value()->type() == Value::ItemValueType) { - collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance, - exportedModule.modulePropertyValues); - } else { - evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues, - false); - } - } -} - -// Collects module properties assigned to in other (higher-level) modules. -void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module &module) -{ - if (!module.item->isPresentModule()) - return; - ExportedModule &exportedModule = m_productContext->product->exportedModule; - if (module.isProduct || module.name.first() == StringConstants::qbsModule()) - return; - const auto checkName = [module](const ExportedModuleDependency &d) { - return module.name.toString() == d.name; - }; - if (any_of(exportedModule.moduleDependencies, checkName)) - return; - - Item *modulePrototype = module.item->prototype(); - while (modulePrototype && modulePrototype->type() != ItemType::Module) - modulePrototype = modulePrototype->prototype(); - if (!modulePrototype) // Can happen for broken products in relaxed mode. - return; - TempScopeSetter tempScopeSetter(modulePrototype, module.item->scope()); - const Item::PropertyMap &props = modulePrototype->properties(); - ExportedModuleDependency dep; - dep.name = module.name.toString(); - for (auto it = props.begin(); it != props.end(); ++it) { - if (it.value()->type() == Value::ItemValueType) - collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties); - } - exportedModule.moduleDependencies.push_back(dep); - - for (const auto &dep : module.item->modules()) - collectPropertiesForModuleInExportItem(dep); -} - -static bool hasDependencyCycle(Set<ResolvedProduct *> *checked, - Set<ResolvedProduct *> *branch, - const ResolvedProductPtr &product, - ErrorInfo *error) -{ - if (branch->contains(product.get())) - return true; - if (checked->contains(product.get())) - return false; - checked->insert(product.get()); - branch->insert(product.get()); - for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { - if (hasDependencyCycle(checked, branch, dep, error)) { - error->prepend(dep->name, dep->location); - return true; - } - } - branch->remove(product.get()); - return false; -} - -using DependencyMap = QHash<ResolvedProduct *, Set<ResolvedProduct *>>; -void gatherDependencies(ResolvedProduct *product, DependencyMap &dependencies) -{ - if (dependencies.contains(product)) - return; - Set<ResolvedProduct *> &productDeps = dependencies[product]; - for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { - productDeps << dep.get(); - gatherDependencies(dep.get(), dependencies); - productDeps += dependencies.value(dep.get()); - } -} - - - -static DependencyMap allDependencies(const std::vector<ResolvedProductPtr> &products) -{ - DependencyMap dependencies; - for (const ResolvedProductPtr &product : products) - gatherDependencies(product.get(), dependencies); - return dependencies; -} - -void ProjectResolver::resolveProductDependencies(const ProjectContext &projectContext) -{ - // Resolve all inter-product dependencies. - const std::vector<ResolvedProductPtr> allProducts = projectContext.project->allProducts(); - bool disabledDependency = false; - for (const ResolvedProductPtr &rproduct : allProducts) { - if (!rproduct->enabled) - continue; - Item *productItem = m_productItemMap.value(rproduct); - const ModuleLoaderResult::ProductInfo &productInfo - = m_loadResult.productInfos.value(productItem); - const ProductDependencyInfos &depInfos = getProductDependencies(rproduct, productInfo); - if (depInfos.hasDisabledDependency) - disabledDependency = true; - for (const auto &dep : depInfos.dependencies) { - if (!contains(rproduct->dependencies, dep.product)) - rproduct->dependencies.push_back(dep.product); - if (!dep.parameters.empty()) - rproduct->dependencyParameters.insert(dep.product, dep.parameters); - } - } - - // Check for cyclic dependencies. - Set<ResolvedProduct *> checked; - for (const ResolvedProductPtr &rproduct : allProducts) { - Set<ResolvedProduct *> branch; - ErrorInfo error; - if (hasDependencyCycle(&checked, &branch, rproduct, &error)) { - error.prepend(rproduct->name, rproduct->location); - error.prepend(Tr::tr("Cyclic dependencies detected.")); - throw error; - } - } - - // Mark all products as disabled that have a disabled dependency. - if (disabledDependency && m_setupParams.productErrorMode() == ErrorHandlingMode::Relaxed) { - const DependencyMap allDeps = allDependencies(allProducts); - DependencyMap allDepsReversed; - for (auto it = allDeps.constBegin(); it != allDeps.constEnd(); ++it) { - for (ResolvedProduct *dep : qAsConst(it.value())) - allDepsReversed[dep] << it.key(); - } - for (auto it = allDepsReversed.constBegin(); it != allDepsReversed.constEnd(); ++it) { - if (it.key()->enabled) - continue; - for (ResolvedProduct * const dependingProduct : qAsConst(it.value())) { - if (dependingProduct->enabled) { - m_logger.qbsWarning() << Tr::tr("Disabling product '%1', because it depends on " - "disabled product '%2'.") - .arg(dependingProduct->name, it.key()->name); - dependingProduct->enabled = false; - } - } - } - } -} - -void ProjectResolver::postProcess(const ResolvedProductPtr &product, - ProjectContext *projectContext) const -{ - product->fileTaggers << projectContext->fileTaggers; - std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers), - [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) { - return a->priority() > b->priority(); - }); - for (const RulePtr &rule : projectContext->rules) { - RulePtr clonedRule = rule->clone(); - clonedRule->product = product.get(); - product->rules.push_back(clonedRule); - } -} - -void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const -{ - for (const SourceArtifactPtr &artifact : product->allEnabledFiles()) - applyFileTaggers(artifact, product); -} - -void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product) -{ - if (!artifact->overrideFileTags || artifact->fileTags.empty()) { - const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); - const FileTags fileTags = product->fileTagsForFileName(fileName); - artifact->fileTags.unite(fileTags); - if (artifact->fileTags.empty()) - artifact->fileTags.insert(unknownFileTag()); - qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags - << "to" << fileName; - } -} - -QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototype) -{ - AccumulatingTimer modPropEvalTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeModPropEval : nullptr); - QVariantMap moduleValues; - for (const Item::Module &module : item->modules()) { - if (!module.item->isPresentModule()) - continue; - const QString fullName = module.name.toString(); - moduleValues[fullName] = evaluateProperties(module.item, lookupPrototype, true); - } - - return moduleValues; -} - -QVariantMap ProjectResolver::evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors) -{ - const QVariantMap tmplt; - return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors); -} - -QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *propertiesContainer, - const QVariantMap &tmplt, bool lookupPrototype, bool checkErrors) -{ - AccumulatingTimer propEvalTimer(m_setupParams.logElapsedTime() - ? &m_elapsedTimeAllPropEval : nullptr); - QVariantMap result = tmplt; - for (QMap<QString, ValuePtr>::const_iterator it = propertiesContainer->properties().begin(); - it != propertiesContainer->properties().end(); ++it) { - checkCancelation(); - evaluateProperty(item, it.key(), it.value(), result, checkErrors); - } - return lookupPrototype && propertiesContainer->prototype() - ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors) - : result; -} - -void ProjectResolver::evaluateProperty(const Item *item, const QString &propName, - const ValuePtr &propValue, QVariantMap &result, bool checkErrors) -{ - switch (propValue->type()) { - case Value::ItemValueType: - { - // Ignore items. Those point to module instances - // and are handled in evaluateModuleValues(). - break; - } - case Value::JSSourceValueType: - { - if (result.contains(propName)) - break; - const PropertyDeclaration pd = item->propertyDeclaration(propName); - if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) { - break; - } - const QScriptValue scriptValue = m_evaluator->property(item, propName); - if (checkErrors && Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(scriptValue))) { - throw ErrorInfo(m_evaluator->engine()->lastError(scriptValue, - propValue->location())); - } - - // NOTE: Loses type information if scriptValue.isUndefined == true, - // as such QScriptValues become invalid QVariants. - QVariant v; - if (scriptValue.isFunction()) { - v = scriptValue.toString(); - } else { - v = scriptValue.toVariant(); - QVariantMap m = v.toMap(); - if (m.contains(StringConstants::importScopeNamePropertyInternal())) { - QVariantMap tmp = m; - m = scriptValue.prototype().toVariant().toMap(); - for (auto it = tmp.begin(); it != tmp.end(); ++it) - m.insert(it.key(), it.value()); - v = m; - } - } - - if (pd.type() == PropertyDeclaration::Path && v.isValid()) { - v = v.toString(); - } else if (pd.type() == PropertyDeclaration::PathList - || pd.type() == PropertyDeclaration::StringList) { - v = v.toStringList(); - } else if (pd.type() == PropertyDeclaration::VariantList) { - v = v.toList(); - } - result[propName] = v; - break; - } - case Value::VariantValueType: - { - if (result.contains(propName)) - break; - VariantValuePtr vvp = std::static_pointer_cast<VariantValue>(propValue); - QVariant v = vvp->value(); - - if (v.isNull() && !item->propertyDeclaration(propName).isScalar()) // QTBUG-51237 - v = QStringList(); - - result[propName] = v; - break; - } - } -} - -void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleName, - const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps) -{ - QBS_CHECK(value->type() == Value::ItemValueType); - Item * const itemValueItem = std::static_pointer_cast<ItemValue>(value)->item(); - if (itemValueItem->type() == ItemType::ModuleInstance) { - struct EvalPreparer { - EvalPreparer(Item *valueItem, Item *moduleInstance, const QualifiedId &moduleName) - : valueItem(valueItem), oldScope(valueItem->scope()), - hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) - { - valueItem->setScope(moduleInstance); - if (!hadName) { - // EvaluatorScriptClass expects a name here. - valueItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(moduleName.toString())); - } - } - ~EvalPreparer() - { - valueItem->setScope(oldScope); - if (!hadName) - valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr()); - } - Item * const valueItem; - Item * const oldScope; - const bool hadName; - }; - EvalPreparer ep(itemValueItem, moduleInstance, moduleName); - moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false)); - return; - } - QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix); - const Item::PropertyMap &props = itemValueItem->properties(); - for (auto it = props.begin(); it != props.end(); ++it) { - QualifiedId fullModuleName = moduleName; - fullModuleName << it.key(); - collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps); - } -} - -void ProjectResolver::createProductConfig(ResolvedProduct *product) -{ - EvalCacheEnabler cachingEnabler(m_evaluator); - m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory); - product->moduleProperties->setValue(evaluateModuleValues(m_productContext->item)); - product->productProperties = evaluateProperties(m_productContext->item, m_productContext->item, - QVariantMap(), true, true); - m_evaluator->clearPathPropertiesBaseDir(); -} - -void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, - ProjectContext *projectContext) -{ - const ItemFuncPtr f = mappings.value(item->type()); - QBS_CHECK(f); - if (item->type() == ItemType::Project) { - ProjectContext subProjectContext = createProjectContext(projectContext); - (this->*f)(item, &subProjectContext); - } else { - (this->*f)(item, projectContext); - } -} - -ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const -{ - ProjectContext subProjectContext; - subProjectContext.parentContext = parentProjectContext; - subProjectContext.project = ResolvedProject::create(); - parentProjectContext->project->subProjects.push_back(subProjectContext.project); - subProjectContext.project->parentProject = parentProjectContext->project; - return subProjectContext; -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h deleted file mode 100644 index 428ba144d..000000000 --- a/src/lib/corelib/language/projectresolver.h +++ /dev/null @@ -1,205 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qbs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PROJECTRESOLVER_H -#define PROJECTRESOLVER_H - -#include "filetags.h" -#include "itemtype.h" -#include "moduleloader.h" -#include "qualifiedid.h" - -#include <logging/logger.h> -#include <tools/set.h> - -#include <QtCore/qhash.h> -#include <QtCore/qmap.h> -#include <QtCore/qstringlist.h> - -#include <utility> -#include <vector> - -namespace qbs { -class JobLimits; -namespace Internal { - -class Evaluator; -class Item; -class ProgressObserver; -class ScriptEngine; - -class ProjectResolver -{ -public: - ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult, - SetupProjectParameters setupParameters, Logger &logger); - ~ProjectResolver(); - - void setProgressObserver(ProgressObserver *observer); - TopLevelProjectPtr resolve(); - - static void applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product); - - using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>; - static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct, - const QString &fileName, const GroupPtr &group, bool wildcard, - const CodeLocation &filesLocation = CodeLocation(), - FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr); - -private: - struct ProjectContext; - struct ProductContext; - struct ModuleContext; - class ProductContextSwitcher; - - void checkCancelation() const; - QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const; - QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const; - ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; - ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; - void ignoreItem(Item *item, ProjectContext *projectContext); - TopLevelProjectPtr resolveTopLevelProject(); - void resolveProject(Item *item, ProjectContext *projectContext); - void resolveProjectFully(Item *item, ProjectContext *projectContext); - void resolveSubProject(Item *item, ProjectContext *projectContext); - void resolveProduct(Item *item, ProjectContext *projectContext); - void resolveProductFully(Item *item, ProjectContext *projectContext); - void resolveModules(const Item *item, ProjectContext *projectContext); - void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, - const QVariantMap ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext); - void gatherProductTypes(ResolvedProduct *product, Item *item); - QVariantMap resolveAdditionalModuleProperties(const Item *group, - const QVariantMap ¤tValues); - void resolveGroup(Item *item, ProjectContext *projectContext); - void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled); - void resolveShadowProduct(Item *item, ProjectContext *); - void resolveExport(Item *exportItem, ProjectContext *); - std::unique_ptr<ExportedItem> resolveExportChild(const Item *item, - const ExportedModule &module); - void resolveRule(Item *item, ProjectContext *projectContext); - void resolveRuleArtifact(const RulePtr &rule, Item *item); - void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, - const QStringList &namePrefix, - QualifiedIdSet *seenBindings); - void resolveFileTagger(Item *item, ProjectContext *projectContext); - void resolveJobLimit(Item *item, ProjectContext *projectContext); - void resolveScanner(Item *item, ProjectContext *projectContext); - void resolveProductDependencies(const ProjectContext &projectContext); - void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; - void applyFileTaggers(const ResolvedProductPtr &product) const; - QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true); - QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); - QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, - const QVariantMap &tmplt, bool lookupPrototype, - bool checkErrors); - void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, - QVariantMap &result, bool checkErrors); - void createProductConfig(ResolvedProduct *product); - ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; - void adaptExportedPropertyValues(const Item *shadowProductItem); - void collectExportedProductDependencies(); - - struct ProductDependencyInfo - { - ProductDependencyInfo(const ResolvedProductPtr &product, - const QVariantMap ¶meters = QVariantMap()) - : product(product), parameters(parameters) - { - } - - ResolvedProductPtr product; - QVariantMap parameters; - }; - - struct ProductDependencyInfos - { - std::vector<ProductDependencyInfo> dependencies; - bool hasDisabledDependency = false; - }; - - ProductDependencyInfos getProductDependencies(const ResolvedProductConstPtr &product, - const ModuleLoaderResult::ProductInfo &productInfo); - QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, - const PropertyDeclaration &decl) const; - QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const; - static void matchArtifactProperties(const ResolvedProductPtr &product, - const std::vector<SourceArtifactPtr> &artifacts); - void printProfilingInfo(); - - void collectPropertiesForExportItem(Item *productModuleInstance); - void collectPropertiesForModuleInExportItem(const Item::Module &module); - - void collectPropertiesForExportItem(const QualifiedId &moduleName, - const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps); - void setupExportedProperties(const Item *item, const QString &namePrefix, - std::vector<ExportedProperty> &properties); - - Evaluator *m_evaluator = nullptr; - Logger &m_logger; - ScriptEngine *m_engine = nullptr; - ProgressObserver *m_progressObserver = nullptr; - ProductContext *m_productContext = nullptr; - ModuleContext *m_moduleContext = nullptr; - QMap<QString, ResolvedProductPtr> m_productsByName; - QHash<FileTag, QList<ResolvedProductPtr> > m_productsByType; - QHash<ResolvedProductPtr, Item *> m_productItemMap; - mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap; - mutable QHash<CodeLocation, ScriptFunctionPtr> m_scriptFunctionMap; - mutable QHash<std::pair<QStringRef, QStringList>, QString> m_scriptFunctions; - mutable QHash<QStringRef, QString> m_sourceCode; - const SetupProjectParameters m_setupParams; - ModuleLoaderResult m_loadResult; - Set<CodeLocation> m_groupLocationWarnings; - std::vector<std::pair<ResolvedProductPtr, Item *>> m_productExportInfo; - std::vector<ErrorInfo> m_queuedErrors; - qint64 m_elapsedTimeModPropEval = 0; - qint64 m_elapsedTimeAllPropEval = 0; - qint64 m_elapsedTimeGroups = 0; - - typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); - using ItemFuncMap = QMap<ItemType, ItemFuncPtr>; - 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 index 204704672..302973d19 100644 --- a/src/lib/corelib/language/property.h +++ b/src/lib/corelib/language/property.h @@ -65,9 +65,13 @@ public: { } - Property(const QString &product, const QString &module, const QString &property, - const QVariant &v, Kind k) - : productName(product), moduleName(module), propertyName(property), value(v), kind(k) + Property(QString product, QString module, QString property, + QVariant v, Kind k) + : productName(std::move(product)) + , moduleName(std::move(module)) + , propertyName(std::move(property)) + , value(std::move(v)) + , kind(k) { } @@ -90,7 +94,7 @@ inline bool operator==(const Property &p1, const Property &p2) } bool operator<(const Property &p1, const Property &p2); -inline uint qHash(const Property &p) +inline auto qHash(const Property &p) { return QT_PREPEND_NAMESPACE(qHash)(p.productName + p.moduleName + p.propertyName); } diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp index abe6a1626..d56ab3bb0 100644 --- a/src/lib/corelib/language/propertydeclaration.cpp +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -40,16 +40,53 @@ #include "propertydeclaration.h" #include "deprecationinfo.h" -#include <api/languageinfo.h> +#include "filecontext.h" +#include "item.h" +#include "qualifiedid.h" +#include "value.h" +#include <api/languageinfo.h> +#include <loader/loaderutils.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/setupprojectparameters.h> +#include <tools/qttools.h> #include <tools/stringconstants.h> +#include <QtCore/qmetatype.h> #include <QtCore/qshareddata.h> #include <QtCore/qstringlist.h> +#include <QtCore/qvariant.h> namespace qbs { namespace Internal { +// returns QMetaType::UnknownType for types that do not need conversion +static QMetaType::Type variantType(PropertyDeclaration::Type t) +{ + switch (t) { + case PropertyDeclaration::UnknownType: + break; + case PropertyDeclaration::Boolean: + return QMetaType::Bool; + case PropertyDeclaration::Integer: + return QMetaType::Int; + case PropertyDeclaration::Path: + return QMetaType::QString; + case PropertyDeclaration::PathList: + return QMetaType::QStringList; + case PropertyDeclaration::String: + return QMetaType::QString; + case PropertyDeclaration::StringList: + return QMetaType::QStringList; + case PropertyDeclaration::VariantList: + return QMetaType::QVariantList; + case PropertyDeclaration::Variant: + break; + } + return QMetaType::UnknownType; +} + class PropertyDeclarationData : public QSharedData { public: @@ -62,13 +99,13 @@ public: QString name; PropertyDeclaration::Type type; PropertyDeclaration::Flags flags; + QStringList allowedValues; QString description; QString initialValueSource; QStringList functionArgumentNames; DeprecationInfo deprecationInfo; }; - PropertyDeclaration::PropertyDeclaration() : d(new PropertyDeclarationData) { @@ -182,6 +219,16 @@ void PropertyDeclaration::setFlags(Flags f) d->flags = f; } +const QStringList &PropertyDeclaration::allowedValues() const +{ + return d->allowedValues; +} + +void PropertyDeclaration::setAllowedValues(const QStringList &v) +{ + d->allowedValues = v; +} + const QString &PropertyDeclaration::description() const { return d->description; @@ -232,5 +279,236 @@ void PropertyDeclaration::setDeprecationInfo(const DeprecationInfo &deprecationI d->deprecationInfo = deprecationInfo; } +ErrorInfo PropertyDeclaration::checkForDeprecation(DeprecationWarningMode mode, + const CodeLocation &loc, Logger &logger) const +{ + return deprecationInfo().checkForDeprecation(mode, name(), loc, false, logger); +} + +QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t, + const QStringList &namePrefix, const QString &key) +{ + if (v.isNull() || !v.isValid()) + return v; + const auto vt = variantType(t); + if (vt == QMetaType::UnknownType) + return v; + + // Handle the foo,bar,bla stringlist syntax. + if (t == PropertyDeclaration::StringList && v.userType() == QMetaType::QString) + return v.toString().split(QLatin1Char(',')); + + QVariant c = v; + if (!qVariantConvert(c, vt)) { + QStringList name = namePrefix; + name << key; + throw ErrorInfo(Tr::tr("Value '%1' of property '%2' has incompatible type.") + .arg(v.toString(), name.join(QLatin1Char('.')))); + } + return c; +} + +QVariant PropertyDeclaration::typedNullValue() const +{ + switch (type()) { + case PropertyDeclaration::Boolean: + return typedNullVariant<bool>(); + case PropertyDeclaration::Integer: + return typedNullVariant<int>(); + case PropertyDeclaration::VariantList: + return typedNullVariant<QVariantList>(); + case PropertyDeclaration::String: + case PropertyDeclaration::Path: + return typedNullVariant<QString>(); + case PropertyDeclaration::StringList: + case PropertyDeclaration::PathList: + return typedNullVariant<QStringList>(); + default: + return {}; + } +} + +bool PropertyDeclaration::shouldCheckAllowedValues() const +{ + return isValid() + && (d->type == PropertyDeclaration::String || d->type == PropertyDeclaration::StringList) + && !d->allowedValues.empty(); +} + +void PropertyDeclaration::checkAllowedValues( + const QVariant &value, + const CodeLocation &loc, + const QString &key, + LoaderState &loaderState) const +{ + const auto type = d->type; + if (!shouldCheckAllowedValues()) + return; + + if (value.isNull()) + return; + + const auto &allowedValues = d->allowedValues; + + const auto checkValue = [&loc, &allowedValues, &key, &loaderState](const QString &value) + { + if (!allowedValues.contains(value)) { + const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.") + .arg(value, key); + ErrorInfo error(message, loc); + handlePropertyError(error, loaderState.parameters(), loaderState.logger()); + } + }; + + if (type == PropertyDeclaration::StringList) { + const auto strings = value.toStringList(); + for (const auto &string: strings) { + checkValue(string); + } + } else if (type == PropertyDeclaration::String) { + checkValue(value.toString()); + } +} + +namespace { +class PropertyDeclarationCheck : public ValueHandler +{ +public: + PropertyDeclarationCheck(LoaderState &loaderState) : m_loaderState(loaderState) {} + void operator()(Item *item) + { + m_checkingProject = item->type() == ItemType::Project; + handleItem(item); + } + +private: + void handle(JSSourceValue *value) override + { + if (!value->createdByPropertiesBlock()) { + const ErrorInfo error(Tr::tr("Property '%1' is not declared.") + .arg(m_currentName), value->location()); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); + } + } + void handle(ItemValue *value) override + { + if (checkItemValue(value)) + handleItem(value->item()); + } + bool checkItemValue(ItemValue *value) + { + // TODO: Remove once QBS-1030 is fixed. + if (parentItem()->type() == ItemType::Artifact) + return false; + + if (parentItem()->type() == ItemType::Properties) + return false; + + // TODO: Check where the in-between module instances come from. + if (value->item()->type() == ItemType::ModuleInstancePlaceholder) { + for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) { + if ((*it)->type() == ItemType::Group) + return false; + if ((*it)->type() == ItemType::ModulePrefix) + continue; + break; + } + } + + if (value->item()->type() != ItemType::ModuleInstance + && value->item()->type() != ItemType::ModulePrefix + && (!parentItem()->file() || !parentItem()->file()->idScope() + || !parentItem()->file()->idScope()->hasProperty(m_currentName)) + && !value->createdByPropertiesBlock()) { + CodeLocation location = value->location(); + for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i) + location = m_parentItems.at(i)->location(); + const ErrorInfo error(Tr::tr("Item '%1' is not declared. " + "Did you forget to add a Depends item?") + .arg(m_currentModuleName.toString()), location); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); + return false; + } + + return true; + } + void handleItem(Item *item) + { + if (m_checkingProject && item->type() == ItemType::Product) + return; + if (!m_handledItems.insert(item).second) + return; + if (item->type() == ItemType::Module + || item->type() == ItemType::Export + || (item->type() == ItemType::ModuleInstance && !item->isPresentModule()) + || item->type() == ItemType::Properties + + // The Properties child of a SubProject item is not a regular item. + || item->type() == ItemType::PropertiesInSubProject + + || m_loaderState.topLevelProject().isDisabledItem(item)) { + return; + } + + m_parentItems.push_back(item); + for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) { + if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() + && it.value()->type() == Value::ItemValueType) + continue; + const PropertyDeclaration decl = item->propertyDeclaration(it.key()); + if (decl.isValid()) { + const ErrorInfo deprecationError = decl.checkForDeprecation( + m_loaderState.parameters().deprecationWarningMode(), it.value()->location(), + m_loaderState.logger()); + if (deprecationError.hasError()) { + handlePropertyError(deprecationError, m_loaderState.parameters(), + m_loaderState.logger()); + } + continue; + } + m_currentName = it.key(); + const QualifiedId oldModuleName = m_currentModuleName; + if (parentItem()->type() != ItemType::ModulePrefix) + m_currentModuleName.clear(); + m_currentModuleName.push_back(m_currentName); + it.value()->apply(this); + m_currentModuleName = oldModuleName; + } + m_parentItems.pop_back(); + for (Item * const child : item->children()) { + switch (child->type()) { + case ItemType::Export: + case ItemType::Depends: + case ItemType::Parameter: + case ItemType::Parameters: + break; + case ItemType::Group: + if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance) + break; + Q_FALLTHROUGH(); + default: + handleItem(child); + } + } + } + void handle(VariantValue *) override { /* only created internally - no need to check */ } + + Item *parentItem() const { return m_parentItems.back(); } + + LoaderState &m_loaderState; + Set<Item *> m_handledItems; + std::vector<Item *> m_parentItems; + QualifiedId m_currentModuleName; + QString m_currentName; + bool m_checkingProject = false; +}; +} // namespace + +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState) +{ + (PropertyDeclarationCheck(loaderState))(topLevelItem); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h index 874275bde..79a39ecbd 100644 --- a/src/lib/corelib/language/propertydeclaration.h +++ b/src/lib/corelib/language/propertydeclaration.h @@ -40,17 +40,24 @@ #ifndef QBS_PROPERTYDECLARATION_H #define QBS_PROPERTYDECLARATION_H +#include <tools/deprecationwarningmode.h> + #include <QtCore/qshareddata.h> #include <QtCore/qstring.h> QT_BEGIN_NAMESPACE -class QStringList; +class QVariant; QT_END_NAMESPACE namespace qbs { +class CodeLocation; +class ErrorInfo; namespace Internal { class DeprecationInfo; class PropertyDeclarationData; +class Item; +class LoaderState; +class Logger; class PropertyDeclaration { @@ -100,6 +107,9 @@ public: Flags flags() const; void setFlags(Flags f); + const QStringList &allowedValues() const; + void setAllowedValues(const QStringList &v); + const QString &description() const; void setDescription(const QString &str); @@ -113,11 +123,27 @@ public: bool isExpired() const; const DeprecationInfo &deprecationInfo() const; void setDeprecationInfo(const DeprecationInfo &deprecationInfo); + ErrorInfo checkForDeprecation(DeprecationWarningMode mode, const CodeLocation &loc, + Logger &logger) const; + + static QVariant convertToPropertyType( + const QVariant &v, Type t, const QStringList &namePrefix, const QString &key); + QVariant typedNullValue() const; + + bool shouldCheckAllowedValues() const; + void checkAllowedValues( + const QVariant &value, + const CodeLocation &loc, + const QString &key, + LoaderState &loaderState) const; private: QSharedDataPointer<PropertyDeclarationData> d; }; +void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState); + + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h index 83e18ba48..af551cf6f 100644 --- a/src/lib/corelib/language/propertymapinternal.h +++ b/src/lib/corelib/language/propertymapinternal.h @@ -77,7 +77,7 @@ private: inline bool operator==(const PropertyMapInternal &lhs, const PropertyMapInternal &rhs) { - return lhs.m_value == rhs.m_value; + return qVariantsEqual(lhs.m_value, rhs.m_value); } QVariant QBS_AUTOTEST_EXPORT moduleProperty(const QVariantMap &properties, diff --git a/src/lib/corelib/language/qualifiedid.cpp b/src/lib/corelib/language/qualifiedid.cpp index 9eb0e9463..87248ac21 100644 --- a/src/lib/corelib/language/qualifiedid.cpp +++ b/src/lib/corelib/language/qualifiedid.cpp @@ -58,7 +58,7 @@ QualifiedId::QualifiedId(const QStringList &nameParts) QualifiedId QualifiedId::fromString(const QString &str) { - return QualifiedId(str.split(QLatin1Char('.'))); + return {str.split(QLatin1Char('.'))}; } QString QualifiedId::toString() const diff --git a/src/lib/corelib/language/qualifiedid.h b/src/lib/corelib/language/qualifiedid.h index 2b4c9d286..7e8b388d3 100644 --- a/src/lib/corelib/language/qualifiedid.h +++ b/src/lib/corelib/language/qualifiedid.h @@ -60,7 +60,7 @@ public: QString toString() const; }; -inline uint qHash(const QualifiedId &qid) { return qHash(qid.toString()); } +inline auto qHash(const QualifiedId &qid) { return qHash(qid.toString()); } using QualifiedIdSet = Set<QualifiedId>; diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp index 7c531e764..998384547 100644 --- a/src/lib/corelib/language/scriptengine.cpp +++ b/src/lib/corelib/language/scriptengine.cpp @@ -46,16 +46,19 @@ #include "preparescriptobserver.h" #include <buildgraph/artifact.h> +#include <buildgraph/rulenode.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/error.h> #include <tools/fileinfo.h> #include <tools/profiling.h> #include <tools/qbsassert.h> +#include <tools/scripttools.h> #include <tools/qttools.h> #include <tools/stlutils.h> #include <tools/stringconstants.h> +#include <QtCore/qdatetime.h> #include <QtCore/qdebug.h> #include <QtCore/qdiriterator.h> #include <QtCore/qfile.h> @@ -63,18 +66,15 @@ #include <QtCore/qtextstream.h> #include <QtCore/qtimer.h> -#include <QtScript/qscriptclass.h> -#include <QtScript/qscriptvalueiterator.h> - +#include <cstring> #include <functional> #include <set> #include <utility> +#include <vector> namespace qbs { namespace Internal { -static QString getterFuncHelperProperty() { return QStringLiteral("qbsdata"); } - const bool debugJSImports = false; bool operator==(const ScriptEngine::PropertyCacheKey &lhs, @@ -85,133 +85,222 @@ bool operator==(const ScriptEngine::PropertyCacheKey &lhs, && lhs.m_propertyName == rhs.m_propertyName; } -static inline uint combineHash(uint h1, uint h2, uint seed) +static QHashValueType combineHash(QHashValueType h1, QHashValueType h2, QHashValueType seed) { // stolen from qHash(QPair) return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed; } -uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed = 0) +QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed = 0) { return combineHash(qHash(k.m_moduleName), combineHash(qHash(k.m_propertyName), qHash(k.m_propertyMap), seed), seed); } -std::mutex ScriptEngine::m_creationDestructionMutex; - -ScriptEngine::ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent) - : QScriptEngine(parent), m_scriptImporter(new ScriptImporter(this)), - m_modulePropertyScriptClass(nullptr), - m_propertyCacheEnabled(true), m_active(false), m_logger(logger), m_evalContext(evalContext), +ScriptEngine::ScriptEngine(Logger &logger, EvalContext evalContext, PrivateTag) + : m_scriptImporter(new ScriptImporter(this)), + m_logger(logger), m_evalContext(evalContext), m_observer(new PrepareScriptObserver(this, UnobserveMode::Disabled)) { - setProcessEventsInterval(1000); // For the cancelation mechanism to work. - m_cancelationError = currentContext()->throwValue(tr("Execution canceled")); - QScriptValue objectProto = globalObject().property(QStringLiteral("Object")); - m_definePropertyFunction = objectProto.property(QStringLiteral("defineProperty")); - QBS_ASSERT(m_definePropertyFunction.isFunction(), /* ignore */); - m_emptyFunction = evaluate(QStringLiteral("(function(){})")); - QBS_ASSERT(m_emptyFunction.isFunction(), /* ignore */); - // Initially push a new context to turn off scope chain insanity mode. - QScriptEngine::pushContext(); + setMaxStackSize(); + JS_SetRuntimeOpaque(m_jsRuntime, this); + JS_SetInterruptHandler(m_jsRuntime, interruptor, this); + setScopeLookup(m_context, &ScriptEngine::doExtraScopeLookup); + setFoundUndefinedHandler(m_context, &ScriptEngine::handleUndefinedFound); + setFunctionEnteredHandler(m_context, &ScriptEngine::handleFunctionEntered); + setFunctionExitedHandler(m_context, &ScriptEngine::handleFunctionExited); + m_dataWithPtrClass = registerClass("__data", nullptr, nullptr, JS_UNDEFINED); installQbsBuiltins(); extendJavaScriptBuiltins(); } -ScriptEngine *ScriptEngine::create(Logger &logger, EvalContext evalContext, QObject *parent) +std::unique_ptr<ScriptEngine> ScriptEngine::create(Logger &logger, EvalContext evalContext) { - std::lock_guard<std::mutex> lock(m_creationDestructionMutex); - return new ScriptEngine(logger, evalContext, parent); + return std::make_unique<ScriptEngine>(logger, evalContext, PrivateTag()); } -ScriptEngine::~ScriptEngine() +ScriptEngine *ScriptEngine::engineForRuntime(const JSRuntime *runtime) +{ + return static_cast<ScriptEngine *>(JS_GetRuntimeOpaque(const_cast<JSRuntime *>(runtime))); + +} + +ScriptEngine *ScriptEngine::engineForContext(const JSContext *ctx) +{ + return engineForRuntime(JS_GetRuntime(const_cast<JSContext *>(ctx))); +} + +LookupResult ScriptEngine::doExtraScopeLookup(JSContext *ctx, JSAtom prop) +{ + static const LookupResult fail{JS_UNDEFINED, JS_UNDEFINED, false}; + + ScriptEngine * const engine = engineForContext(ctx); + engine->m_lastLookupWasSuccess = false; + + JSValueList scopes; + if (!engine->m_scopeChains.isEmpty()) + scopes = engine->m_scopeChains.last(); + if (JS_IsObject(engine->m_globalObject)) + scopes.insert(scopes.begin(), engine->m_globalObject); + for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) { + const JSValue v = JS_GetProperty(ctx, *it, prop); + if (!JS_IsUndefined(v) || engine->m_lastLookupWasSuccess) { + engine->m_lastLookupWasSuccess = false; + return {v, *it, true}; + } + } + return fail; +} + +void ScriptEngine::handleUndefinedFound(JSContext *ctx) +{ + engineForContext(ctx)->setLastLookupStatus(true); +} + +void ScriptEngine::handleFunctionEntered(JSContext *ctx, JSValue this_obj) +{ + ScriptEngine::engineForContext(ctx)->m_contextStack.push_back(this_obj); +} + +void ScriptEngine::handleFunctionExited(JSContext *ctx) { - m_creationDestructionMutex.lock(); - connect(this, &QObject::destroyed, std::bind(&std::mutex::unlock, &m_creationDestructionMutex)); + ScriptEngine::engineForContext(ctx)->m_contextStack.pop_back(); +} - releaseResourcesOfScriptObjects(); - delete (m_scriptImporter); +ScriptEngine::~ScriptEngine() +{ + reset(); + delete m_scriptImporter; if (m_elapsedTimeImporting != -1) { m_logger.qbsLog(LoggerInfo, true) << Tr::tr("Setting up imports took %1.") .arg(elapsedTimeString(m_elapsedTimeImporting)); } - delete m_modulePropertyScriptClass; - delete m_productPropertyScriptClass; + for (const auto &ext : std::as_const(m_internalExtensions)) + JS_FreeValue(m_context, ext); + for (const JSValue &s : std::as_const(m_stringCache)) + JS_FreeValue(m_context, s); + for (JSValue * const externalRef : std::as_const(m_externallyCachedValues)) { + JS_FreeValue(m_context, *externalRef); + *externalRef = JS_UNDEFINED; + } + setPropertyOnGlobalObject(QLatin1String("console"), JS_UNDEFINED); + JS_FreeContext(m_context); + JS_FreeRuntime(m_jsRuntime); } -void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, - ObserveMode observeMode) +void ScriptEngine::reset() { - installImportFunctions(); - m_currentDirPathStack.push(FileInfo::path(fileCtx->filePath())); - m_extensionSearchPathsStack.push(fileCtx->searchPaths()); - m_observeMode = observeMode; - - for (const JsImport &jsImport : fileCtx->jsImports()) - import(jsImport, targetObject); - if (m_observeMode == ObserveMode::Enabled) { - for (QScriptValue &sv : m_requireResults) - observeImport(sv); - m_requireResults.clear(); + // TODO: Check whether we can keep file and imports cache. + // We'd have to find a solution for the scope name problem then. + clearImportsCache(); + for (const auto &e : std::as_const(m_jsFileCache)) + JS_FreeValue(m_context, e.second); + m_jsFileCache.clear(); + + for (const JSValue &s : std::as_const(m_jsValueCache)) + JS_FreeValue(m_context, s); + m_jsValueCache.clear(); + + for (auto it = m_evalResults.cbegin(); it != m_evalResults.cend(); ++it) { + for (int i = 0; i < it.value(); ++i) + JS_FreeValue(m_context, it.key()); + } + m_evalResults.clear(); + for (const auto &e : std::as_const(m_projectScriptValues)) + JS_FreeValue(m_context, e.second); + m_projectScriptValues.clear(); + for (const auto &e : std::as_const(m_baseProductScriptValues)) + JS_FreeValue(m_context, e.second); + m_baseProductScriptValues.clear(); + for (const auto &e : std::as_const(m_productArtifactsMapScriptValues)) + JS_FreeValue(m_context, e.second); + m_productArtifactsMapScriptValues.clear(); + for (const auto &e : std::as_const(m_moduleArtifactsMapScriptValues)) + JS_FreeValue(m_context, e.second); + m_moduleArtifactsMapScriptValues.clear(); + for (const auto &e : std::as_const(m_baseModuleScriptValues)) + JS_FreeValue(m_context, e.second); + m_baseModuleScriptValues.clear(); + for (auto it = m_artifactsScriptValues.cbegin(); it != m_artifactsScriptValues.cend(); ++it) { + it.key().first->setDeregister({}); + JS_FreeValue(m_context, it.value()); } + m_artifactsScriptValues.clear(); +} - m_currentDirPathStack.pop(); - m_extensionSearchPathsStack.pop(); - uninstallImportFunctions(); +void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, + ObserveMode observeMode) +{ + Importer(*this, fileCtx, targetObject, observeMode).run(); } -void ScriptEngine::import(const JsImport &jsImport, QScriptValue &targetObject) +void ScriptEngine::import(const JsImport &jsImport, JSValue &targetObject) { - QBS_ASSERT(targetObject.isObject(), return); - QBS_ASSERT(targetObject.engine() == this, return); + QBS_ASSERT(JS_IsObject(targetObject), return); if (debugJSImports) qDebug() << "[ENGINE] import into " << jsImport.scopeName; - QScriptValue jsImportValue = m_jsImportCache.value(jsImport); - if (jsImportValue.isValid()) { + JSValue jsImportValue = m_jsImportCache.value(jsImport); + if (JS_IsObject(jsImportValue)) { if (debugJSImports) qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache hit)"; } else { if (debugJSImports) qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache miss)"; - jsImportValue = newObject(); + + ScopedJsValue scopedImportValue(m_context, JS_NewObject(m_context)); for (const QString &filePath : jsImport.filePaths) - importFile(filePath, jsImportValue); + importFile(filePath, scopedImportValue); + jsImportValue = scopedImportValue.release(); m_jsImportCache.insert(jsImport, jsImportValue); std::vector<QString> &filePathsForScriptValue - = m_filePathsPerImport[jsImportValue.objectId()]; - for (const QString &fp : jsImport.filePaths) - filePathsForScriptValue.push_back(fp); + = m_filePathsPerImport[jsObjectId(jsImportValue)]; + transform(jsImport.filePaths, filePathsForScriptValue, [](const auto &fp) { + return fp; }); } - QScriptValue sv = newObject(); - sv.setPrototype(jsImportValue); - sv.setProperty(StringConstants::importScopeNamePropertyInternal(), jsImport.scopeName); - targetObject.setProperty(jsImport.scopeName, sv); + JSValue sv = JS_NewObjectProto(m_context, jsImportValue); + setJsProperty(m_context, sv, StringConstants::importScopeNamePropertyInternal(), + jsImport.scopeName); + setJsProperty(m_context, targetObject, jsImport.scopeName, sv); if (m_observeMode == ObserveMode::Enabled) observeImport(jsImportValue); } -void ScriptEngine::observeImport(QScriptValue &jsImport) +void ScriptEngine::observeImport(JSValue &jsImport) { - if (!m_observer->addImportId(jsImport.objectId())) + if (!m_observer->addImportId(quintptr((JS_VALUE_GET_OBJ(jsImport))))) return; - QScriptValueIterator it(jsImport); - while (it.hasNext()) { - it.next(); - if (it.flags() & QScriptValue::PropertyGetter) - continue; - QScriptValue property = it.value(); - if (!property.isFunction()) - continue; - setObservedProperty(jsImport, it.name(), property); - } + handleJsProperties(jsImport, [this, &jsImport](const JSAtom &name, + const JSPropertyDescriptor &desc) { + if (!JS_IsFunction(m_context, desc.value)) + return; + const char *const nameStr = JS_AtomToCString(m_context, name); + setObservedProperty(jsImport, QString::fromUtf8(nameStr, std::strlen(nameStr)), desc.value); + JS_FreeCString(m_context, nameStr); + }); } void ScriptEngine::clearImportsCache() { + for (const auto &jsImport : std::as_const(m_jsImportCache)) + JS_FreeValue(m_context, jsImport); m_jsImportCache.clear(); + m_filePathsPerImport.clear(); + m_observer->clearImportIds(); +} + +void ScriptEngine::registerEvaluator(Evaluator *evaluator) +{ + QBS_ASSERT(!m_evaluator, return); + m_evaluator = evaluator; +} + +void ScriptEngine::unregisterEvaluator(const Evaluator *evaluator) +{ + QBS_ASSERT(m_evaluator == evaluator, return); + m_evaluator = nullptr; } void ScriptEngine::checkContext(const QString &operation, @@ -240,7 +329,15 @@ void ScriptEngine::checkContext(const QString &operation, QBS_ASSERT(false, continue); break; } - m_logger.printWarning(ErrorInfo(warning, currentContext()->backtrace())); + if (!m_evalPositions.empty()) { + const JSValue exVal = JS_NewObject(m_context); + const auto &[file, line] = m_evalPositions.top(); + build_backtrace(m_context, exVal, file.toUtf8().constData(), line, 0); + const JsException ex(m_context, exVal, {}); + m_logger.printWarning(ErrorInfo(warning, ex.stackTrace())); + } else { + m_logger.printWarning(ErrorInfo(warning)); + } return; } } @@ -251,7 +348,7 @@ void ScriptEngine::addPropertyRequestedFromArtifact(const Artifact *artifact, m_propertiesRequestedFromArtifact[artifact->filePath()] << property; } -void ScriptEngine::addImportRequestedInScript(qint64 importValueId) +void ScriptEngine::addImportRequestedInScript(quintptr importValueId) { // Import list is assumed to be small, so let's not use a set. if (!contains(m_importsRequestedInScript, importValueId)) @@ -289,37 +386,21 @@ QVariant ScriptEngine::retrieveFromPropertyCache(const QString &moduleName, return m_propertyCache.value(PropertyCacheKey(moduleName, propertyName, propertyMap)); } -void ScriptEngine::defineProperty(QScriptValue &object, const QString &name, - const QScriptValue &descriptor) +static JSValue js_observedGet(JSContext *ctx, JSValueConst, int, JSValueConst *, int, JSValue *data) { - 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()); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + engine->observer()->onPropertyRead(data[0], getJsString(ctx, data[1]), data[2]); + return JS_DupValue(engine->context(), data[2]); } -static QScriptValue js_observedGet(QScriptContext *context, QScriptEngine *, - ScriptPropertyObserver * const observer) +void ScriptEngine::setObservedProperty(JSValue &object, const QString &name, + const JSValue &value) { - const QScriptValue data = context->callee().property(getterFuncHelperProperty()); - 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) -{ - QScriptValue data = newArray(); - data.setProperty(0, object); - data.setProperty(1, name); - data.setProperty(2, value); - QScriptValue getterFunc = newFunction(js_observedGet, - static_cast<ScriptPropertyObserver *>(m_observer.get())); - getterFunc.setProperty(getterFuncHelperProperty(), data); - object.setProperty(name, getterFunc, QScriptValue::PropertyGetter); + ScopedJsValue jsName(m_context, makeJsString(m_context, name)); + JSValueList funcData{object, jsName, value}; + JSValue getterFunc = JS_NewCFunctionData(m_context, &js_observedGet, 0, 0, 3, funcData.data()); + const ScopedJsAtom nameAtom(m_context, name); + JS_DefinePropertyGetSet(m_context, object, nameAtom, getterFunc, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); if (m_observer->unobserveMode() == UnobserveMode::Enabled) m_observedProperties.emplace_back(object, name, value); } @@ -327,38 +408,16 @@ void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name void ScriptEngine::unobserveProperties() { for (auto &elem : m_observedProperties) { - QScriptValue &object = std::get<0>(elem); + JSValue &object = std::get<0>(elem); const QString &name = std::get<1>(elem); - const QScriptValue &value = std::get<2>(elem); - object.setProperty(name, QScriptValue(), QScriptValue::PropertyGetter); - object.setProperty(name, value, QScriptValue::PropertyFlags()); + const JSValue &value = std::get<2>(elem); + const ScopedJsAtom jsName(m_context, name); + JS_DefineProperty(m_context, object, jsName, value, JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); } m_observedProperties.clear(); } -static QScriptValue js_deprecatedGet(QScriptContext *context, QScriptEngine *qtengine) -{ - const auto engine = static_cast<const ScriptEngine *>(qtengine); - const QScriptValue data = context->callee().property(getterFuncHelperProperty()); - engine->logger().qbsWarning() - << ScriptEngine::tr("Property %1 is deprecated. Please use %2 instead.").arg( - data.property(0).toString(), data.property(1).toString()); - return data.property(2); -} - -void ScriptEngine::setDeprecatedProperty(QScriptValue &object, const QString &oldName, - const QString &newName, const QScriptValue &value) -{ - QScriptValue data = newArray(); - data.setProperty(0, oldName); - data.setProperty(1, newName); - data.setProperty(2, value); - QScriptValue getterFunc = newFunction(js_deprecatedGet); - getterFunc.setProperty(getterFuncHelperProperty(), data); - object.setProperty(oldName, getterFunc, QScriptValue::PropertyGetter - | QScriptValue::SkipInEnumeration); -} - QProcessEnvironment ScriptEngine::environment() const { return m_environment; @@ -369,19 +428,19 @@ void ScriptEngine::setEnvironment(const QProcessEnvironment &env) m_environment = env; } -void ScriptEngine::importFile(const QString &filePath, QScriptValue &targetObject) +void ScriptEngine::importFile(const QString &filePath, JSValue targetObject) { AccumulatingTimer importTimer(m_elapsedTimeImporting != -1 ? &m_elapsedTimeImporting : nullptr); - QScriptValue &evaluationResult = m_jsFileCache[filePath]; - if (evaluationResult.isValid()) { - ScriptImporter::copyProperties(evaluationResult, targetObject); + JSValue &evaluationResult = m_jsFileCache[filePath]; + if (JS_IsObject(evaluationResult)) { + ScriptImporter::copyProperties(m_context, evaluationResult, targetObject); return; } QFile file(filePath); if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) - throw ErrorInfo(tr("Cannot open '%1'.").arg(filePath)); + throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); QTextStream stream(&file); - stream.setCodec("UTF-8"); + setupDefaultCodec(stream); const QString sourceCode = stream.readAll(); file.close(); m_currentDirPathStack.push(FileInfo::path(filePath)); @@ -392,7 +451,7 @@ void ScriptEngine::importFile(const QString &filePath, QScriptValue &targetObjec static QString findExtensionDir(const QStringList &searchPaths, const QString &extensionPath) { for (const QString &searchPath : searchPaths) { - const QString dirPath = searchPath + QStringLiteral("/imports/") + extensionPath; + QString dirPath = searchPath + QStringLiteral("/imports/") + extensionPath; QFileInfo fi(dirPath); if (fi.exists() && fi.isDir()) return dirPath; @@ -400,101 +459,177 @@ static QString findExtensionDir(const QStringList &searchPaths, const QString &e return {}; } -static QScriptValue mergeExtensionObjects(const QScriptValueList &lst) +JSValue ScriptEngine::mergeExtensionObjects(const JSValueList &lst) { - QScriptValue result; - for (const QScriptValue &v : lst) { - if (!result.isValid()) { + JSValue result = JS_UNDEFINED; + for (const JSValue &v : lst) { + if (!JS_IsObject(result)) { result = v; continue; } - QScriptValueIterator svit(v); - while (svit.hasNext()) { - svit.next(); - result.setProperty(svit.name(), svit.value()); - } + ScriptImporter::copyProperties(m_context, v, result); + JS_FreeValue(m_context, v); } return result; } -static QScriptValue loadInternalExtension(QScriptContext *context, ScriptEngine *engine, - const QString &uri) +JSValue ScriptEngine::getInternalExtension(const char *name) const { - const QString name = uri.mid(4); // remove the "qbs." part - QScriptValue extensionObj = JsExtensions::loadExtension(engine, name); - if (!extensionObj.isValid()) { - return context->throwError(ScriptEngine::tr("loadExtension: " - "cannot load extension '%1'.").arg(uri)); - } - return extensionObj; + const auto cached = m_internalExtensions.constFind(QLatin1String(name)); + if (cached != m_internalExtensions.constEnd()) + return JS_DupValue(m_context, cached.value()); + return JS_UNDEFINED; +} + +void ScriptEngine::addInternalExtension(const char *name, JSValue ext) +{ + m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext)); } -QScriptValue ScriptEngine::js_loadExtension(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QVariant &v, quintptr id, bool frozen) { - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The loadExtension function requires " - "an extension name.")); + if (v.isNull()) + return JS_UNDEFINED; + switch (static_cast<QMetaType::Type>(v.userType())) { + case QMetaType::QByteArray: + return asJsValue(v.toByteArray()); + case QMetaType::QString: + return asJsValue(v.toString()); + case QMetaType::QStringList: + return asJsValue(v.toStringList()); + case QMetaType::QVariantList: + return asJsValue(v.toList(), id, frozen); + case QMetaType::Int: + case QMetaType::UInt: + return JS_NewInt32(m_context, v.toInt()); + case QMetaType::Long: + case QMetaType::ULong: + case QMetaType::LongLong: + case QMetaType::ULongLong: + return JS_NewInt64(m_context, v.toInt()); + case QMetaType::Bool: + return JS_NewBool(m_context, v.toBool()); + case QMetaType::QDateTime: + return JS_NewDate(m_context, v.toDateTime().toMSecsSinceEpoch()); + case QMetaType::QVariantMap: + return asJsValue(v.toMap(), id, frozen); + default: + return JS_UNDEFINED; } +} - const auto engine = static_cast<const ScriptEngine *>(qtengine); - ErrorInfo deprWarning(Tr::tr("The loadExtension() function is deprecated and will be " - "removed in a future version of Qbs. Use require() " - "instead."), context->backtrace()); - engine->logger().printWarning(deprWarning); +JSValue ScriptEngine::asJsValue(const QByteArray &s) +{ + return JS_NewArrayBufferCopy( + m_context, reinterpret_cast<const uint8_t *>(s.constData()), s.size()); +} - return js_require(context, qtengine); +JSValue ScriptEngine::asJsValue(const QString &s) +{ + const auto it = m_stringCache.constFind(s); + if (it != m_stringCache.constEnd()) + return JS_DupValue(m_context, it.value()); + const JSValue sv = JS_NewString(m_context, s.toUtf8().constData()); + m_stringCache.insert(s, sv); + return JS_DupValue(m_context, sv); } -QScriptValue ScriptEngine::js_loadFile(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QStringList &l) { - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The loadFile function requires a file path.")); - } + JSValue array = JS_NewArray(m_context); + setJsProperty(m_context, array, QLatin1String("length"), JS_NewInt32(m_context, l.size())); + for (int i = 0; i < l.size(); ++i) + JS_SetPropertyUint32(m_context, array, i, asJsValue(l.at(i))); + return array; +} - const auto engine = static_cast<const ScriptEngine *>(qtengine); - ErrorInfo deprWarning(Tr::tr("The loadFile() function is deprecated and will be " - "removed in a future version of Qbs. Use require() " - "instead."), context->backtrace()); - engine->logger().printWarning(deprWarning); +JSValue ScriptEngine::asJsValue(const QVariantMap &m, quintptr id, bool frozen) +{ + const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd(); + if (it != m_jsValueCache.constEnd()) + return JS_DupValue(m_context, it.value()); + frozen = id || frozen; + JSValue obj = JS_NewObject(m_context); + for (auto it = m.begin(); it != m.end(); ++it) + setJsProperty(m_context, obj, it.key(), asJsValue(it.value(), 0, frozen)); + if (frozen) + JS_ObjectSeal(m_context, obj, true); + if (!id) + return obj; + m_jsValueCache[id] = obj; + return JS_DupValue(m_context, obj); +} - return js_require(context, qtengine); +void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue value) +{ + const ScopedJsValue globalObject(m_context, JS_GetGlobalObject(m_context)); + setJsProperty(m_context, globalObject, property, value); } -QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qtengine) +JSValue ScriptEngine::asJsValue(const QVariantList &l, quintptr id, bool frozen) { - const auto engine = static_cast<ScriptEngine *>(qtengine); - if (context->argumentCount() < 1) { - return context->throwError( - ScriptEngine::tr("The require function requires a module name or path.")); - } + const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd(); + if (it != m_jsValueCache.constEnd()) + return JS_DupValue(m_context, it.value()); + frozen = id || frozen; + JSValue array = JS_NewArray(m_context); + setJsProperty(m_context, array, QLatin1String("length"), JS_NewInt32(m_context, l.size())); + for (int i = 0; i < l.size(); ++i) + JS_SetPropertyUint32(m_context, array, i, asJsValue(l.at(i), 0, frozen)); + if (frozen) + JS_ObjectSeal(m_context, array, true); + if (!id) + return array; + m_jsValueCache[id] = array; + return JS_DupValue(m_context, array); +} + +JSValue ScriptEngine::loadInternalExtension(const QString &uri) +{ + const QString name = uri.mid(4); // remove the "qbs." part + const auto cached = m_internalExtensions.constFind(name); + if (cached != m_internalExtensions.constEnd()) + return cached.value(); + JSValue extensionObj = JsExtensions::loadExtension(this, name); + if (!JS_IsObject(extensionObj)) + return throwError(Tr::tr("loadExtension: cannot load extension '%1'.").arg(uri)); + m_internalExtensions.insert(name, extensionObj); + return extensionObj; +} + +JSValue ScriptEngine::js_require(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int, JSValue *func_data) +{ + Q_UNUSED(this_val) + + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + QBS_ASSERT(engine, return JS_EXCEPTION); + if (argc < 1) + return engine->throwError(Tr::tr("The require function requires a module name or path.")); - const QString moduleName = context->argument(0).toString(); + const QString moduleName = getJsString(ctx, argv[0]); // First try to load a named module if the argument doesn't look like a file path if (!moduleName.contains(QLatin1Char('/'))) { if (engine->m_extensionSearchPathsStack.empty()) - return context->throwError( - ScriptEngine::tr("require: internal error. No search paths.")); + return engine->throwError(Tr::tr("require: internal error. No search paths.")); - const QString uri = moduleName; if (engine->m_logger.debugEnabled()) { engine->m_logger.qbsDebug() - << "[require] loading extension " << uri; + << "[require] loading extension " << moduleName; } - QString uriAsPath = uri; - uriAsPath.replace(QLatin1Char('.'), QLatin1Char('/')); + QString moduleNameAsPath = moduleName; + moduleNameAsPath.replace(QLatin1Char('.'), QLatin1Char('/')); const QStringList searchPaths = engine->m_extensionSearchPathsStack.top(); - const QString dirPath = findExtensionDir(searchPaths, uriAsPath); + const QString dirPath = findExtensionDir(searchPaths, moduleNameAsPath); if (dirPath.isEmpty()) { - if (uri.startsWith(QStringLiteral("qbs."))) - return loadInternalExtension(context, engine, uri); + if (moduleName.startsWith(QStringLiteral("qbs."))) + return JS_DupValue(ctx, engine->loadInternalExtension(moduleName)); } else { QDirIterator dit(dirPath, StringConstants::jsFileWildcards(), QDir::Files | QDir::Readable); - QScriptValueList values; + JSValueList values; std::vector<QString> filePaths; try { while (dit.hasNext()) { @@ -503,19 +638,19 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt engine->m_logger.qbsDebug() << "[require] importing file " << filePath; } - QScriptValue obj = engine->newObject(); + ScopedJsValue obj(engine->context(), engine->newObject()); engine->importFile(filePath, obj); - values << obj; + values << obj.release(); filePaths.push_back(filePath); } } catch (const ErrorInfo &e) { - return context->throwError(e.toString()); + return engine->throwError(e.toString()); } if (!values.empty()) { - const QScriptValue mergedValue = mergeExtensionObjects(values); + const JSValue mergedValue = engine->mergeExtensionObjects(values); engine->m_requireResults.push_back(mergedValue); - engine->m_filePathsPerImport[mergedValue.objectId()] = filePaths; + engine->m_filePathsPerImport[jsObjectId(mergedValue)] = filePaths; return mergedValue; } } @@ -524,52 +659,88 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt // file located in the current directory search path; try that next } - if (engine->m_currentDirPathStack.empty()) { - return context->throwError( - ScriptEngine::tr("require: internal error. No current directory.")); - } + if (engine->m_currentDirPathStack.empty()) + return engine->throwError(Tr::tr("require: internal error. No current directory.")); - QScriptValue result; + JSValue result; try { const QString filePath = FileInfo::resolvePath(engine->m_currentDirPathStack.top(), moduleName); - result = engine->newObject(); - engine->importFile(filePath, result); static const QString scopeNamePrefix = QStringLiteral("_qbs_scope_"); const QString scopeName = scopeNamePrefix + QString::number(qHash(filePath), 16); - result.setProperty(StringConstants::importScopeNamePropertyInternal(), scopeName); - context->thisObject().setProperty(scopeName, result); + result = getJsProperty(ctx, func_data[0], scopeName); + if (JS_IsObject(result)) + return result; // Same JS file imported from same qbs file via different JS files (e.g. codesign.js from DarwinGCC.qbs via gcc.js and darwin.js). + ScopedJsValue scopedResult(engine->context(), engine->newObject()); + engine->importFile(filePath, scopedResult); + result = scopedResult.release(); + setJsProperty(ctx, result, StringConstants::importScopeNamePropertyInternal(), scopeName); + setJsProperty(ctx, func_data[0], scopeName, result); engine->m_requireResults.push_back(result); - engine->m_filePathsPerImport[result.objectId()] = { filePath }; + engine->m_filePathsPerImport[jsObjectId(JS_DupValue(ctx, result))] = { filePath }; } catch (const ErrorInfo &e) { - result = context->throwError(e.toString()); + result = engine->throwError(e.toString()); } return result; } -QScriptClass *ScriptEngine::modulePropertyScriptClass() const +JSClassID ScriptEngine::modulePropertyScriptClass() const { return m_modulePropertyScriptClass; } -void ScriptEngine::setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass) +void ScriptEngine::setModulePropertyScriptClass(JSClassID modulePropertyScriptClass) { m_modulePropertyScriptClass = modulePropertyScriptClass; } -void ScriptEngine::addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj) +template<typename T> JSValue getScriptValue(JSContext *ctx, const T *t, + const std::unordered_map<const T *, JSValue> &map) { - m_resourceAcquiringScriptObjects.push_back(obj); + const auto it = map.find(t); + return it == map.end() ? JS_UNDEFINED : JS_DupValue(ctx, it->second); } -void ScriptEngine::releaseResourcesOfScriptObjects() +template<typename T> void setScriptValue(JSContext *ctx, const T *t, JSValue value, + std::unordered_map<const T *, JSValue> &map) { - if (m_resourceAcquiringScriptObjects.empty()) + value = JS_DupValue(ctx, value); + const auto it = map.find(t); + if (it == map.end()) { + map.insert(std::make_pair(t, value)); return; - std::for_each(m_resourceAcquiringScriptObjects.begin(), m_resourceAcquiringScriptObjects.end(), - std::mem_fn(&ResourceAcquiringScriptObject::releaseResources)); - m_resourceAcquiringScriptObjects.clear(); + } + JS_FreeValue(ctx, it->second); + it->second = value; +} + +JSValue ScriptEngine::artifactsMapScriptValue(const ResolvedProduct *product) +{ + return getScriptValue(m_context, product, m_productArtifactsMapScriptValues); +} + +void ScriptEngine::setArtifactsMapScriptValue(const ResolvedProduct *product, JSValue value) +{ + setScriptValue(m_context, product, value, m_productArtifactsMapScriptValues); +} + +JSValue ScriptEngine::artifactsMapScriptValue(const ResolvedModule *module) +{ + return getScriptValue(m_context, module, m_moduleArtifactsMapScriptValues); +} + +void ScriptEngine::setArtifactsMapScriptValue(const ResolvedModule *module, JSValue value) +{ + setScriptValue(m_context, module, value, m_moduleArtifactsMapScriptValues); +} + +JSValue ScriptEngine::getArtifactProperty(JSValue obj, + const std::function<JSValue (const Artifact *)> &propGetter) +{ + std::lock_guard lock(m_artifactsMutex); + const Artifact * const a = attachedPointer<Artifact>(obj, dataWithPtrClass()); + return a ? propGetter(a) : JS_EXCEPTION; } void ScriptEngine::addCanonicalFilePathResult(const QString &filePath, @@ -616,49 +787,100 @@ Set<QString> ScriptEngine::imports() const return filePaths; } -QScriptValueList ScriptEngine::argumentList(const QStringList &argumentNames, - const QScriptValue &context) +JSValue ScriptEngine::newObject() const { - QScriptValueList result; - for (const auto &name : argumentNames) - result += context.property(name); - return result; + return JS_NewObject(m_context); +} + +JSValue ScriptEngine::newArray(int length, JsValueOwner owner) +{ + JSValue arr = JS_NewArray(m_context); + JS_SetPropertyStr(m_context, arr, "length", JS_NewInt32(m_context, length)); + if (owner == JsValueOwner::ScriptEngine) + ++m_evalResults[arr]; + return arr; +} + +JSValue ScriptEngine::evaluate(JsValueOwner resultOwner, const QString &code, + const QString &filePath, int line, const JSValueList &scopeChain) +{ + m_scopeChains << scopeChain; + const QByteArray &codeStr = code.toUtf8(); + + m_evalPositions.emplace(filePath, line); + const JSValue v = JS_EvalThis(m_context, globalObject(), codeStr.constData(), codeStr.length(), + filePath.toUtf8().constData(), line, JS_EVAL_TYPE_GLOBAL); + m_evalPositions.pop(); + m_scopeChains.removeLast(); + if (resultOwner == JsValueOwner::ScriptEngine && JS_VALUE_HAS_REF_COUNT(v)) + ++m_evalResults[v]; + return v; } -CodeLocation ScriptEngine::lastErrorLocation(const QScriptValue &v, - const CodeLocation &fallbackLocation) const +void ScriptEngine::handleJsProperties(JSValue obj, const PropertyHandler &handler) { - const QScriptValue &errorVal = lastErrorValue(v); - const CodeLocation errorLoc(errorVal.property(StringConstants::fileNameProperty()).toString(), - errorVal.property(QStringLiteral("lineNumber")).toInt32(), - errorVal.property(QStringLiteral("expressionCaretOffset")).toInt32(), - false); - return errorLoc.isValid() ? errorLoc : fallbackLocation; + qbs::Internal::handleJsProperties(m_context, obj, handler); } -ErrorInfo ScriptEngine::lastError(const QScriptValue &v, const CodeLocation &fallbackLocation) const +ScopedJsValueList ScriptEngine::argumentList(const QStringList &argumentNames, + const JSValue &context) const { - const QString msg = lastErrorString(v); - CodeLocation errorLocation = lastErrorLocation(v); - if (errorLocation.isValid()) - return ErrorInfo(msg, errorLocation); - const QStringList backtrace = uncaughtExceptionBacktraceOrEmpty(); - if (!backtrace.empty()) { - ErrorInfo e(msg, backtrace); - if (e.hasLocation()) - return e; + JSValueList result; + for (const auto &name : argumentNames) + result.push_back(getJsProperty(m_context, context, name)); + return {m_context, result}; +} + +JSClassID ScriptEngine::registerClass(const char *name, JSClassCall *constructor, + JSClassFinalizer *finalizer, JSValue scope, + GetPropertyNames getPropertyNames, GetProperty getProperty) +{ + JSClassID id = 0; + const auto classIt = m_classes.constFind(QLatin1String(name)); + if (classIt == m_classes.constEnd()) { + JS_NewClassID(&id); + const auto it = getProperty + ? m_exoticMethods.insert(id, JSClassExoticMethods{getProperty, getPropertyNames}) + : m_exoticMethods.end(); + JSClassDef jsClass{name, finalizer, nullptr, constructor, + it != m_exoticMethods.end() ? &it.value() : nullptr}; + const int status = JS_NewClass(m_jsRuntime, id, &jsClass); + QBS_ASSERT(status == 0, return 0); + m_classes.insert(QLatin1String(name), id); + } else { + id = classIt.value(); + } + if (!JS_IsUndefined(scope)) { + const JSValue classObj = JS_NewObjectClass(m_context, id); + JS_SetConstructorBit(m_context, classObj, constructor != nullptr); + JS_SetPropertyStr(m_context, scope, name, classObj); } - return ErrorInfo(msg, fallbackLocation); + return id; +} + +JSClassID ScriptEngine::getClassId(const char *name) const +{ + return m_classes.value(QLatin1String(name)); +} + +JSValue ScriptEngine::throwError(const QString &message) const +{ + return qbs::Internal::throwError(m_context, message); } void ScriptEngine::cancel() { - QTimer::singleShot(0, this, [this] { abort(); }); + m_canceling = true; } -void ScriptEngine::abort() +int ScriptEngine::interruptor(JSRuntime *, void *opaqueEngine) { - abortEvaluation(m_cancelationError); + const auto engine = reinterpret_cast<ScriptEngine *>(opaqueEngine); + if (engine->m_canceling) { + engine->m_canceling = false; + return 1; + } + return 0; } bool ScriptEngine::gatherFileResults() const @@ -667,83 +889,105 @@ bool ScriptEngine::gatherFileResults() const || evalContext() == EvalContext::ProbeExecution; } +void ScriptEngine::setMaxStackSize() +{ + size_t stackSize = 0; // Turn check off by default. + bool ok; + const int stackSizeFromEnv = qEnvironmentVariableIntValue("QBS_MAX_JS_STACK_SIZE", &ok); + if (ok && stackSizeFromEnv >= 0) + stackSize = stackSizeFromEnv; + JS_SetMaxStackSize(m_jsRuntime, stackSize); +} + +JSValue ScriptEngine::getArtifactScriptValue(Artifact *a, const QString &moduleName, + const std::function<void(JSValue obj)> &setup) +{ + std::lock_guard lock(m_artifactsMutex); + const auto it = m_artifactsScriptValues.constFind(qMakePair(a, moduleName)); + if (it != m_artifactsScriptValues.constEnd()) + return JS_DupValue(m_context, *it); + a->setDeregister([this](const Artifact *a) { + const std::lock_guard lock(m_artifactsMutex); + for (auto it = m_artifactsScriptValues.begin(); it != m_artifactsScriptValues.end(); ) { + if (it.key().first == a) { + JS_SetOpaque(it.value(), nullptr); + JS_FreeValue(m_context, it.value()); + it = m_artifactsScriptValues.erase(it); + } else { + ++it; + } + } + }); + JSValue obj = JS_NewObjectClass(context(), dataWithPtrClass()); + attachPointerTo(obj, a); + setup(obj); + m_artifactsScriptValues.insert(qMakePair(a, moduleName), JS_DupValue(m_context, obj)); + return obj; +} + +void ScriptEngine::releaseInputArtifactScriptValues(const RuleNode *ruleNode) +{ + for (auto it = m_artifactsScriptValues.begin(); it != m_artifactsScriptValues.end();) { + Artifact * const a = it.key().first; + if (ruleNode->children.contains(a)) { + a->setDeregister({}); + JS_FreeValue(m_context, it.value()); + it = m_artifactsScriptValues.erase(it); + } else { + ++it; + } + } +} + class JSTypeExtender { public: - JSTypeExtender(ScriptEngine *engine, const QString &typeName) - : m_engine(engine) + JSTypeExtender(ScriptEngine *engine, const QString &typeName) : m_engine(engine) { - m_proto = engine->globalObject().property(typeName) - .property(QStringLiteral("prototype")); - QBS_ASSERT(m_proto.isObject(), return); - m_descriptor = engine->newObject(); + const ScopedJsValue globalObject(engine->context(), JS_GetGlobalObject(engine->context())); + const ScopedJsValue type(engine->context(), getJsProperty(engine->context(), + globalObject, typeName)); + m_proto = getJsProperty(engine->context(), type, QStringLiteral("prototype")); + QBS_ASSERT(JS_IsObject(m_proto), return); + } + + ~JSTypeExtender() + { + JS_FreeValue(m_engine->context(), m_proto); } void addFunction(const QString &name, const QString &code) { - QScriptValue f = m_engine->evaluate(code); - QBS_ASSERT(f.isFunction(), return); - m_descriptor.setProperty(QStringLiteral("value"), f); - m_engine->defineProperty(m_proto, name, m_descriptor); + const JSValue f = m_engine->evaluate(JsValueOwner::Caller, code); + QBS_ASSERT(JS_IsFunction(m_engine->context(), f), return); + JS_DefinePropertyValueStr(m_engine->context(), m_proto, name.toUtf8().constData(), f, 0); } private: - ScriptEngine *const m_engine; - QScriptValue m_proto; - QScriptValue m_descriptor; + ScriptEngine * const m_engine; + JSValue m_proto = JS_UNDEFINED; }; -static QScriptValue js_consoleError(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.error() expects 1 argument")); - logger->qbsLog(LoggerError) << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleWarn(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.warn() expects 1 argument")); - logger->qbsWarning() << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleInfo(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.info() expects 1 argument")); - logger->qbsInfo() << context->argument(0).toString(); - return engine->undefinedValue(); -} - -static QScriptValue js_consoleDebug(QScriptContext *context, QScriptEngine *engine, Logger *logger) +static JSValue js_consoleFunc(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv, + int level) { - if (Q_UNLIKELY(context->argumentCount() != 1)) - return context->throwError(QScriptContext::SyntaxError, - QStringLiteral("console.debug() expects 1 argument")); - logger->qbsDebug() << context->argument(0).toString(); + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + QBS_ASSERT(engine, return JS_EXCEPTION); + if (Q_UNLIKELY(argc != 1)) + return engine->throwError(Tr::tr("The console functions expect 1 argument.")); + engine->logger().qbsLog(static_cast<LoggerLevel>(level)) << getJsString(ctx, argv[0]); return engine->undefinedValue(); } -static QScriptValue js_consoleLog(QScriptContext *context, QScriptEngine *engine, Logger *logger) -{ - return js_consoleDebug(context, engine, logger); -} - void ScriptEngine::installQbsBuiltins() { - globalObject().setProperty(StringConstants::qbsModule(), m_qbsObject = newObject()); - - globalObject().setProperty(QStringLiteral("console"), m_consoleObject = newObject()); - installConsoleFunction(QStringLiteral("debug"), &js_consoleDebug); - installConsoleFunction(QStringLiteral("error"), &js_consoleError); - installConsoleFunction(QStringLiteral("info"), &js_consoleInfo); - installConsoleFunction(QStringLiteral("log"), &js_consoleLog); - installConsoleFunction(QStringLiteral("warn"), &js_consoleWarn); + const JSValue consoleObj = newObject(); + setPropertyOnGlobalObject(QLatin1String("console"), consoleObj); + installConsoleFunction(consoleObj, QStringLiteral("debug"), LoggerDebug); + installConsoleFunction(consoleObj, QStringLiteral("error"), LoggerError); + installConsoleFunction(consoleObj, QStringLiteral("info"), LoggerInfo); + installConsoleFunction(consoleObj, QStringLiteral("log"), LoggerDebug); + installConsoleFunction(consoleObj, QStringLiteral("warn"), LoggerWarning); } void ScriptEngine::extendJavaScriptBuiltins() @@ -773,48 +1017,27 @@ void ScriptEngine::extendJavaScriptBuiltins() JSTypeExtender stringExtender(this, QStringLiteral("String")); stringExtender.addFunction(QStringLiteral("contains"), QStringLiteral("(function(e){return this.indexOf(e) !== -1;})")); - stringExtender.addFunction(QStringLiteral("startsWith"), - QStringLiteral("(function(e){return this.slice(0, e.length) === e;})")); - stringExtender.addFunction(QStringLiteral("endsWith"), - QStringLiteral("(function(e){return this.slice(-e.length) === e;})")); } -void ScriptEngine::installFunction(const QString &name, int length, QScriptValue *functionValue, - FunctionSignature f, QScriptValue *targetObject = nullptr) +void ScriptEngine::installConsoleFunction(JSValue consoleObj, const QString &name, + LoggerLevel level) { - if (!functionValue->isValid()) - *functionValue = newFunction(f, length); - (targetObject ? *targetObject : globalObject()).setProperty(name, *functionValue); + JS_SetPropertyStr(m_context, consoleObj, name.toUtf8().constData(), + JS_NewCFunctionMagic(m_context, js_consoleFunc, name.toUtf8().constData(), 1, + JS_CFUNC_generic_magic, level)); } -void ScriptEngine::installQbsFunction(const QString &name, int length, FunctionSignature f) -{ - QScriptValue functionValue; - installFunction(name, length, &functionValue, f, &m_qbsObject); -} - -void ScriptEngine::installConsoleFunction(const QString &name, - QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)) -{ - m_consoleObject.setProperty(name, newFunction(f, &m_logger)); -} - -static QString loadFileString() { return QStringLiteral("loadFile"); } -static QString loadExtensionString() { return QStringLiteral("loadExtension"); } static QString requireString() { return QStringLiteral("require"); } -void ScriptEngine::installImportFunctions() +void ScriptEngine::installImportFunctions(JSValue importScope) { - installFunction(loadFileString(), 1, &m_loadFileFunction, js_loadFile); - installFunction(loadExtensionString(), 1, &m_loadExtensionFunction, js_loadExtension); - installFunction(requireString(), 1, &m_requireFunction, js_require); + const JSValue require = JS_NewCFunctionData(m_context, js_require, 1, 0, 1, &importScope); + setPropertyOnGlobalObject(requireString(), require); } void ScriptEngine::uninstallImportFunctions() { - globalObject().setProperty(loadFileString(), QScriptValue()); - globalObject().setProperty(loadExtensionString(), QScriptValue()); - globalObject().setProperty(requireString(), QScriptValue()); + setPropertyOnGlobalObject(requireString(), JS_UNDEFINED); } ScriptEngine::PropertyCacheKey::PropertyCacheKey(QString moduleName, @@ -825,5 +1048,54 @@ ScriptEngine::PropertyCacheKey::PropertyCacheKey(QString moduleName, { } +JsException ScriptEngine::checkAndClearException(const CodeLocation &fallbackLocation) const +{ + return {m_context, JS_GetException(m_context), fallbackLocation}; +} + +void ScriptEngine::clearRequestedProperties() +{ + m_propertiesRequestedInScript.clear(); + m_propertiesRequestedFromArtifact.clear(); + m_importsRequestedInScript.clear(); + m_productsWithRequestedDependencies.clear(); + m_requestedArtifacts.clear(); + m_requestedExports.clear(); +}; + +void ScriptEngine::takeOwnership(JSValue v) +{ + ++m_evalResults[v]; +} + +ScriptEngine::Importer::Importer( + ScriptEngine &engine, const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, + ObserveMode observeMode) + : m_engine(engine), m_fileCtx(fileCtx), m_targetObject(targetObject) +{ + m_engine.installImportFunctions(targetObject); + m_engine.m_currentDirPathStack.push(FileInfo::path(fileCtx->filePath())); + m_engine.m_extensionSearchPathsStack.push(fileCtx->searchPaths()); + m_engine.m_observeMode = observeMode; +} + +ScriptEngine::Importer::~Importer() +{ + m_engine.m_requireResults.clear(); + m_engine.m_currentDirPathStack.pop(); + m_engine.m_extensionSearchPathsStack.pop(); + m_engine.uninstallImportFunctions(); +} + +void ScriptEngine::Importer::run() +{ + for (const JsImport &jsImport : m_fileCtx->jsImports()) + m_engine.import(jsImport, m_targetObject); + if (m_engine.m_observeMode == ObserveMode::Enabled) { + for (JSValue &sv : m_engine.m_requireResults) + m_engine.observeImport(sv); + } +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h index 24e133dff..4a55392e3 100644 --- a/src/lib/corelib/language/scriptengine.h +++ b/src/lib/corelib/language/scriptengine.h @@ -45,8 +45,11 @@ #include <buildgraph/requestedartifacts.h> #include <buildgraph/requesteddependencies.h> #include <logging/logger.h> +#include <quickjs.h> #include <tools/codelocation.h> #include <tools/filetime.h> +#include <tools/porting.h> +#include <tools/scripttools.h> #include <tools/set.h> #include <QtCore/qdir.h> @@ -55,8 +58,8 @@ #include <QtCore/qprocess.h> #include <QtCore/qstring.h> -#include <QtScript/qscriptengine.h> - +#include <atomic> +#include <functional> #include <memory> #include <mutex> #include <stack> @@ -67,8 +70,10 @@ namespace qbs { namespace Internal { class Artifact; +class Evaluator; class JsImport; class PrepareScriptObserver; +class RuleNode; class ScriptImporter; class ScriptPropertyObserver; @@ -85,33 +90,33 @@ public: }; using DubiousContextList = std::vector<DubiousContext>; - -/* - * ScriptObject that acquires resources, for example a file handle. - * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources. - */ -class ResourceAcquiringScriptObject -{ -public: - virtual ~ResourceAcquiringScriptObject() = default; - virtual void releaseResources() = 0; -}; +enum class JsValueOwner { Caller, ScriptEngine }; // TODO: This smells like cheating. Should always be Caller. enum class ObserveMode { Enabled, Disabled }; -class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine +class QBS_AUTOTEST_EXPORT ScriptEngine { - Q_OBJECT - ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); + struct PrivateTag {}; public: - static ScriptEngine *create(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); - ~ScriptEngine() override; + ScriptEngine(Logger &logger, EvalContext evalContext, PrivateTag); + ~ScriptEngine(); + + static std::unique_ptr<ScriptEngine> create(Logger &logger, EvalContext evalContext); + static ScriptEngine *engineForRuntime(const JSRuntime *runtime); + static ScriptEngine *engineForContext(const JSContext *ctx); + static LookupResult doExtraScopeLookup(JSContext *ctx, JSAtom prop); + + void reset(); Logger &logger() const { return m_logger; } - void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, + void import(const FileContextBaseConstPtr &fileCtx, JSValue &targetObject, ObserveMode observeMode); void clearImportsCache(); + void registerEvaluator(Evaluator *evaluator); + void unregisterEvaluator(const Evaluator *evaluator); + Evaluator *evaluator() const { return m_evaluator; } + void setEvalContext(EvalContext c) { m_evalContext = c; } EvalContext evalContext() const { return m_evalContext; } void checkContext(const QString &operation, const DubiousContextList &dubiousContexts); @@ -141,14 +146,7 @@ public: } void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); void addRequestedExport(const ResolvedProduct *product) { m_requestedExports.insert(product); } - void clearRequestedProperties() { - m_propertiesRequestedInScript.clear(); - m_propertiesRequestedFromArtifact.clear(); - m_importsRequestedInScript.clear(); - m_productsWithRequestedDependencies.clear(); - m_requestedArtifacts.clear(); - m_requestedExports.clear(); - } + void clearRequestedProperties(); PropertySet propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } QHash<QString, PropertySet> propertiesRequestedFromArtifact() const { return m_propertiesRequestedFromArtifact; @@ -164,9 +162,11 @@ public: RequestedArtifacts requestedArtifacts() const { return m_requestedArtifacts; } Set<const ResolvedProduct *> requestedExports() const { return m_requestedExports; } - void addImportRequestedInScript(qint64 importValueId); + void addImportRequestedInScript(quintptr importValueId); std::vector<QString> importedFilesUsedInScript() const; + void addExternallyCachedValue(JSValue *v) { m_externallyCachedValues.push_back(v); } + void setUsesIo() { m_usesIo = true; } void clearUsesIo() { m_usesIo = false; } bool usesIo() const { return m_usesIo; } @@ -180,11 +180,8 @@ public: 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); + void setObservedProperty(JSValue &object, const QString &name, const JSValue &value); void unobserveProperties(); - void setDeprecatedProperty(QScriptValue &object, const QString &name, const QString &newName, - const QScriptValue &value); PrepareScriptObserver *observer() const { return m_observer.get(); } QProcessEnvironment environment() const; @@ -203,23 +200,34 @@ public: QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; } Set<QString> imports() const; - static QScriptValueList argumentList(const QStringList &argumentNames, - const QScriptValue &context); - QStringList uncaughtExceptionBacktraceOrEmpty() const { - return hasUncaughtException() ? uncaughtExceptionBacktrace() : QStringList(); - } - bool hasErrorOrException(const QScriptValue &v) const { - return v.isError() || hasUncaughtException(); - } - QScriptValue lastErrorValue(const QScriptValue &v) const { - return v.isError() ? v : uncaughtException(); - } - QString lastErrorString(const QScriptValue &v) const { return lastErrorValue(v).toString(); } - CodeLocation lastErrorLocation(const QScriptValue &v, - const CodeLocation &fallbackLocation = CodeLocation()) const; - ErrorInfo lastError(const QScriptValue &v, - const CodeLocation &fallbackLocation = CodeLocation()) const; + JSValue newObject() const; + JSValue newArray(int length, JsValueOwner owner); + void takeOwnership(JSValue v); + JSValue undefinedValue() const { return JS_UNDEFINED; } + JSValue toScriptValue(const QVariant &v, quintptr id = 0) { return asJsValue(v, id); } + JSValue evaluate(JsValueOwner resultOwner, const QString &code, + const QString &filePath = QString(), int line = 1, + const JSValueList &scopeChain = {}); + void setLastLookupStatus(bool success) { m_lastLookupWasSuccess = success; } + JSContext *context() const { return m_context; } + JSValue globalObject() const { return m_globalObject; } + void setGlobalObject(JSValue obj) { m_globalObject = obj; } + void handleJsProperties(JSValueConst obj, const PropertyHandler &handler); + ScopedJsValueList argumentList(const QStringList &argumentNames, const JSValue &context) const; + + using GetProperty = int (*)(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); + using GetPropertyNames = int (*)(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj); + JSClassID registerClass(const char *name, JSClassCall *constructor, JSClassFinalizer *finalizer, + JSValue scope, + GetPropertyNames getPropertyNames = nullptr, + GetProperty getProperty = nullptr); + JSClassID getClassId(const char *name) const; + + JsException checkAndClearException(const CodeLocation &fallbackLocation) const; + JSValue throwError(const QString &message) const; void cancel(); @@ -228,77 +236,100 @@ public: bool isActive() const { return m_active; } void setActive(bool on) { m_active = on; } - using QScriptEngine::newFunction; - - template <typename T, typename E, - typename = std::enable_if_t<std::is_pointer<T>::value>, - typename = std::enable_if_t<std::is_pointer<E>::value>, - typename = std::enable_if_t<std::is_base_of< - QScriptEngine, std::remove_pointer_t<E>>::value> - > QScriptValue newFunction(QScriptValue (*signature)(QScriptContext *, E, T), T arg) { - return QScriptEngine::newFunction( - reinterpret_cast<FunctionWithArgSignature>(signature), - reinterpret_cast<void *>(const_cast< - std::add_pointer_t< - std::remove_const_t< - std::remove_pointer_t<T>>>>(arg))); - } - - QScriptClass *modulePropertyScriptClass() const; - void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass); + JSClassID modulePropertyScriptClass() const; + void setModulePropertyScriptClass(JSClassID modulePropertyScriptClass); - QScriptClass *productPropertyScriptClass() const { return m_productPropertyScriptClass; } - void setProductPropertyScriptClass(QScriptClass *productPropertyScriptClass) + JSClassID productPropertyScriptClass() const { return m_productPropertyScriptClass; } + void setProductPropertyScriptClass(JSClassID productPropertyScriptClass) { m_productPropertyScriptClass = productPropertyScriptClass; } - QScriptClass *artifactsScriptClass() const { return m_artifactsScriptClass; } - void setArtifactsScriptClass(QScriptClass *artifactsScriptClass) + JSClassID artifactsScriptClass(int index) const { return m_artifactsScriptClass[index]; } + void setArtifactsScriptClass(int index, JSClassID artifactsScriptClass) { - m_artifactsScriptClass = artifactsScriptClass; + m_artifactsScriptClass[index] = artifactsScriptClass; } - void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj); - void releaseResourcesOfScriptObjects(); + JSValue artifactsMapScriptValue(const ResolvedProduct *product); + void setArtifactsMapScriptValue(const ResolvedProduct *product, JSValue value); + JSValue artifactsMapScriptValue(const ResolvedModule *module); + void setArtifactsMapScriptValue(const ResolvedModule *module, JSValue value); + + JSValue getArtifactProperty(JSValue obj, + const std::function<JSValue(const Artifact *)> &propGetter); - QScriptValue &productScriptValuePrototype(const ResolvedProduct *product) + JSValue& baseProductScriptValue(const ResolvedProduct *product) { - return m_productScriptValues[product]; + return m_baseProductScriptValues[product]; } - QScriptValue &projectScriptValue(const ResolvedProject *project) + JSValue &projectScriptValue(const ResolvedProject *project) { return m_projectScriptValues[project]; } - QScriptValue &moduleScriptValuePrototype(const ResolvedModule *module) + JSValue &baseModuleScriptValue(const ResolvedModule *module) { - return m_moduleScriptValues[module]; + return m_baseModuleScriptValues[module]; } + JSValue getArtifactScriptValue(Artifact *a, const QString &moduleName, + const std::function<void(JSValue obj)> &setup); + void releaseInputArtifactScriptValues(const RuleNode *ruleNode); + + const JSValueList &contextStack() const { return m_contextStack; } + + JSClassID dataWithPtrClass() const { return m_dataWithPtrClass; } + + JSValue getInternalExtension(const char *name) const; + void addInternalExtension(const char *name, JSValue ext); + JSValue asJsValue(const QVariant &v, quintptr id = 0, bool frozen = false); + JSValue asJsValue(const QByteArray &s); + JSValue asJsValue(const QString &s); + JSValue asJsValue(const QStringList &l); + JSValue asJsValue(const QVariantList &l, quintptr id = 0, bool frozen = false); + JSValue asJsValue(const QVariantMap &m, quintptr id = 0, bool frozen = false); + + QVariant property(const char *name) const { return m_properties.value(QLatin1String(name)); } + void setProperty(const char *k, const QVariant &v) { m_properties.insert(QLatin1String(k), v); } + private: - QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE; + class Importer { + public: + Importer(ScriptEngine &engine, const FileContextBaseConstPtr &fileCtx, + JSValue &targetObject, ObserveMode observeMode); + ~Importer(); + void run(); + + private: + ScriptEngine &m_engine; + const FileContextBaseConstPtr &m_fileCtx; + JSValue &m_targetObject; + }; - void abort(); + static int interruptor(JSRuntime *rt, void *opaqueEngine); bool gatherFileResults() const; + void setMaxStackSize(); + void setPropertyOnGlobalObject(const QString &property, JSValue value); void installQbsBuiltins(); void extendJavaScriptBuiltins(); - void installFunction(const QString &name, int length, QScriptValue *functionValue, - FunctionSignature f, QScriptValue *targetObject); - void installQbsFunction(const QString &name, int length, FunctionSignature f); - void installConsoleFunction(const QString &name, - QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)); - void installImportFunctions(); + void installConsoleFunction(JSValue consoleObj, const QString &name, LoggerLevel level); + void installImportFunctions(JSValue importScope); void uninstallImportFunctions(); - void import(const JsImport &jsImport, QScriptValue &targetObject); - void observeImport(QScriptValue &jsImport); - void importFile(const QString &filePath, QScriptValue &targetObject); - static QScriptValue js_loadExtension(QScriptContext *context, QScriptEngine *qtengine); - static QScriptValue js_loadFile(QScriptContext *context, QScriptEngine *qtengine); - static QScriptValue js_require(QScriptContext *context, QScriptEngine *qtengine); + void import(const JsImport &jsImport, JSValue &targetObject); + void observeImport(JSValue &jsImport); + void importFile(const QString &filePath, JSValue targetObject); + static JSValue js_require(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data); + JSValue mergeExtensionObjects(const JSValueList &lst); + JSValue loadInternalExtension(const QString &uri); + + static void handleUndefinedFound(JSContext *ctx); + static void handleFunctionEntered(JSContext *ctx, JSValue this_obj); + static void handleFunctionExited(JSContext *ctx); class PropertyCacheKey { @@ -311,27 +342,30 @@ private: const PropertyMapConstPtr m_propertyMap; friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); - friend uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed); + friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed); }; friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); - friend uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed); + friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed); - static std::mutex m_creationDestructionMutex; + JSRuntime * const m_jsRuntime = JS_NewRuntime(); + JSContext * const m_context = JS_NewContext(m_jsRuntime); + JSValue m_globalObject = JS_NULL; ScriptImporter *m_scriptImporter; - QScriptClass *m_modulePropertyScriptClass; - QScriptClass *m_productPropertyScriptClass = nullptr; - QScriptClass *m_artifactsScriptClass = nullptr; - QHash<JsImport, QScriptValue> m_jsImportCache; - std::unordered_map<QString, QScriptValue> m_jsFileCache; - bool m_propertyCacheEnabled; - bool m_active; + JSClassID m_modulePropertyScriptClass = 0; + JSClassID m_productPropertyScriptClass = 0; + JSClassID m_artifactsScriptClass[2] = {0, 0}; + JSClassID m_dataWithPtrClass = 0; + Evaluator *m_evaluator = nullptr; + QHash<JsImport, JSValue> m_jsImportCache; + std::unordered_map<QString, JSValue> m_jsFileCache; + bool m_propertyCacheEnabled = true; + bool m_active = false; + std::atomic_bool m_canceling = false; QHash<PropertyCacheKey, QVariant> m_propertyCache; PropertySet m_propertiesRequestedInScript; QHash<QString, PropertySet> m_propertiesRequestedFromArtifact; Logger &m_logger; - QScriptValue m_definePropertyFunction; - QScriptValue m_emptyFunction; QProcessEnvironment m_environment; QHash<QString, QString> m_canonicalFilePathResult; QHash<QString, bool> m_fileExistsResult; @@ -339,28 +373,38 @@ private: QHash<QString, FileTime> m_fileLastModifiedResult; std::stack<QString> m_currentDirPathStack; std::stack<QStringList> m_extensionSearchPathsStack; - QScriptValue m_loadFileFunction; - QScriptValue m_loadExtensionFunction; - QScriptValue m_requireFunction; - QScriptValue m_qbsObject; - QScriptValue m_consoleObject; - QScriptValue m_cancelationError; + std::stack<std::pair<QString, int>> m_evalPositions; + JSValue m_qbsObject = JS_UNDEFINED; qint64 m_elapsedTimeImporting = -1; bool m_usesIo = false; EvalContext m_evalContext; - std::vector<ResourceAcquiringScriptObject *> m_resourceAcquiringScriptObjects; const std::unique_ptr<PrepareScriptObserver> m_observer; - std::vector<std::tuple<QScriptValue, QString, QScriptValue>> m_observedProperties; - std::vector<QScriptValue> m_requireResults; - std::unordered_map<qint64, std::vector<QString>> m_filePathsPerImport; + std::vector<std::tuple<JSValue, QString, JSValue>> m_observedProperties; + JSValueList m_requireResults; + std::unordered_map<quintptr, std::vector<QString>> m_filePathsPerImport; std::vector<qint64> m_importsRequestedInScript; Set<const ResolvedProduct *> m_productsWithRequestedDependencies; RequestedArtifacts m_requestedArtifacts; Set<const ResolvedProduct *> m_requestedExports; ObserveMode m_observeMode = ObserveMode::Disabled; - std::unordered_map<const ResolvedProduct *, QScriptValue> m_productScriptValues; - std::unordered_map<const ResolvedProject *, QScriptValue> m_projectScriptValues; - std::unordered_map<const ResolvedModule *, QScriptValue> m_moduleScriptValues; + std::unordered_map<const ResolvedProduct *, JSValue> m_baseProductScriptValues; + std::unordered_map<const ResolvedProduct *, JSValue> m_productArtifactsMapScriptValues; + std::unordered_map<const ResolvedModule *, JSValue> m_moduleArtifactsMapScriptValues; + std::unordered_map<const ResolvedProject *, JSValue> m_projectScriptValues; + std::unordered_map<const ResolvedModule *, JSValue> m_baseModuleScriptValues; + QList<JSValueList> m_scopeChains; + JSValueList m_contextStack; + QHash<JSClassID, JSClassExoticMethods> m_exoticMethods; + QHash<QString, JSClassID> m_classes; + QHash<QString, JSValue> m_internalExtensions; + QHash<QString, JSValue> m_stringCache; + QHash<quintptr, JSValue> m_jsValueCache; + QHash<JSValue, int> m_evalResults; + std::vector<JSValue *> m_externallyCachedValues; + QHash<QPair<Artifact *, QString>, JSValue> m_artifactsScriptValues; + QVariantMap m_properties; + std::recursive_mutex m_artifactsMutex; + bool m_lastLookupWasSuccess = false; }; class EvalContextSwitcher diff --git a/src/lib/corelib/language/scriptimporter.cpp b/src/lib/corelib/language/scriptimporter.cpp index 9c6d4d38e..fdb0689ad 100644 --- a/src/lib/corelib/language/scriptimporter.cpp +++ b/src/lib/corelib/language/scriptimporter.cpp @@ -48,8 +48,6 @@ #include <parser/qmljsparser_p.h> #include <tools/error.h> -#include <QtScript/qscriptvalueiterator.h> - namespace qbs { namespace Internal { @@ -95,7 +93,7 @@ private: return false; } - void add(const QStringRef &name) + void add(QStringView name) { if (m_first) { m_first = false; @@ -104,9 +102,10 @@ private: m_suffix.reserve(m_suffix.length() + name.length() * 2 + 2); m_suffix += QLatin1Char(','); } - m_suffix += name; - m_suffix += QLatin1Char(':'); - m_suffix += name; + // ugly, but qt5 does not have append overload for QStringView + m_suffix.append(name.data(), name.size()); + m_suffix.append(QLatin1Char(':')); + m_suffix.append(name.data(), name.size()); } bool m_first = false; @@ -120,10 +119,10 @@ ScriptImporter::ScriptImporter(ScriptEngine *scriptEngine) { } -QScriptValue ScriptImporter::importSourceCode(const QString &sourceCode, const QString &filePath, - QScriptValue &targetObject) +JSValue ScriptImporter::importSourceCode(const QString &sourceCode, const QString &filePath, + JSValue &targetObject) { - Q_ASSERT(targetObject.isObject()); + Q_ASSERT(JS_IsObject(targetObject)); // The targetObject 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. @@ -143,19 +142,18 @@ QScriptValue ScriptImporter::importSourceCode(const QString &sourceCode, const Q code = QLatin1String("(function(){\n") + sourceCode + extractor.suffix(); } - QScriptValue result = m_engine->evaluate(code, filePath, 0); - throwOnEvaluationError(m_engine, result, [&filePath] () { return CodeLocation(filePath, 0); }); - copyProperties(result, targetObject); - return result; + ScopedJsValue result(m_engine->context(), + m_engine->evaluate(JsValueOwner::Caller, code, filePath, 0)); + throwOnEvaluationError(m_engine, [&filePath] () { return CodeLocation(filePath, 0); }); + copyProperties(m_engine->context(), result, targetObject); + return result.release(); } -void ScriptImporter::copyProperties(const QScriptValue &src, QScriptValue &dst) +void ScriptImporter::copyProperties(JSContext *ctx, const JSValue &src, JSValue &dst) { - QScriptValueIterator it(src); - while (it.hasNext()) { - it.next(); - dst.setProperty(it.name(), it.value()); - } + handleJsProperties(ctx, src, [ctx, &dst](const JSAtom &name, const JSPropertyDescriptor &desc) { + JS_SetProperty(ctx, dst, name, JS_DupValue(ctx, desc.value)); + }); } } // namespace Internal diff --git a/src/lib/corelib/language/scriptimporter.h b/src/lib/corelib/language/scriptimporter.h index 8cff09382..6bec9b088 100644 --- a/src/lib/corelib/language/scriptimporter.h +++ b/src/lib/corelib/language/scriptimporter.h @@ -40,9 +40,9 @@ #ifndef SCRIPTIMPORTER_H #define SCRIPTIMPORTER_H -#include <QtCore/qhash.h> +#include <quickjs.h> -#include <QtScript/qscriptvalue.h> +#include <QtCore/qhash.h> namespace qbs { namespace Internal { @@ -53,9 +53,10 @@ class ScriptImporter { public: ScriptImporter(ScriptEngine *scriptEngine); - QScriptValue importSourceCode(const QString &sourceCode, const QString &filePath, QScriptValue &targetObject); + JSValue importSourceCode(const QString &sourceCode, const QString &filePath, + JSValue &targetObject); - static void copyProperties(const QScriptValue &src, QScriptValue &dst); + static void copyProperties(JSContext *ctx, const JSValue &src, JSValue &dst); private: ScriptEngine *m_engine; diff --git a/src/lib/corelib/language/scriptpropertyobserver.h b/src/lib/corelib/language/scriptpropertyobserver.h index 7fb362b95..80da705ee 100644 --- a/src/lib/corelib/language/scriptpropertyobserver.h +++ b/src/lib/corelib/language/scriptpropertyobserver.h @@ -40,10 +40,11 @@ #ifndef QBS_SCRIPTPROPERTYOBSERVER_H #define QBS_SCRIPTPROPERTYOBSERVER_H +#include <quickjs.h> + #include <QtCore/qglobal.h> QT_BEGIN_NAMESPACE -class QScriptValue; class QString; QT_END_NAMESPACE @@ -64,8 +65,8 @@ public: virtual ~ScriptPropertyObserver(); - virtual void onPropertyRead(const QScriptValue &object, const QString &name, - const QScriptValue &value) = 0; + virtual void onPropertyRead(const JSValue &object, const QString &name, + const JSValue &value) = 0; protected: ScriptEngine * engine() const { return m_engine; } diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp index f8cd3ad26..634f54faf 100644 --- a/src/lib/corelib/language/value.cpp +++ b/src/lib/corelib/language/value.cpp @@ -48,29 +48,65 @@ namespace qbs { namespace Internal { -Value::Value(Type t, bool createdByPropertiesBlock) - : m_type(t), m_definingItem(nullptr), m_createdByPropertiesBlock(createdByPropertiesBlock) +Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t) { + if (createdByPropertiesBlock) + m_flags |= OriginPropertiesBlock; } -Value::Value(const Value &other) +Value::Value(const Value &other, ItemPool &pool) : m_type(other.m_type), - m_definingItem(other.m_definingItem), - m_next(other.m_next ? other.m_next->clone() : ValuePtr()), - m_createdByPropertiesBlock(other.m_createdByPropertiesBlock) + m_scope(other.m_scope), + m_scopeName(other.m_scopeName), + m_next(other.m_next ? other.m_next->clone(pool) : ValuePtr()), + m_candidates(other.m_candidates), + m_flags(other.m_flags) { } Value::~Value() = default; -Item *Value::definingItem() const +void Value::setScope(Item *scope, const QString &scopeName) { - return m_definingItem; + m_scope = scope; + m_scopeName = scopeName; } -void Value::setDefiningItem(Item *item) +int Value::priority(const Item *productItem) const { - m_definingItem = item; + if (m_priority == -1) + m_priority = calculatePriority(productItem); + return m_priority; +} + +int Value::calculatePriority(const Item *productItem) const +{ + if (setInternally()) + return INT_MAX; + if (setByCommandLine()) + return INT_MAX - 1; + if (setByProfile()) + return 2; + if (!scope()) + return 1; + if (scope()->type() == ItemType::Product) + return INT_MAX - 2; + if (!scope()->isPresentModule()) + return 0; + const auto it = std::find_if( + productItem->modules().begin(), productItem->modules().end(), + [this](const Item::Module &m) { return m.item == scope(); }); + QBS_CHECK(it != productItem->modules().end()); + return INT_MAX - 3 - it->maxDependsChainLength; +} + +void Value::resetPriority() +{ + m_priority = -1; + if (m_next) + m_next->resetPriority(); + for (const ValuePtr &v : m_candidates) + v->resetPriority(); } ValuePtr Value::next() const @@ -85,6 +121,11 @@ void Value::setNext(const ValuePtr &next) m_next = next; } +bool Value::setInternally() const +{ + return type() == VariantValueType && !setByProfile() && !setByCommandLine(); +} + JSSourceValue::JSSourceValue(bool createdByPropertiesBlock) : Value(JSSourceValueType, createdByPropertiesBlock) @@ -93,31 +134,30 @@ JSSourceValue::JSSourceValue(bool createdByPropertiesBlock) { } -JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other) +JSSourceValue::JSSourceValue(const JSSourceValue &other, ItemPool &pool) : Value(other, pool) { m_sourceCode = other.m_sourceCode; m_line = other.m_line; m_column = other.m_column; m_file = other.m_file; - m_flags = other.m_flags; m_baseValue = other.m_baseValue - ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone()) + ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone(pool)) : JSSourceValuePtr(); - m_alternatives.reserve(other.m_alternatives.size()); - for (const Alternative &otherAlt : other.m_alternatives) - m_alternatives.push_back(otherAlt.clone()); + m_alternatives = transformed<std::vector<Alternative>>( + other.m_alternatives, [&pool](const auto &alternative) { + return alternative.clone(pool); }); } JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock) { - return JSSourceValuePtr(new JSSourceValue(createdByPropertiesBlock)); + return std::make_shared<JSSourceValue>(createdByPropertiesBlock); } JSSourceValue::~JSSourceValue() = default; -ValuePtr JSSourceValue::clone() const +ValuePtr JSSourceValue::clone(ItemPool &pool) const { - return JSSourceValuePtr(new JSSourceValue(*this)); + return std::make_shared<JSSourceValue>(*this, pool); } QString JSSourceValue::sourceCodeForEvaluation() const @@ -142,24 +182,45 @@ CodeLocation JSSourceValue::location() const return CodeLocation(m_file->filePath(), m_line, m_column); } -void JSSourceValue::setHasFunctionForm(bool b) +void JSSourceValue::clearAlternatives() { - if (b) - m_flags |= HasFunctionForm; - else - m_flags &= ~HasFunctionForm; + m_alternatives.clear(); } -void JSSourceValue::clearAlternatives() +void JSSourceValue::setScope(Item *scope, const QString &scopeName) { - m_alternatives.clear(); + Value::setScope(scope, scopeName); + if (m_baseValue) + m_baseValue->setScope(scope, scopeName); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->setScope(scope, scopeName); +} + +void JSSourceValue::resetPriority() +{ + Value::resetPriority(); + if (m_baseValue) + m_baseValue->resetPriority(); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->resetPriority(); +} + +void JSSourceValue::addCandidate(const ValuePtr &v) +{ + Value::addCandidate(v); + if (m_baseValue) + m_baseValue->addCandidate(v); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->addCandidate(v); } -void JSSourceValue::setDefiningItem(Item *item) +void JSSourceValue::setCandidates(const std::vector<ValuePtr> &candidates) { - Value::setDefiningItem(item); + Value::setCandidates(candidates); + if (m_baseValue) + m_baseValue->setCandidates(candidates); for (const JSSourceValue::Alternative &a : m_alternatives) - a.value->setDefiningItem(item); + a.value->setCandidates(candidates); } ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock) @@ -171,49 +232,71 @@ ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock) ItemValuePtr ItemValue::create(Item *item, bool createdByPropertiesBlock) { - return ItemValuePtr(new ItemValue(item, createdByPropertiesBlock)); + return std::make_shared<ItemValue>(item, createdByPropertiesBlock); } -ValuePtr ItemValue::clone() const +ValuePtr ItemValue::clone(ItemPool &pool) const { - return create(m_item->clone(), createdByPropertiesBlock()); + return create(m_item->clone(pool), createdByPropertiesBlock()); } +class StoredVariantValue : public VariantValue +{ +public: + explicit StoredVariantValue(QVariant v) : VariantValue(std::move(v)) {} + + quintptr id() const override { return quintptr(this); } +}; + VariantValue::VariantValue(QVariant v) : Value(VariantValueType, false) , m_value(std::move(v)) { } -VariantValuePtr VariantValue::create(const QVariant &v) +VariantValue::VariantValue(const VariantValue &other, ItemPool &pool) + : Value(other, pool), m_value(other.m_value) {} + +template<typename T> +VariantValuePtr createImpl(const QVariant &v) { if (!v.isValid()) - return invalidValue(); - if (static_cast<QMetaType::Type>(v.type()) == QMetaType::Bool) + return VariantValue::invalidValue(); + if (static_cast<QMetaType::Type>(v.userType()) == QMetaType::Bool) return v.toBool() ? VariantValue::trueValue() : VariantValue::falseValue(); - return VariantValuePtr(new VariantValue(v)); + return std::make_shared<T>(v); +} + +VariantValuePtr VariantValue::create(const QVariant &v) +{ + return createImpl<VariantValue>(v); +} + +VariantValuePtr VariantValue::createStored(const QVariant &v) +{ + return createImpl<StoredVariantValue>(v); } -ValuePtr VariantValue::clone() const +ValuePtr VariantValue::clone(ItemPool &pool) const { - return VariantValuePtr(new VariantValue(*this)); + return std::make_shared<VariantValue>(*this, pool); } const VariantValuePtr &VariantValue::falseValue() { - static const VariantValuePtr v = VariantValuePtr(new VariantValue(false)); + static const VariantValuePtr v = std::make_shared<VariantValue>(false); return v; } const VariantValuePtr &VariantValue::trueValue() { - static const VariantValuePtr v = VariantValuePtr(new VariantValue(true)); + static const VariantValuePtr v = std::make_shared<VariantValue>(true); return v; } const VariantValuePtr &VariantValue::invalidValue() { - static const VariantValuePtr v = VariantValuePtr(new VariantValue(QVariant())); + static const VariantValuePtr v = std::make_shared<VariantValue>(QVariant()); return v; } diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h index a48e974d3..1a6746e24 100644 --- a/src/lib/corelib/language/value.h +++ b/src/lib/corelib/language/value.h @@ -42,6 +42,7 @@ #include "forward_decls.h" #include <tools/codelocation.h> +#include <QtCore/qstring.h> #include <QtCore/qvariant.h> #include <vector> @@ -49,42 +50,86 @@ namespace qbs { namespace Internal { class Item; +class ItemPool; class ValueHandler; class Value { public: - enum Type - { + enum Type { JSSourceValueType, ItemValueType, VariantValueType }; + enum Flag { + NoFlags = 0x00, + SourceUsesBase = 0x01, + SourceUsesOuter = 0x02, + SourceUsesOriginal = 0x04, + HasFunctionForm = 0x08, + ExclusiveListValue = 0x10, + BuiltinDefaultValue = 0x20, + OriginPropertiesBlock = 0x40, + OriginProfile = 0x80, + OriginCommandLine = 0x100, + }; + Q_DECLARE_FLAGS(Flags, Flag) + Value(Type t, bool createdByPropertiesBlock); - Value(const Value &other); + Value(const Value &other) = delete; + Value(const Value &other, ItemPool &pool); virtual ~Value(); Type type() const { return m_type; } virtual void apply(ValueHandler *) = 0; - virtual ValuePtr clone() const = 0; + virtual ValuePtr clone(ItemPool &) const = 0; virtual CodeLocation location() const { return {}; } - Item *definingItem() const; - virtual void setDefiningItem(Item *item); + Item *scope() const { return m_scope; } + virtual void setScope(Item *scope, const QString &scopeName); + QString scopeName() const { return m_scopeName; } + int priority(const Item *productItem) const; + virtual void resetPriority(); ValuePtr next() const; void setNext(const ValuePtr &next); - bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; } - void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; } - void clearCreatedByPropertiesBlock() { m_createdByPropertiesBlock = false; } + virtual void addCandidate(const ValuePtr &v) { m_candidates.push_back(v); } + const std::vector<ValuePtr> &candidates() const { return m_candidates; } + virtual void setCandidates(const std::vector<ValuePtr> &candidates) { m_candidates = candidates; } + + bool createdByPropertiesBlock() const { return m_flags & OriginPropertiesBlock; } + void markAsSetByProfile() { m_flags |= OriginProfile; } + bool setByProfile() const { return m_flags & OriginProfile; } + void markAsSetByCommandLine() { m_flags |= OriginCommandLine; } + bool setByCommandLine() const { return m_flags & OriginCommandLine; } + bool setInternally() const; + bool expired(const Item *productItem) const { return priority(productItem) == 0; } + + void setSourceUsesBase() { m_flags |= SourceUsesBase; } + bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); } + void setSourceUsesOuter() { m_flags |= SourceUsesOuter; } + bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); } + void setSourceUsesOriginal() { m_flags |= SourceUsesOriginal; } + bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); } + void setHasFunctionForm() { m_flags |= HasFunctionForm; } + bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); } + void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; } + bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); } + void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; } + bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); } private: + int calculatePriority(const Item *productItem) const; + Type m_type; - Item *m_definingItem; + Item *m_scope = nullptr; + QString m_scopeName; ValuePtr m_next; - bool m_createdByPropertiesBlock; + std::vector<ValuePtr> m_candidates; + Flags m_flags; + mutable int m_priority = -1; }; class ValueHandler @@ -98,30 +143,19 @@ public: class JSSourceValue : public Value { friend class ItemReaderASTVisitor; - JSSourceValue(bool createdByPropertiesBlock); - JSSourceValue(const JSSourceValue &other); - - enum Flag - { - NoFlags = 0x00, - SourceUsesBase = 0x01, - SourceUsesOuter = 0x02, - SourceUsesOriginal = 0x04, - HasFunctionForm = 0x08, - ExclusiveListValue = 0x10, - BuiltinDefaultValue = 0x20, - }; - Q_DECLARE_FLAGS(Flags, Flag) public: + explicit JSSourceValue(bool createdByPropertiesBlock); + JSSourceValue(const JSSourceValue &other, ItemPool &pool); + static JSSourceValuePtr QBS_AUTOTEST_EXPORT create(bool createdByPropertiesBlock = false); ~JSSourceValue() override; void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; - void setSourceCode(const QStringRef &sourceCode) { m_sourceCode = sourceCode; } - const QStringRef &sourceCode() const { return m_sourceCode; } + void setSourceCode(QStringView sourceCode) { m_sourceCode = sourceCode; } + QStringView sourceCode() const { return m_sourceCode; } QString sourceCodeForEvaluation() const; void setLocation(int line, int column); @@ -132,17 +166,6 @@ public: void setFile(const FileContextPtr &file) { m_file = file; } const FileContextPtr &file() const { return m_file; } - void setSourceUsesBaseFlag() { m_flags |= SourceUsesBase; } - bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); } - bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); } - bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); } - bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); } - void setHasFunctionForm(bool b); - void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; } - bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); } - void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; } - bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); } - const JSSourceValuePtr &baseValue() const { return m_baseValue; } void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; } @@ -151,18 +174,18 @@ public: struct PropertyData { PropertyData() = default; - PropertyData(const QString &v, const CodeLocation &l) : value(v), location(l) {} + PropertyData(QString v, const CodeLocation &l) : value(std::move(v)), location(l) {} QString value; CodeLocation location; }; Alternative() = default; - Alternative(const PropertyData &c, const PropertyData &o, const JSSourceValuePtr &v) - : condition(c), overrideListProperties(o), value(v) {} - Alternative clone() const + Alternative(PropertyData c, PropertyData o, JSSourceValuePtr v) + : condition(std::move(c)), overrideListProperties(std::move(o)), value(std::move(v)) {} + Alternative clone(ItemPool &pool) const { return Alternative(condition, overrideListProperties, - std::static_pointer_cast<JSSourceValue>(value->clone())); + std::static_pointer_cast<JSSourceValue>(value->clone(pool))); } PropertyData condition; @@ -175,14 +198,16 @@ public: void addAlternative(const Alternative &alternative) { m_alternatives.push_back(alternative); } void clearAlternatives(); - void setDefiningItem(Item *item) override; + void setScope(Item *scope, const QString &scopeName) override; + void resetPriority() override; + void addCandidate(const ValuePtr &v) override; + void setCandidates(const std::vector<ValuePtr> &candidates) override; private: - QStringRef m_sourceCode; + QStringView m_sourceCode; int m_line; int m_column; FileContextPtr m_file; - Flags m_flags; JSSourceValuePtr m_baseValue; std::vector<Alternative> m_alternatives; }; @@ -190,8 +215,8 @@ private: class ItemValue : public Value { - ItemValue(Item *item, bool createdByPropertiesBlock); public: + ItemValue(Item *item, bool createdByPropertiesBlock); static ItemValuePtr create(Item *item, bool createdByPropertiesBlock = false); Item *item() const { return m_item; } @@ -199,7 +224,7 @@ public: private: void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; Item *m_item; }; @@ -207,14 +232,17 @@ private: class VariantValue : public Value { - VariantValue(QVariant v); public: + explicit VariantValue(QVariant v); + VariantValue(const VariantValue &v, ItemPool &pool); static VariantValuePtr create(const QVariant &v = QVariant()); + static VariantValuePtr createStored(const QVariant &v = QVariant()); void apply(ValueHandler *handler) override { handler->handle(this); } - ValuePtr clone() const override; + ValuePtr clone(ItemPool &pool) const override; const QVariant &value() const { return m_value; } + virtual quintptr id() const { return 0; } static const VariantValuePtr &falseValue(); static const VariantValuePtr &trueValue(); diff --git a/src/lib/corelib/language/astimportshandler.cpp b/src/lib/corelib/loader/astimportshandler.cpp index d634af7e4..c0281ca24 100644 --- a/src/lib/corelib/language/astimportshandler.cpp +++ b/src/lib/corelib/loader/astimportshandler.cpp @@ -38,12 +38,12 @@ ****************************************************************************/ #include "astimportshandler.h" -#include "asttools.h" -#include "builtindeclarations.h" -#include "filecontext.h" #include "itemreadervisitorstate.h" -#include "jsextensions/jsextensions.h" +#include <jsextensions/jsextensions.h> +#include <language/asttools.h> +#include <language/builtindeclarations.h> +#include <language/filecontext.h> #include <logging/logger.h> #include <logging/translator.h> #include <parser/qmljsast_p.h> @@ -268,21 +268,9 @@ void ASTImportsHandler::checkImportVersion(const QbsQmlJS::AST::SourceLocation & void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as) { QStringList fileNames; // Yes, file *names*. - if (m_visitorState.findDirectoryEntries(path, &fileNames)) { - for (const QString &fileName : qAsConst(fileNames)) - addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); - return; - } - - QDirIterator dirIter(path, StringConstants::qbsFileWildcards()); - while (dirIter.hasNext()) { - const QString filePath = dirIter.next(); - const QString fileName = dirIter.fileName(); - if (addPrototype(fileName, filePath, as, true)) - fileNames << fileName; - } - m_visitorState.cacheDirectoryEntries(path, fileNames); - + m_visitorState.findDirectoryEntries(path, &fileNames); + for (const QString &fileName : std::as_const(fileNames)) + addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); } void ASTImportsHandler::collectPrototypesAndJsCollections(const QString &path, const QString &as, diff --git a/src/lib/corelib/language/astimportshandler.h b/src/lib/corelib/loader/astimportshandler.h index e9c2b6c27..582e1c698 100644 --- a/src/lib/corelib/language/astimportshandler.h +++ b/src/lib/corelib/loader/astimportshandler.h @@ -39,7 +39,7 @@ #ifndef QBS_ASTIMPORTSHANDLER_H #define QBS_ASTIMPORTSHANDLER_H -#include "forward_decls.h" +#include <language/forward_decls.h> #include <parser/qmljsastfwd_p.h> #include <tools/set.h> diff --git a/src/lib/corelib/language/astpropertiesitemhandler.cpp b/src/lib/corelib/loader/astpropertiesitemhandler.cpp index 1ea78bf79..8183e6b79 100644 --- a/src/lib/corelib/language/astpropertiesitemhandler.cpp +++ b/src/lib/corelib/loader/astpropertiesitemhandler.cpp @@ -38,8 +38,8 @@ ****************************************************************************/ #include "astpropertiesitemhandler.h" -#include "item.h" -#include "value.h" +#include <language/item.h> +#include <language/value.h> #include <logging/translator.h> #include <tools/error.h> @@ -49,7 +49,8 @@ namespace qbs { namespace Internal { -ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem) : m_parentItem(parentItem) +ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem, ItemPool &itemPool) + : m_parentItem(parentItem), m_itemPool(itemPool) { } @@ -82,9 +83,11 @@ class PropertiesBlockConverter public: PropertiesBlockConverter(const JSSourceValue::AltProperty &condition, const JSSourceValue::AltProperty &overrideListProperties, - Item *propertiesBlockContainer, const Item *propertiesBlock) + Item *propertiesBlockContainer, const Item *propertiesBlock, + ItemPool &pool) : m_propertiesBlockContainer(propertiesBlockContainer) , m_propertiesBlock(propertiesBlock) + , m_itemPool(pool) { m_alternative.condition = condition; m_alternative.overrideListProperties = overrideListProperties; @@ -99,6 +102,7 @@ private: JSSourceValue::Alternative m_alternative; Item * const m_propertiesBlockContainer; const Item * const m_propertiesBlock; + ItemPool &m_itemPool; void doApply(Item *outer, const Item *inner) { @@ -111,9 +115,9 @@ private: } if (it.value()->type() == Value::ItemValueType) { Item * const innerVal = std::static_pointer_cast<ItemValue>(it.value())->item(); - ItemValuePtr outerVal = outer->itemProperty(it.key()); + ItemValuePtr outerVal = outer->itemProperty(it.key(), m_itemPool); if (!outerVal) { - outerVal = ItemValue::create(Item::create(outer->pool(), innerVal->type()), + outerVal = ItemValue::create(Item::create(&m_itemPool, innerVal->type()), true); outer->setProperty(it.key(), outerVal); } @@ -139,8 +143,8 @@ private: value = JSSourceValue::create(true); value->setFile(conditionalValue->file()); item->setProperty(propertyName, value); - value->setSourceCode(QStringRef(&StringConstants::baseVar())); - value->setSourceUsesBaseFlag(); + value->setSourceCode(StringConstants::baseVar()); + value->setSourceUsesBase(); } m_alternative.value = conditionalValue; value->addAlternative(m_alternative); @@ -155,8 +159,7 @@ static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, co throw ErrorInfo(Tr::tr("Properties.condition must be provided."), propertiesItem->location()); } - return JSSourceValue::AltProperty(StringConstants::falseValue(), - propertiesItem->location()); + return {StringConstants::falseValue(), propertiesItem->location()}; } if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) { throw ErrorInfo(Tr::tr("Properties.%1 must be a value binding.").arg(name), @@ -176,7 +179,7 @@ static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, co } const JSSourceValuePtr srcval = std::static_pointer_cast<JSSourceValue>(value); - return JSSourceValue::AltProperty(srcval->sourceCodeForEvaluation(), srcval->location()); + return {srcval->sourceCodeForEvaluation(), srcval->location()}; } void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem) @@ -185,7 +188,7 @@ void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem) const auto overrideListProperties = getPropertyData(propertiesItem, StringConstants::overrideListPropertiesProperty()); PropertiesBlockConverter(condition, overrideListProperties, m_parentItem, - propertiesItem).apply(); + propertiesItem, m_itemPool).apply(); } } // namespace Internal diff --git a/src/lib/corelib/language/astpropertiesitemhandler.h b/src/lib/corelib/loader/astpropertiesitemhandler.h index 413512ee5..804abb8a1 100644 --- a/src/lib/corelib/language/astpropertiesitemhandler.h +++ b/src/lib/corelib/loader/astpropertiesitemhandler.h @@ -42,11 +42,12 @@ namespace qbs { namespace Internal { class Item; +class ItemPool; class ASTPropertiesItemHandler { public: - ASTPropertiesItemHandler(Item *parentItem); + ASTPropertiesItemHandler(Item *parentItem, ItemPool &itemPool); void handlePropertiesItems(); @@ -55,6 +56,7 @@ private: void handlePropertiesBlock(const Item *propertiesItem); Item * const m_parentItem; + ItemPool &m_itemPool; }; } // namespace Internal diff --git a/src/lib/corelib/loader/dependenciesresolver.cpp b/src/lib/corelib/loader/dependenciesresolver.cpp new file mode 100644 index 000000000..059592e9c --- /dev/null +++ b/src/lib/corelib/loader/dependenciesresolver.cpp @@ -0,0 +1,1175 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "dependenciesresolver.h" + +#include "itemreader.h" +#include "loaderutils.h" +#include "moduleinstantiator.h" +#include "moduleloader.h" + +#include <language/scriptengine.h> +#include <language/evaluator.h> +#include <language/item.h> +#include <language/itempool.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/preferences.h> +#include <tools/profiling.h> +#include <tools/settings.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <optional> +#include <queue> +#include <unordered_map> + +namespace qbs::Internal { +namespace { +enum class HandleDependency { Use, Ignore, Defer }; + +class LoadModuleResult +{ +public: + Item *moduleItem = nullptr; + ProductContext *product = nullptr; + HandleDependency handleDependency = HandleDependency::Use; +}; + +// Corresponds completely to a Depends item. +// May result in more than one module, due to "multiplexing" properties such as subModules etc. +// May also result in no module at all, e.g. if productTypes does not match anything. +class EvaluatedDependsItem +{ +public: + Item *item = nullptr; + QualifiedId name; + QStringList subModules; + FileTags productTypes; + QStringList multiplexIds; + std::optional<QStringList> profiles; + VersionRange versionRange; + QVariantMap parameters; + bool limitToSubProject = false; + bool requiredLocally = true; + bool requiredGlobally = true; +}; + +// As opposed to EvaluatedDependsItem, one of these corresponds exactly to one module +// to be loaded. Such an attempt might still fail, though, which may or may not result +// in an error, depending on the value of Depends.required and other circumstances. +class FullyResolvedDependsItem +{ +public: + FullyResolvedDependsItem(ProductContext *product, const EvaluatedDependsItem &dependency); + FullyResolvedDependsItem(const EvaluatedDependsItem &dependency, QualifiedId name, + QString profile, QString multiplexId); + FullyResolvedDependsItem() = default; + static FullyResolvedDependsItem makeBaseDependency(); + + QString id() const; + CodeLocation location() const; + QString displayName() const; + + // If product is non-null, we already know which product the dependency targets. + // This happens either if Depends.productTypes was set, or if we tried to load the + // dependency before and already identified the product, but could not complete the + // procedure because said product had itself not been handled yet. + ProductContext *product = nullptr; + + Item *item = nullptr; + QualifiedId name; + QString profile; + QString multiplexId; + VersionRange versionRange; + QVariantMap parameters; + bool limitToSubProject = false; + bool requiredLocally = true; + bool requiredGlobally = true; + bool checkProduct = true; +}; + +class DependenciesResolvingState +{ +public: + Item *loadingItem = nullptr; + FullyResolvedDependsItem loadingItemOrigin; + std::queue<Item *> pendingDependsItems; + std::optional<EvaluatedDependsItem> currentDependsItem; + std::queue<FullyResolvedDependsItem> pendingResolvedDependencies; + bool requiredByLoadingItem = true; +}; + +class DependenciesContextImpl : public DependenciesContext +{ +public: + DependenciesContextImpl(ProductContext &product, LoaderState &loaderState); + + std::list<DependenciesResolvingState> stateStack; + +private: + std::pair<ProductDependency, ProductContext *> pendingDependency() const override; + + void setSearchPathsForProduct(LoaderState &loaderState); + + ProductContext &m_product; +}; + +class DependenciesResolver +{ +public: + DependenciesResolver(LoaderState &loaderState, ProductContext &product, Deferral deferral) + : m_loaderState(loaderState), m_product(product), m_deferral(deferral) {} + + void resolve(); + LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency); + +private: + void evaluateNextDependsItem(); + HandleDependency handleResolvedDependencies(); + std::pair<Item::Module *, Item *> findExistingModule(const FullyResolvedDependsItem &dependency, + Item *item); + void updateModule(Item::Module &module, const FullyResolvedDependsItem &dependency); + int dependsChainLength(); + ProductContext *findMatchingProduct(const FullyResolvedDependsItem &dependency); + Item *findMatchingModule(const FullyResolvedDependsItem &dependency); + std::pair<bool, HandleDependency> checkProductDependency( + const FullyResolvedDependsItem &depSpec, const ProductContext &dep); + void checkModule(const FullyResolvedDependsItem &dependency, Item *moduleItem, + ProductContext *productDep); + void checkForModuleNamePrefixCollision(const FullyResolvedDependsItem &dependency); + Item::Module createModule(const FullyResolvedDependsItem &dependency, Item *item, + ProductContext *productDep); + void adjustDependsItemForMultiplexing(Item *dependsItem); + std::optional<EvaluatedDependsItem> evaluateDependsItem(Item *item); + std::queue<FullyResolvedDependsItem> multiplexDependency( + const EvaluatedDependsItem &dependency); + QVariantMap extractParameters(Item *dependsItem) const; + void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules); + void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, + const Item::Modules &modules); + std::list<DependenciesResolvingState> &stateStack(); + + LoaderState &m_loaderState; + ProductContext &m_product; + Deferral m_deferral; +}; + +static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2); +static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v); + +} // namespace + +void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState) +{ + DependenciesResolver(loaderState, product, deferral).resolve(); +} + +Item *loadBaseModule(ProductContext &product, Item *item, LoaderState &loaderState) +{ + const auto baseDependency = FullyResolvedDependsItem::makeBaseDependency(); + Item * const moduleItem = DependenciesResolver(loaderState, product, Deferral::NotAllowed) + .loadModule(item, baseDependency).moduleItem; + if (Q_UNLIKELY(!moduleItem)) + throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); + return moduleItem; +} + +namespace { + +void DependenciesResolver::resolve() +{ + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.dependenciesResolving : nullptr); + + if (!m_product.dependenciesContext) { + m_product.dependenciesContext = std::make_unique<DependenciesContextImpl>( + m_product, m_loaderState); + } else { + QBS_CHECK(!m_product.dependenciesContext->dependenciesResolved); + } + SearchPathsManager searchPathsMgr(m_loaderState.itemReader(), m_product.searchPaths); + + while (!stateStack().empty()) { + auto &state = stateStack().front(); + + // If we have pending FullyResolvedDependsItems, then these are handled first. + if (handleResolvedDependencies() == HandleDependency::Defer) + return; + + // The above procedure might have pushed another state to the stack due to recursive + // dependencies (i.e. Depends items in the newly loaded module), in which case we + // continue with that one. + if (&state != &stateStack().front()) + continue; + + // If we have a pending EvaluatedDependsItem, we multiplex it and then handle + // the resulting FullyResolvedDependsItems, if there were any. + if (state.currentDependsItem) { + QBS_CHECK(state.pendingResolvedDependencies.empty()); + + // We postpone handling Depends.productTypes for as long as possible, because + // the full type of a product becomes available only after its modules have been loaded. + if (!state.currentDependsItem->productTypes.empty() && m_deferral == Deferral::Allowed) + return; + + state.pendingResolvedDependencies = multiplexDependency(*state.currentDependsItem); + state.currentDependsItem.reset(); + m_deferral = Deferral::Allowed; // We made progress. + + continue; + } + + // Here we have no resolved/evaluated Depends items of any kind, so we evaluate the next + // pending Depends item. + evaluateNextDependsItem(); + if (state.currentDependsItem) + continue; + + // No resolved or unresolved Depends items are left, so we're done with the current state. + QBS_CHECK(!state.currentDependsItem); + QBS_CHECK(state.pendingResolvedDependencies.empty()); + QBS_CHECK(state.pendingDependsItems.empty()); + + // This ensures our invariant: A sorted module list in the product + // (dependers after dependencies). + if (stateStack().size() > 1) { + QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance); + Item::Modules &modules = m_product.item->modules(); + const auto loadingItemModule = std::find_if(modules.begin(), modules.end(), + [&](const Item::Module &m) { + return m.item == state.loadingItem; + }); + QBS_CHECK(loadingItemModule != modules.end()); + const Item::Module tempModule = *loadingItemModule; + modules.erase(loadingItemModule); + modules.push_back(tempModule); + } + stateStack().pop_front(); + } + m_product.dependenciesContext->dependenciesResolved = true; +} + +void DependenciesResolver::evaluateNextDependsItem() +{ + auto &state = stateStack().front(); + while (!state.pendingDependsItems.empty()) { + QBS_CHECK(!state.currentDependsItem); + QBS_CHECK(state.pendingResolvedDependencies.empty()); + Item * const dependsItem = state.pendingDependsItems.front(); + state.pendingDependsItems.pop(); + adjustDependsItemForMultiplexing(dependsItem); + if (auto evaluated = evaluateDependsItem(dependsItem)) { + evaluated->requiredGlobally = evaluated->requiredLocally + && state.loadingItemOrigin.requiredGlobally; + state.currentDependsItem = evaluated; + return; + } + } +} + +HandleDependency DependenciesResolver::handleResolvedDependencies() +{ + DependenciesResolvingState &state = stateStack().front(); + while (!state.pendingResolvedDependencies.empty()) { + QBS_CHECK(!state.currentDependsItem); + const FullyResolvedDependsItem dependency = state.pendingResolvedDependencies.front(); + try { + const LoadModuleResult res = loadModule(state.loadingItem, dependency); + switch (res.handleDependency) { + case HandleDependency::Defer: + QBS_CHECK(m_deferral == Deferral::Allowed); + + // Optimization: We already looked up the product, so let's not do that again + // next time. + if (res.product) + state.pendingResolvedDependencies.front().product = res.product; + + return HandleDependency::Defer; + case HandleDependency::Ignore: + // This happens if the dependency was not required or the module was already + // loaded via another path. + state.pendingResolvedDependencies.pop(); + continue; + case HandleDependency::Use: + if (dependency.name.toString() == StringConstants::qbsModule()) { + // Shortcut: No need to look for recursive dependencies in the base module. + state.pendingResolvedDependencies.pop(); + continue; + } + m_deferral = Deferral::Allowed; // We made progress. + break; + } + + QBS_CHECK(res.moduleItem); + + // Now continue with the dependencies of the just-loaded module. + std::queue<Item *> moduleDependsItems; + for (Item * const child : res.moduleItem->children()) { + if (child->type() == ItemType::Depends) + moduleDependsItems.push(child); + } + state.pendingResolvedDependencies.pop(); + stateStack().push_front( + {res.moduleItem, dependency, moduleDependsItems, {}, {}, + dependency.requiredGlobally || state.requiredByLoadingItem}); + stateStack().front().pendingResolvedDependencies.push( + FullyResolvedDependsItem::makeBaseDependency()); + break; + } catch (const ErrorInfo &e) { + if (dependency.name.toString() == StringConstants::qbsModule()) + throw e; + + // See QBS-1338 for why we do not abort handling the product. + state.pendingResolvedDependencies.pop(); + Item::Modules &modules = m_product.item->modules(); + + // Unwind. + while (stateStack().size() > 1) { + const auto loadingItemModule = std::find_if( + modules.begin(), modules.end(), [&](const Item::Module &m) { + return m.item == stateStack().front().loadingItem; + }); + for (auto it = loadingItemModule; it != modules.end(); ++it) { + createNonPresentModule(m_loaderState.itemPool(), it->name.toString(), + QLatin1String("error in Depends chain"), it->item); + } + modules.erase(loadingItemModule, modules.end()); + stateStack().pop_front(); + } + + m_product.handleError(e); + return HandleDependency::Ignore; + } + } + return HandleDependency::Ignore; +} + +// Produces an item of type ModuleInstance corresponding to the specified dependency. +// The instance's prototype item is either of type Export (if the dependency is a product) +// or of type Module (for an actual module). +// The loadingItem parameter is either the depending product or another module. The newly +// created module is added to the module list of the product item and additionally to the +// loading item's one, if it is not the product. Its name is also injected into the respective +// scopes. +LoadModuleResult DependenciesResolver::loadModule( + Item *loadingItem, const FullyResolvedDependsItem &dependency) +{ + qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString() + << "id:" << dependency.id(); + + QBS_CHECK(loadingItem); + + ProductContext *productDep = nullptr; + Item *moduleItem = nullptr; + + const auto addLoadContext = [&](Item::Module &module) { + module.loadContexts.emplace_back(dependency.item, + std::make_pair(dependency.parameters, + INT_MAX - dependsChainLength())); + }; + + // The module might already have been loaded for this product (directly or indirectly). + const auto &[existingModule, moduleWithSameName] + = findExistingModule(dependency, m_product.item); + if (existingModule) { + // Merge version range and required property. These will be checked again + // after probes resolving. + if (existingModule->name.toString() != StringConstants::qbsModule()) + updateModule(*existingModule, dependency); + + QBS_CHECK(existingModule->item); + moduleItem = existingModule->item; + const auto matcher = [loadingItem](const Item::Module::LoadContext &context) { + return context.loadingItem() == loadingItem; + }; + const auto it = std::find_if(existingModule->loadContexts.begin(), + existingModule->loadContexts.end(), matcher); + if (it == existingModule->loadContexts.end()) + addLoadContext(*existingModule); + else + it->parameters.first = mergeDependencyParameters(it->parameters.first, + dependency.parameters); + } else if (dependency.product) { + productDep = dependency.product; // We have already done the look-up. + } else if (productDep = findMatchingProduct(dependency); !productDep) { + moduleItem = findMatchingModule(dependency); + } + + if (productDep) { + const auto checkResult = checkProductDependency(dependency, *productDep); + + // We found a product dependency, but that product has not been handled yet, + // so stop dependency resolving for the current product and resume it later, when the + // dependency is ready. + if (checkResult.second == HandleDependency::Defer) + return {nullptr, productDep, HandleDependency::Defer}; + + if (checkResult.first) { + QBS_CHECK(productDep->mergedExportItem); + moduleItem = productDep->mergedExportItem->clone(m_loaderState.itemPool()); + moduleItem->setParent(nullptr); + + // Needed for isolated Export item evaluation. + moduleItem->setPrototype(productDep->mergedExportItem); + } else { + // The product dependency is faulty, but Depends.reqired was false. + productDep = nullptr; + } + } + + if (moduleItem) + checkModule(dependency, moduleItem, productDep); + + // Can only happen with multiplexing. + if (moduleWithSameName && moduleWithSameName != moduleItem) + QBS_CHECK(productDep); + + // The loading name is only used to ensure consistent sorting in case of equal + // value priorities; see ModulePropertyMerger. + QString loadingName; + if (loadingItem == m_product.item) { + loadingName = m_product.name; + } else if (m_product.dependenciesContext && !stateStack().empty()) { + const auto &loadingItemOrigin = stateStack().front().loadingItemOrigin; + loadingName = loadingItemOrigin.name.toString() + loadingItemOrigin.multiplexId + + loadingItemOrigin.profile; + } + instantiateModule({m_product, loadingItem, loadingName, moduleItem, moduleWithSameName, + productDep ? productDep->item : nullptr, dependency.name, dependency.id(), + bool(existingModule)}, m_loaderState); + + // At this point, a null module item is only possible for a non-required dependency. + // Note that we still needed to to the instantiation above, as that injects the module + // name into the surrounding item for the ".present" check. + if (!moduleItem) { + QBS_CHECK(!dependency.requiredGlobally); + return {nullptr, nullptr, HandleDependency::Ignore}; + } + + const auto addLocalModule = [&] { + if (loadingItem->type() == ItemType::ModuleInstance + && !findExistingModule(dependency, loadingItem).first) { + loadingItem->addModule(createModule(dependency, moduleItem, productDep)); + } + }; + + // The module has already been loaded, so we don't need to add it to the product's list of + // modules, nor do we need to handle its dependencies. The only thing we might need to + // do is to add it to the "local" list of modules of the loading item, if it isn't in there yet. + if (existingModule) { + addLocalModule(); + return {nullptr, nullptr, HandleDependency::Ignore}; + } + + qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString(); + if (m_product.item) { + Item::Module module = createModule(dependency, moduleItem, productDep); + module.required = dependency.requiredGlobally; + addLoadContext(module); + module.maxDependsChainLength = dependsChainLength(); + m_product.item->addModule(module); + addLocalModule(); + } + return {moduleItem, nullptr, HandleDependency::Use}; +} + +std::pair<Item::Module *, Item *> DependenciesResolver::findExistingModule( + const FullyResolvedDependsItem &dependency, Item *item) +{ + if (!item) // Happens if and only if called via loadBaseModule(). + return {}; + Item *moduleWithSameName = nullptr; + for (Item::Module &m : item->modules()) { + if (m.name != dependency.name) + continue; + if (!m.product) { + QBS_CHECK(!dependency.product); + return {&m, m.item}; + } + if ((dependency.profile.isEmpty() || (m.product->profileName == dependency.profile)) + && m.product->multiplexConfigurationId == dependency.multiplexId) { + return {&m, m.item}; + } + + // This can happen if there are dependencies to several variants of a multiplexed + // product. + moduleWithSameName = m.item; + } + return {nullptr, moduleWithSameName}; +} + +void DependenciesResolver::updateModule( + Item::Module &module, const FullyResolvedDependsItem &dependency) +{ + forwardParameterDeclarations(dependency.item, m_product.item->modules()); + module.versionRange.narrowDown(dependency.versionRange); + module.required |= dependency.requiredGlobally; + if (dependsChainLength() > module.maxDependsChainLength) + module.maxDependsChainLength = dependsChainLength(); +} + +int DependenciesResolver::dependsChainLength() +{ + return m_product.dependenciesContext ? stateStack().size() : 1; +} + +ProductContext *DependenciesResolver::findMatchingProduct( + const FullyResolvedDependsItem &dependency) +{ + const auto constraint = [this, &dependency](ProductContext &product) { + if (product.multiplexConfigurationId != dependency.multiplexId) + return false; + if (!dependency.profile.isEmpty() && dependency.profile != product.profileName) + return false; + if (dependency.limitToSubProject && !haveSameSubProject(m_product, product)) + return false; + return true; + + }; + return m_product.project->topLevelProject->productWithNameAndConstraint( + dependency.name.toString(), constraint); +} + +Item *DependenciesResolver::findMatchingModule( + const FullyResolvedDependsItem &dependency) +{ + // If we can tell that this is supposed to be a product dependency, we can skip + // the module look-up. + if (!dependency.profile.isEmpty() || !dependency.multiplexId.isEmpty()) { + if (dependency.requiredGlobally) { + if (!dependency.profile.isEmpty()) { + throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist " + "for the requested profile '%3'.") + .arg(m_product.displayName(), dependency.displayName(), + dependency.profile), + m_product.item->location()); + } + throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.") + .arg(m_product.displayName(), dependency.displayName()), + m_product.item->location()); + } + return nullptr; + } + + if (Item *moduleItem = searchAndLoadModuleFile( + m_loaderState, m_product, dependency.location(), dependency.name)) { + QBS_CHECK(moduleItem->type() == ItemType::Module); + Item * const proto = moduleItem; + ModuleItemLocker locker(*moduleItem); + moduleItem = moduleItem->clone(m_loaderState.itemPool()); + moduleItem->setPrototype(proto); // For parameter declarations. + return moduleItem; + } + + if (!dependency.requiredGlobally) { + return createNonPresentModule(m_loaderState.itemPool(), dependency.name.toString(), + QStringLiteral("not found"), nullptr); + } + + throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.") + .arg(dependency.name.toString(), m_product.displayName()), + dependency.location()); +} + +std::pair<bool, HandleDependency> DependenciesResolver::checkProductDependency( + const FullyResolvedDependsItem &depSpec, const ProductContext &dep) +{ + // Optimization: If we already checked the product earlier and then deferred, we don't + // need to check it again. + if (!depSpec.checkProduct) + return {true, HandleDependency::Use}; + + if (&dep == &m_product) { + throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.").arg(depSpec.name.toString()), + depSpec.location()); + } + + if (m_product.project->topLevelProject->isProductQueuedForHandling(dep)) { + if (m_deferral == Deferral::Allowed) + return {false, HandleDependency::Defer}; + ErrorInfo e; + e.append(Tr::tr("Cyclic dependencies detected:")); + e.append(Tr::tr("First product is '%1'.") + .arg(m_product.displayName()), m_product.item->location()); + e.append(Tr::tr("Second product is '%1'.") + .arg(dep.displayName()), dep.item->location()); + e.append(Tr::tr("Requested here."), depSpec.location()); + throw e; + } + + // This covers both the case of user-disabled products and products with errors. + // The latter are force-disabled in ProductContext::handleError(). + if (m_product.project->topLevelProject->isDisabledItem(dep.item)) { + if (depSpec.requiredGlobally) { + ErrorInfo e; + e.append(Tr::tr("Product '%1' depends on '%2',") + .arg(m_product.displayName(), dep.displayName()), + m_product.item->location()); + e.append(Tr::tr("but product '%1' is disabled.").arg(dep.displayName()), + dep.item->location()); + throw e; + } + return {false, HandleDependency::Ignore}; + } + return {true, HandleDependency::Use}; +} + +void DependenciesResolver::checkModule( + const FullyResolvedDependsItem &dependency, Item *moduleItem, ProductContext *productDep) +{ + // When loading a pseudo or temporary qbs module in early setup via loadBaseModule(), + // there is no proper state yet. + if (!m_product.dependenciesContext) + return; + + for (auto it = stateStack().begin(); it != stateStack().end(); ++it) { + Item *itemToCheck = moduleItem; + if (it->loadingItem != itemToCheck) { + if (!productDep) + continue; + itemToCheck = productDep->item; + } + if (it->loadingItem != itemToCheck) + continue; + ErrorInfo e; + e.append(Tr::tr("Cyclic dependencies detected:")); + while (true) { + e.append(it->loadingItemOrigin.name.toString(), + it->loadingItemOrigin.location()); + if (it->loadingItem->type() == ItemType::ModuleInstance) { + createNonPresentModule(m_loaderState.itemPool(), + it->loadingItemOrigin.name.toString(), + QLatin1String("cyclic dependency"), it->loadingItem); + } + if (it == stateStack().begin()) + break; + --it; + } + e.append(dependency.name.toString(), dependency.location()); + throw e; + } + checkForModuleNamePrefixCollision(dependency); +} + +void DependenciesResolver::adjustDependsItemForMultiplexing(Item *dependsItem) +{ + if (m_product.name.startsWith(StringConstants::shadowProductPrefix())) + return; + + Evaluator &evaluator = m_loaderState.evaluator(); + const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty()); + const bool productIsMultiplexed = !m_product.multiplexConfigurationId.isEmpty(); + if (name == m_product.name) { + QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator. + return; + } + + bool hasNonMultiplexedDependency = false; + const std::vector<ProductContext *> multiplexedDependencies = m_product.project + ->topLevelProject->productsWithNameAndConstraint(name, [&hasNonMultiplexedDependency] + (const ProductContext &product) { + if (product.multiplexConfigurationId.isEmpty()) { + hasNonMultiplexedDependency = true; + return false; + } + return true; + }); + const bool hasMultiplexedDependencies = !multiplexedDependencies.empty(); + + // These are the allowed cases: + // (1) Normal dependency with no multiplexing whatsoever. + // (2) Both product and dependency are multiplexed. + // (2a) The profiles property is not set, we want to depend on the best + // matching variant. + // (2b) The profiles property is set, we want to depend on all variants + // with a matching profile. + // (3) The product is not multiplexed, but the dependency is. + // (3a) The profiles property is not set, the dependency has an aggregator. + // We want to depend on the aggregator. + // (3b) The profiles property is not set, the dependency does not have an + // aggregator. We want to depend on all the multiplexed variants. + // (3c) The profiles property is set, we want to depend on all variants + // with a matching profile regardless of whether an aggregator exists or not. + // (4) The product is multiplexed, but the dependency is not. We don't have to adapt + // any Depends items. + // (1) and (4) + if (!hasMultiplexedDependencies) + return; + + bool profilesPropertyIsSet; + const QStringList profiles = evaluator.stringListValue( + dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet); + + // (3a) + if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet) + return; + + static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs, + const QVariantMap &rhs) { + QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty()); + for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) { + const auto rhsProperty = rhs.find(lhsProperty.key()); + const bool isCommonProperty = rhsProperty != rhs.constEnd(); + if (isCommonProperty && !qVariantsEqual(lhsProperty.value(), rhsProperty.value())) + return false; + } + return true; + }; + + QStringList multiplexIds; + const auto productMultiplexConfig = m_loaderState.topLevelProject().multiplexConfiguration( + m_product.multiplexConfigurationId); + + for (const ProductContext *dependency : multiplexedDependencies) { + if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a + if (dependency->multiplexConfigurationId == m_product.multiplexConfigurationId) { + const ValuePtr &multiplexId = m_product.item->property( + StringConstants::multiplexConfigurationIdProperty()); + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + multiplexId); + return; + + } + // Otherwise collect partial matches and decide later + const auto dependencyMultiplexConfig = m_loaderState.topLevelProject() + .multiplexConfiguration(dependency->multiplexConfigurationId); + + if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig)) + multiplexIds << dependency->multiplexConfigurationId; + } else { + // (2b), (3b) or (3c) + const bool profileMatch = !profilesPropertyIsSet || profiles.empty() + || profiles.contains(dependency->profileName); + if (profileMatch) + multiplexIds << dependency->multiplexConfigurationId; + } + } + if (multiplexIds.empty()) { + const QString productName = m_product.displayName(); + throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. " + "There are no eligible multiplex candidates.").arg(productName, + name), + dependsItem->location()); + } + + // In case of (2a), at most 1 match is allowed + if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) { + QStringList candidateNames; + for (const auto &id : std::as_const(multiplexIds)) + candidateNames << fullProductDisplayName(name, id); + throw ErrorInfo( + Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. " + "Eligible multiplex candidates: %3.").arg( + m_product.displayName(), name, candidateNames.join(QLatin1String(", "))), + dependsItem->location()); + } + + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + VariantValue::create(multiplexIds)); + +} + +std::optional<EvaluatedDependsItem> DependenciesResolver::evaluateDependsItem(Item *item) +{ + Evaluator &evaluator = m_loaderState.evaluator(); + if (!m_product.project->topLevelProject->checkItemCondition(item, evaluator)) { + qCDebug(lcModuleLoader) << "Depends item disabled, ignoring."; + return {}; + } + + const QString name = evaluator.stringValue(item, StringConstants::nameProperty()); + if (name == StringConstants::qbsModule()) // Redundant + return {}; + + bool submodulesPropertySet; + const QStringList submodules = evaluator.stringListValue( + item, StringConstants::submodulesProperty(), &submodulesPropertySet); + if (submodules.empty() && submodulesPropertySet) { + qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list."; + return {}; + } + if (Q_UNLIKELY(submodules.size() > 1 && !item->id().isEmpty())) { + QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); + throw ErrorInfo(msg, item->location()); + } + bool productTypesWasSet; + const QStringList productTypes = evaluator.stringListValue( + item, StringConstants::productTypesProperty(), &productTypesWasSet); + if (!name.isEmpty() && !productTypes.isEmpty()) { + throw ErrorInfo(Tr::tr("The name and productTypes are mutually exclusive " + "in a Depends item."), item->location()); + } + if (productTypes.isEmpty() && productTypesWasSet) { + qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list."; + return {}; + } + if (name.isEmpty() && !productTypesWasSet) { + throw ErrorInfo(Tr::tr("Either name or productTypes must be set in a Depends item"), + item->location()); + } + + bool profilesPropertyWasSet = false; + std::optional<QStringList> profiles; + bool required = true; + if (productTypes.isEmpty()) { + const QStringList profileList = evaluator.stringListValue( + item, StringConstants::profilesProperty(), &profilesPropertyWasSet); + if (profilesPropertyWasSet) + profiles.emplace(profileList); + required = evaluator.boolValue(item, StringConstants::requiredProperty()); + } + const Version minVersion = Version::fromString( + evaluator.stringValue(item, StringConstants::versionAtLeastProperty())); + const Version maxVersion = Version::fromString( + evaluator.stringValue(item, StringConstants::versionBelowProperty())); + const bool limitToSubProject = evaluator.boolValue( + item, StringConstants::limitToSubProjectProperty()); + const QStringList multiplexIds = evaluator.stringListValue( + item, StringConstants::multiplexConfigurationIdsProperty()); + adjustParametersScopes(item, item); + forwardParameterDeclarations(item, m_product.item->modules()); + const QVariantMap parameters = extractParameters(item); + + const FileTags productTypeTags = FileTags::fromStringList(productTypes); + if (!productTypeTags.empty()) + m_product.bulkDependencies.emplace_back(productTypeTags, item->location()); + return EvaluatedDependsItem{ + item, + QualifiedId::fromString(name), + submodules, + productTypeTags, + multiplexIds, + profiles, + {minVersion, maxVersion}, + parameters, + limitToSubProject, + required}; +} + +// Potentially multiplexes a dependency along Depends.productTypes, Depends.subModules and +// Depends.profiles, as well as internally set up multiplexing axes. +// Each entry in the resulting queue corresponds to exactly one product or module to pull in. +std::queue<FullyResolvedDependsItem> +DependenciesResolver::multiplexDependency(const EvaluatedDependsItem &dependency) +{ + std::queue<FullyResolvedDependsItem> dependencies; + if (!dependency.productTypes.empty()) { + const auto constraint = [&](const ProductContext &product) { + return &product != &m_product && product.name != m_product.name + && (!dependency.limitToSubProject || haveSameSubProject(m_product, product)); + }; + const std::vector<ProductContext *> matchingProducts = m_product.project->topLevelProject + ->productsWithTypeAndConstraint(dependency.productTypes, constraint); + if (matchingProducts.empty()) { + qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything." + << dependency.item->location(); + return {}; + } + for (ProductContext * const match : matchingProducts) + dependencies.emplace(match, dependency); + return dependencies; + } + + const QStringList profiles = dependency.profiles && !dependency.profiles->isEmpty() + ? *dependency.profiles : QStringList(QString()); + const QStringList multiplexIds = !dependency.multiplexIds.isEmpty() + ? dependency.multiplexIds : QStringList(QString()); + const QStringList subModules = !dependency.subModules.isEmpty() + ? dependency.subModules : QStringList(QString()); + for (const QString &profile : profiles) { + for (const QString &multiplexId : multiplexIds) { + for (const QString &subModule : subModules) { + QualifiedId name = dependency.name; + if (!subModule.isEmpty()) + name << subModule.split(QLatin1Char('.')); + dependencies.emplace(dependency, name, profile, multiplexId); + } + } + } + return dependencies; +} + +QVariantMap DependenciesResolver::extractParameters(Item *dependsItem) const +{ + try { + QVariantMap result; + const auto &properties = dependsItem->properties(); + Evaluator &evaluator = m_loaderState.evaluator(); + for (auto it = properties.begin(); it != properties.end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + const JSValue sv = evaluator.scriptValue( + std::static_pointer_cast<ItemValue>(it.value())->item()); + result.insert(it.key(), safeToVariant(evaluator.engine()->context(), sv)); + } + return result; + } catch (const ErrorInfo &exception) { + auto ei = exception; + ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location()); + throw ei; + } +} + +void DependenciesResolver::forwardParameterDeclarations(const Item *dependsItem, + const Item::Modules &modules) +{ + for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + forwardParameterDeclarations(it.key(), + std::static_pointer_cast<ItemValue>(it.value())->item(), + modules); + } +} + +void DependenciesResolver::forwardParameterDeclarations( + const QualifiedId &moduleName, Item *item, const Item::Modules &modules) +{ + auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) { + return m.name == moduleName; + }); + if (it != modules.end()) { + item->setPropertyDeclarations(m_loaderState.topLevelProject().parameterDeclarations( + it->item->rootPrototype())); + } else { + for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + forwardParameterDeclarations(QualifiedId(moduleName) << it.key(), + std::static_pointer_cast<ItemValue>(it.value())->item(), + modules); + } + } +} + +std::list<DependenciesResolvingState> &DependenciesResolver::stateStack() +{ + QBS_CHECK(m_product.dependenciesContext); + return static_cast<DependenciesContextImpl *>(m_product.dependenciesContext.get())->stateStack; +} + +void DependenciesResolver::checkForModuleNamePrefixCollision( + const FullyResolvedDependsItem &dependency) +{ + if (!m_product.item) + return; + + for (const Item::Module &m : m_product.item->modules()) { + if (m.name.length() == dependency.name.length()) + continue; + + QualifiedId shortName; + QualifiedId longName; + if (m.name.length() < dependency.name.length()) { + shortName = m.name; + longName = dependency.name; + } else { + shortName = dependency.name; + longName = m.name; + } + const auto isPrefix = [&] { + for (int i = 0; i < shortName.length(); ++i) { + if (shortName.at(i) != longName.at(i)) + return false; + } + return true; + }; + if (!isPrefix()) + continue; + + throw ErrorInfo(Tr::tr("The name of module '%1' is a prefix of the name of module '%2', " + "which is not allowed") + .arg(shortName.toString(), longName.toString()), dependency.location()); + } +} + +Item::Module DependenciesResolver::createModule( + const FullyResolvedDependsItem &dependency, Item *item, ProductContext *productDep) +{ + Item::Module m; + m.item = item; + m.product = productDep; + m.name = dependency.name; + m.required = dependency.requiredLocally; + m.versionRange = dependency.versionRange; + return m; +} + +FullyResolvedDependsItem::FullyResolvedDependsItem( + ProductContext *product, const EvaluatedDependsItem &dependency) + : product(product) + , item(dependency.item) + , name(product->name) + , versionRange(dependency.versionRange) + , parameters(dependency.parameters) + , checkProduct(false) +{} + +FullyResolvedDependsItem FullyResolvedDependsItem::makeBaseDependency() +{ + FullyResolvedDependsItem item; + item.name = StringConstants::qbsModule(); + return item; +} + +FullyResolvedDependsItem::FullyResolvedDependsItem( + const EvaluatedDependsItem &dependency, QualifiedId name, QString profile, QString multiplexId) + : item(dependency.item) + , name(std::move(name)) + , profile(std::move(profile)) + , multiplexId(std::move(multiplexId)) + , versionRange(dependency.versionRange) + , parameters(dependency.parameters) + , limitToSubProject(dependency.limitToSubProject) + , requiredLocally(dependency.requiredLocally) + , requiredGlobally(dependency.requiredGlobally) +{} + +QString FullyResolvedDependsItem::id() const +{ + if (!item) { + QBS_CHECK(name.toString() == StringConstants::qbsModule()); + return {}; + } + return item->id(); +} + +CodeLocation FullyResolvedDependsItem::location() const +{ + if (!item) { + QBS_CHECK(name.toString() == StringConstants::qbsModule()); + return {}; + } + return item->location(); +} + +QString FullyResolvedDependsItem::displayName() const +{ + return fullProductDisplayName(name.toString(), multiplexId); +} + +bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2) +{ + for (const Item *otherParent = p2.item->parent(); otherParent; + otherParent = otherParent->parent()) { + if (otherParent == p1.item->parent()) + return true; + } + return false; +} + +QVariantMap safeToVariant(JSContext *ctx, const JSValue &v) +{ + QVariantMap result; + handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) { + const JSValue u = desc.value; + if (JS_IsError(ctx, u)) + throw ErrorInfo(getJsString(ctx, u)); + const QString name = getJsString(ctx, prop); + result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u)) + ? safeToVariant(ctx, u) : getJsVariant(ctx, u); + }); + return result; +} + +DependenciesContextImpl::DependenciesContextImpl(ProductContext &product, LoaderState &loaderState) + : m_product(product) +{ + setSearchPathsForProduct(loaderState); + + // Initialize the state with the direct Depends items of the product item. + DependenciesResolvingState newState{product.item,}; + for (Item * const child : product.item->children()) { + if (child->type() == ItemType::Depends) + newState.pendingDependsItems.push(child); + } + stateStack.push_front(std::move(newState)); + stateStack.front().pendingResolvedDependencies.push( + FullyResolvedDependsItem::makeBaseDependency()); +} + +std::pair<ProductDependency, ProductContext *> DependenciesContextImpl::pendingDependency() const +{ + QBS_CHECK(!stateStack.empty()); + if (const auto ¤tDependsItem = stateStack.front().currentDependsItem; + currentDependsItem && !currentDependsItem->productTypes.empty()) { + qCDebug(lcLoaderScheduling) << "product" << m_product.displayName() + << "to be delayed because of bulk dependency"; + return {ProductDependency::Bulk, nullptr}; + } + if (!stateStack.front().pendingResolvedDependencies.empty()) { + if (ProductContext * const dep = stateStack.front().pendingResolvedDependencies + .front().product) { + if (m_product.project->topLevelProject->isProductQueuedForHandling(*dep)) { + qCDebug(lcLoaderScheduling) << "product" << m_product.displayName() + << "to be delayed because of dependency " + "to unfinished product" << dep->displayName(); + return {ProductDependency::Single, dep}; + } else { + qCDebug(lcLoaderScheduling) << "product" << m_product.displayName() + << "to be re-scheduled, as dependency " + << dep->displayName() + << "appears to have finished in the meantime"; + return {ProductDependency::None, dep}; + } + } + } + return {ProductDependency::None, nullptr}; +} + +void DependenciesContextImpl::setSearchPathsForProduct(LoaderState &loaderState) +{ + QBS_CHECK(m_product.searchPaths.isEmpty()); + + m_product.searchPaths = loaderState.itemReader().readExtraSearchPaths(m_product.item); + Settings settings(loaderState.parameters().settingsDirectory()); + const QStringList prefsSearchPaths = Preferences(&settings, m_product.profileModuleProperties) + .searchPaths(); + const QStringList ¤tSearchPaths = loaderState.itemReader().allSearchPaths(); + for (const QString &p : prefsSearchPaths) { + if (!currentSearchPaths.contains(p) && FileInfo(p).exists()) + m_product.searchPaths << p; + } +} + +} // namespace +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/dependenciesresolver.h b/src/lib/corelib/loader/dependenciesresolver.h new file mode 100644 index 000000000..48502adb7 --- /dev/null +++ b/src/lib/corelib/loader/dependenciesresolver.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +namespace qbs::Internal { +class Item; +class LoaderState; +class ProductContext; +enum class Deferral; + +// Collects the products' dependencies and builds the list of modules from them. +// Actual loading of module files is offloaded to ModuleLoader. +void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState); + +// Note: This function is never called for regular loading of the base module into a product, +// but only for the special cases of loading the dummy base module into a project +// and temporarily providing a base module for product multiplexing. +Item *loadBaseModule(ProductContext &product, Item *item, LoaderState &loaderState); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/groupshandler.cpp b/src/lib/corelib/loader/groupshandler.cpp new file mode 100644 index 000000000..3dbfad352 --- /dev/null +++ b/src/lib/corelib/loader/groupshandler.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "groupshandler.h" + +#include "loaderutils.h" +#include "moduleinstantiator.h" + +#include <language/evaluator.h> +#include <language/item.h> +#include <language/value.h> +#include <logging/translator.h> +#include <tools/profiling.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +namespace qbs::Internal { +class GroupsHandler +{ +public: + GroupsHandler(ProductContext &product, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState) {} + + void run(); + +private: + void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix, + QualifiedIdSet &properties); + void markModuleTargetGroups(Item *group, const Item::Module &module); + void moveGroupsFromModuleToProduct(const Item::Module &module); + void moveGroupsFromModulesToProduct(); + void propagateModulesFromParent(Item *group); + void handleGroup(Item *group); + void adjustScopesInGroupModuleInstances(Item *groupItem, const Item::Module &module); + QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group); + + ProductContext &m_product; + LoaderState &m_loaderState; +}; + +void setupGroups(ProductContext &product, LoaderState &loaderState) +{ + GroupsHandler(product, loaderState).run(); +} + +void GroupsHandler::run() +{ + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.groupsSetup : nullptr); + + moveGroupsFromModulesToProduct(); + for (Item * const child : m_product.item->children()) { + if (child->type() == ItemType::Group) + handleGroup(child); + } +} + +void GroupsHandler::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix, + QualifiedIdSet &properties) +{ + const Item::PropertyMap &props = iv->item()->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + switch (it.value()->type()) { + case Value::JSSourceValueType: + properties << (QualifiedId(prefix) << it.key()); + break; + case Value::ItemValueType: + if (iv->item()->type() == ItemType::ModulePrefix) { + gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), + QualifiedId(prefix) << it.key(), properties); + } + break; + default: + break; + } + } +} + +void GroupsHandler::markModuleTargetGroups(Item *group, const Item::Module &module) +{ + QBS_CHECK(group->type() == ItemType::Group); + if (m_loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) { + group->setProperty(StringConstants::modulePropertyInternal(), + VariantValue::create(module.name.toString())); + } + for (Item * const child : group->children()) + markModuleTargetGroups(child, module); +} + +void GroupsHandler::moveGroupsFromModuleToProduct(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + for (auto it = module.item->children().begin(); it != module.item->children().end();) { + Item * const child = *it; + if (child->type() != ItemType::Group) { + ++it; + continue; + } + + Item * const scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope); + scope->setProperties(module.item->properties()); + scope->setScope(m_product.scope); + child->setScope(scope); + setScopeForDescendants(child, scope); + + Item::addChild(m_product.item, child); + markModuleTargetGroups(child, module); + it = module.item->children().erase(it); + } +} + +void GroupsHandler::moveGroupsFromModulesToProduct() +{ + for (const Item::Module &module : m_product.item->modules()) + moveGroupsFromModuleToProduct(module); +} + +// TODO: I don't completely understand this function, and I suspect we do both too much +// and too little here. In particular, I'm not sure why we should even have to do anything +// with groups that don't attach properties. +// Set aside a day or two at some point to fully grasp all the details and rewrite accordingly. +void GroupsHandler::propagateModulesFromParent(Item *group) +{ + QBS_CHECK(group->type() == ItemType::Group); + QHash<QualifiedId, Item *> moduleInstancesForGroup; + + // Step 1: "Instantiate" the product's modules for the group. + for (Item::Module m : group->parent()->modules()) { + Item * const targetItem = retrieveModuleInstanceItem(group, m.name, m_loaderState); + QBS_CHECK(targetItem->type() == ItemType::ModuleInstancePlaceholder); + targetItem->setPrototype(m.item); + + Item * const moduleScope = Item::create(&m_loaderState.itemPool(), ItemType::Scope); + moduleScope->setFile(group->file()); + moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids + moduleScope->setScope(group); + targetItem->setScope(moduleScope); + + targetItem->setFile(m.item->file()); + + // "parent" should point to the group/artifact parent + targetItem->setParent(group->parent()); + + targetItem->setOuterItem(m.item); + + m.item = targetItem; + group->addModule(m); + moduleInstancesForGroup.insert(m.name, targetItem); + } + + // Step 2: Make the inter-module references point to the instances created in step 1. + for (const Item::Module &module : group->modules()) { + Item::Modules adaptedModules; + const Item::Modules &oldModules = module.item->prototype()->modules(); + for (Item::Module depMod : oldModules) { + depMod.item = moduleInstancesForGroup.value(depMod.name); + adaptedModules << depMod; + if (depMod.name.front() == module.name.front()) + continue; + const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front(), + m_loaderState.itemPool()); + QBS_CHECK(modulePrefix); + module.item->setProperty(depMod.name.front(), modulePrefix); + } + module.item->setModules(adaptedModules); + } + + const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(group); + if (propsSetInGroup.empty()) + return; + m_product.modulePropertiesSetInGroups.insert(std::make_pair(group, propsSetInGroup)); + + // Step 3: Adapt scopes in values. This is potentially necessary if module properties + // get assigned on the group level. + for (const Item::Module &module : group->modules()) + adjustScopesInGroupModuleInstances(group, module); +} + +void GroupsHandler::handleGroup(Item *group) +{ + propagateModulesFromParent(group); + m_loaderState.topLevelProject().checkItemCondition(group, m_loaderState.evaluator()); + for (Item * const child : group->children()) { + if (child->type() == ItemType::Group) + handleGroup(child); + } +} + +void GroupsHandler::adjustScopesInGroupModuleInstances(Item *groupItem, + const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + + Item *modulePrototype = module.item->rootPrototype(); + QBS_CHECK(modulePrototype->type() == ItemType::Module + || modulePrototype->type() == ItemType::Export); + + const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations(); + for (const auto &decl : propDecls) { + const QString &propName = decl.name(); + + // Nothing gets inherited for module properties assigned directly in the group. + // In particular, setting a list property overwrites the value from the product's + // (or parent group's) instance completely, rather than appending to it + // (concatenation happens via outer.concat()). + ValuePtr propValue = module.item->ownProperty(propName); + if (propValue) { + propValue->setScope(module.item->scope(), {}); + continue; + } + + // Find the nearest prototype instance that has the value assigned. + // The result is either an instance of a parent group (or the parent group's + // parent group and so on) or the instance of the product or the module prototype. + // In the latter case, we don't have to do anything. + const Item *instanceWithProperty = module.item; + do { + instanceWithProperty = instanceWithProperty->prototype(); + QBS_CHECK(instanceWithProperty); + propValue = instanceWithProperty->ownProperty(propName); + } while (!propValue); + QBS_CHECK(propValue); + + if (propValue->type() != Value::JSSourceValueType) + continue; + + if (propValue->scope()) + module.item->setProperty(propName, propValue->clone(m_loaderState.itemPool())); + } + + for (const ValuePtr &prop : module.item->properties()) { + if (prop->type() != Value::JSSourceValueType) { + QBS_CHECK(!prop->next()); + continue; + } + for (ValuePtr v = prop; v; v = v->next()) { + if (!v->scope()) + continue; + for (const Item::Module &module : groupItem->modules()) { + if (v->scope() == module.item->prototype()) { + v->setScope(module.item, {}); + break; + } + } + } + } +} + +QualifiedIdSet GroupsHandler::gatherModulePropertiesSetInGroup(const Item *group) +{ + QualifiedIdSet propsSetInGroup; + const Item::PropertyMap &props = group->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + if (it.value()->type() == Value::ItemValueType) { + gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(), + QualifiedId(it.key()), propsSetInGroup); + } + } + return propsSetInGroup; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/groupshandler.h b/src/lib/corelib/loader/groupshandler.h new file mode 100644 index 000000000..5de94ce7c --- /dev/null +++ b/src/lib/corelib/loader/groupshandler.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +namespace qbs::Internal { +class LoaderState; +class ProductContext; + +// Sets up Group items for the actual resolving stage. Responsibilities: +// - Moving Group items located in modules over to the product. +// - Identifying groups declaring target artifacts and marking them accordingly. +// - Setting up group-level module instances to ensure proper resolving of per-group module +// properties. +// - As a side effect of the above point, collecting all properties set on the Group level +// to help the ProjectResolver decide which properties need to be re-evaluated at all, +// which is an important optimization (see commit 9cd8653eef). +void setupGroups(ProductContext &product, LoaderState &loaderState); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp new file mode 100644 index 000000000..0638d1af5 --- /dev/null +++ b/src/lib/corelib/loader/itemreader.cpp @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "itemreader.h" + +#include "itemreadervisitorstate.h" +#include "loaderutils.h" + +#include <language/deprecationinfo.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/item.h> +#include <language/value.h> +#include <logging/categories.h> +#include <tools/fileinfo.h> +#include <tools/profiling.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> +#include <tools/stlutils.h> + +#include <QtCore/qfileinfo.h> + +#include <algorithm> + +namespace qbs { +namespace Internal { + +static void makePathsCanonical(QStringList &paths) +{ + Internal::removeIf(paths, [](QString &p) { + p = QFileInfo(p).canonicalFilePath(); + return p.isEmpty(); + }); +} + +ItemReader::ItemReader(LoaderState &loaderState) : m_loaderState(loaderState) {} + +void ItemReader::init() +{ + m_visitorState = std::make_unique<ItemReaderVisitorState>( + m_loaderState.topLevelProject().itemReaderCache(), m_loaderState.logger()); + m_visitorState->setDeprecationWarningMode(m_loaderState.parameters().deprecationWarningMode()); + m_projectFilePath = m_loaderState.parameters().projectFilePath(); + setSearchPaths(m_loaderState.parameters().searchPaths()); + m_elapsedTime = m_loaderState.parameters().logElapsedTime() ? 0 : -1; +} + +ItemReader::~ItemReader() = default; + +void ItemReader::setSearchPaths(const QStringList &searchPaths) +{ + qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths; + m_searchPaths = searchPaths; + makePathsCanonical(m_searchPaths); + m_allSearchPaths.clear(); +} + +void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) +{ + m_extraSearchPaths.push_back(extraSearchPaths); + makePathsCanonical(m_extraSearchPaths.back()); + m_allSearchPaths.clear(); +} + +void ItemReader::popExtraSearchPaths() +{ + m_extraSearchPaths.pop_back(); + m_allSearchPaths.clear(); +} + +const std::vector<QStringList> &ItemReader::extraSearchPathsStack() const +{ + return m_extraSearchPaths; +} + +void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &s) +{ + m_extraSearchPaths = s; + m_allSearchPaths.clear(); +} + +void ItemReader::clearExtraSearchPathsStack() +{ + m_extraSearchPaths.clear(); + m_allSearchPaths.clear(); +} + +const QStringList &ItemReader::allSearchPaths() const +{ + if (m_allSearchPaths.empty()) { + std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(), + [this] (const QStringList &paths) { + m_allSearchPaths += paths; + }); + m_allSearchPaths += m_searchPaths; + m_allSearchPaths.removeDuplicates(); + } + return m_allSearchPaths; +} + +Item *ItemReader::readFile(const QString &filePath) +{ + AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr); + return m_visitorState->readFile(filePath, allSearchPaths(), &m_loaderState.itemPool()); +} + +Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation) +{ + try { + return readFile(filePath); + } catch (const ErrorInfo &e) { + if (e.hasLocation()) + throw; + throw ErrorInfo(e.toString(), referencingLocation); + } +} + +void ItemReader::handlePropertyOptions(Item *optionsItem) +{ + Evaluator &evaluator = m_loaderState.evaluator(); + const QString name = evaluator.stringValue(optionsItem, StringConstants::nameProperty()); + if (name.isEmpty()) { + throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"), + optionsItem->location()); + } + const QString description = evaluator.stringValue( + optionsItem, StringConstants::descriptionProperty()); + const auto removalVersion = Version::fromString( + evaluator.stringValue(optionsItem, StringConstants::removalVersionProperty())); + const auto allowedValues = evaluator.stringListValue( + optionsItem, StringConstants::allowedValuesProperty()); + + PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name); + if (!decl.isValid()) { + decl.setName(name); + decl.setType(PropertyDeclaration::Variant); + } + decl.setDescription(description); + if (removalVersion.isValid()) { + DeprecationInfo di(removalVersion, description); + decl.setDeprecationInfo(di); + } + decl.setAllowedValues(allowedValues); + const ValuePtr property = optionsItem->parent()->property(name); + if (!property && !decl.isExpired()) { + throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'") + .arg(name), optionsItem->location()); + } + if (property && decl.isExpired()) { + ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but " + "is still present.") + .arg(name, removalVersion.toString()), + property->location()); + e.append(Tr::tr("Removal version for '%1' specified here.").arg(name), + optionsItem->location()); + m_visitorState->logger().printWarning(e); + } + optionsItem->parent()->setPropertyDeclaration(name, decl); +} + +void ItemReader::handleAllPropertyOptionsItems(Item *item) +{ + QList<Item *> childItems = item->children(); + auto childIt = childItems.begin(); + while (childIt != childItems.end()) { + Item * const child = *childIt; + if (child->type() == ItemType::PropertyOptions) { + handlePropertyOptions(child); + childIt = childItems.erase(childIt); + } else { + handleAllPropertyOptionsItems(child); + ++childIt; + } + } + item->setChildren(childItems); +} + +Item *ItemReader::setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation) +{ + Item *item = readFile(filePath, referencingLocation); + + // This is technically not needed, because files are only set up once and then served + // from a cache. But it simplifies the checks in item.cpp if we require the locking invariant + // to always hold. + std::unique_ptr<ModuleItemLocker> locker; + if (item->type() == ItemType::Module) + locker = std::make_unique<ModuleItemLocker>(*item); + + handleAllPropertyOptionsItems(item); + return item; +} + +Item *ItemReader::wrapInProjectIfNecessary(Item *item) +{ + if (item->type() == ItemType::Project) + return item; + Item *prj = Item::create(&m_loaderState.itemPool(), ItemType::Project); + Item::addChild(prj, item); + prj->setFile(item->file()); + prj->setLocation(item->location()); + prj->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(), + m_visitorState->logger()); + return prj; +} + +QStringList ItemReader::readExtraSearchPaths(Item *item, bool *wasSet) +{ + QStringList result; + const QStringList paths = m_loaderState.evaluator().stringListValue( + item, StringConstants::qbsSearchPathsProperty(), wasSet); + const JSSourceValueConstPtr prop = item->sourceProperty( + StringConstants::qbsSearchPathsProperty()); + + // Value can come from within a project file or as an overridden value from the user + // (e.g command line). + const QString basePath = FileInfo::path(prop ? prop->file()->filePath() + : m_projectFilePath); + for (const QString &path : paths) + result += FileInfo::resolvePath(basePath, path); + return result; +} + +SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths) + : m_itemReader(itemReader), + m_oldSize(itemReader.extraSearchPathsStack().size()) +{ + if (!extraSearchPaths.isEmpty()) + m_itemReader.pushExtraSearchPaths(extraSearchPaths); +} + +SearchPathsManager::~SearchPathsManager() +{ + while (m_itemReader.extraSearchPathsStack().size() > m_oldSize) + m_itemReader.popExtraSearchPaths(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/loader/itemreader.h index 6ec99fcb5..e444db16c 100644 --- a/src/lib/corelib/language/itemreader.h +++ b/src/lib/corelib/loader/itemreader.h @@ -40,18 +40,22 @@ #ifndef QBS_ITEMREADER_H #define QBS_ITEMREADER_H -#include "forward_decls.h" #include <logging/logger.h> +#include <tools/deprecationwarningmode.h> #include <tools/set.h> #include <QtCore/qstringlist.h> +#include <memory> + namespace qbs { +class SetupProjectParameters; namespace Internal { - +class Evaluator; class Item; class ItemPool; class ItemReaderVisitorState; +class LoaderState; /* * Reads a qbs file and creates a tree of Item objects. @@ -65,34 +69,52 @@ class ItemReaderVisitorState; class ItemReader { public: - ItemReader(Logger &logger); + ItemReader(LoaderState &loaderState); ~ItemReader(); + void init(); - void setPool(ItemPool *pool) { m_pool = pool; } - void setSearchPaths(const QStringList &searchPaths); void pushExtraSearchPaths(const QStringList &extraSearchPaths); void popExtraSearchPaths(); - std::vector<QStringList> extraSearchPathsStack() const; + const std::vector<QStringList> &extraSearchPathsStack() const; void setExtraSearchPathsStack(const std::vector<QStringList> &s); void clearExtraSearchPathsStack(); const QStringList &allSearchPaths() const; - Item *readFile(const QString &filePath); + // Parses a file, creates an item for it, generates PropertyDeclarations from + // PropertyOptions items and removes said items from the item tree. + Item *setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation); - Set<QString> filesRead() const; + Item *wrapInProjectIfNecessary(Item *item); + QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr); - void setEnableTiming(bool on); qint64 elapsedTime() const { return m_elapsedTime; } private: - ItemPool *m_pool = nullptr; + void setSearchPaths(const QStringList &searchPaths); + Item *readFile(const QString &filePath); + Item *readFile(const QString &filePath, const CodeLocation &referencingLocation); + void handlePropertyOptions(Item *optionsItem); + void handleAllPropertyOptionsItems(Item *item); + + LoaderState &m_loaderState; QStringList m_searchPaths; std::vector<QStringList> m_extraSearchPaths; mutable QStringList m_allSearchPaths; - ItemReaderVisitorState * const m_visitorState; + std::unique_ptr<ItemReaderVisitorState> m_visitorState; + QString m_projectFilePath; qint64 m_elapsedTime = -1; }; +class SearchPathsManager { +public: + SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths = {}); + ~SearchPathsManager(); + +private: + ItemReader &m_itemReader; + size_t m_oldSize{0}; +}; + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp index 901772d16..c3e6b9b89 100644 --- a/src/lib/corelib/language/itemreaderastvisitor.cpp +++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp @@ -41,16 +41,16 @@ #include "astimportshandler.h" #include "astpropertiesitemhandler.h" -#include "asttools.h" -#include "builtindeclarations.h" -#include "filecontext.h" -#include "identifiersearch.h" -#include "item.h" #include "itemreadervisitorstate.h" -#include "value.h" #include <api/languageinfo.h> #include <jsextensions/jsextensions.h> +#include <language/asttools.h> +#include <language/builtindeclarations.h> +#include <language/filecontext.h> +#include <language/identifiersearch.h> +#include <language/item.h> +#include <language/value.h> #include <parser/qmljsast_p.h> #include <tools/codelocation.h> #include <tools/error.h> @@ -75,6 +75,8 @@ ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, { } +ItemReaderASTVisitor::~ItemReaderASTVisitor() = default; + bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram) { ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file); @@ -106,6 +108,8 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) Item *item = Item::create(m_itemPool, ItemType::Unknown); item->setFile(m_file); item->setLocation(itemLocation); + const AST::SourceLocation &endLoc = ast->lastSourceLocation(); + item->setEndPosition(CodePosition(endLoc.startLine, endLoc.startColumn)); // Inheritance resolving, part 1: Find out our actual type name (needed for setting // up children and alternatives). @@ -135,10 +139,15 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) item->m_type = itemType; - if (m_item) + if (m_item) { Item::addChild(m_item, item); // Add this item to the children of the parent item. - else + } else { m_item = item; // This is the root item. + if (itemType == ItemType::Module) { + QBS_CHECK(!m_moduleItemLocker); + m_moduleItemLocker = std::make_unique<ModuleItemLocker>(*m_item); + } + } if (ast->initializer) { Item *mdi = m_visitorState.mostDerivingItem(); @@ -153,7 +162,7 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) m_visitorState.setMostDerivingItem(mdi); } - ASTPropertiesItemHandler(item).handlePropertiesItems(); + ASTPropertiesItemHandler(item, *m_itemPool).handlePropertiesItems(); // Inheritance resolving, part 2 (depends on alternatives having been set up). if (baseItem) { @@ -173,7 +182,7 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) // Only the item at the top of the inheritance chain is a built-in item. // We cannot do this in "part 1", because then the visitor would complain about duplicate // bindings. - item->setupForBuiltinType(m_logger); + item->setupForBuiltinType(m_visitorState.deprecationWarningMode(), m_logger); } return false; @@ -239,9 +248,11 @@ bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) const auto * const idExp = AST::cast<AST::IdentifierExpression *>(expStmt->expression); if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + if (m_item->type() == ItemType::Module) + throw ErrorInfo(Tr::tr("Module items cannot have an id property.")); m_item->m_id = idExp->name.toString(); m_file->ensureIdScope(m_itemPool); - ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id()); + ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id(), *m_itemPool); if (existingId) { ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id())); e.append(Tr::tr("First occurrence is here."), existingId->item()->location()); @@ -268,10 +279,10 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement, QBS_CHECK(value); if (AST::cast<AST::Block *>(statement)) - value->m_flags |= JSSourceValue::HasFunctionForm; + value->setHasFunctionForm(); value->setFile(m_file); - value->setSourceCode(textRefOf(m_file->content(), statement)); + value->setSourceCode(textViewOf(m_file->content(), statement)); value->setLocation(statement->firstSourceLocation().startLine, statement->firstSourceLocation().startColumn); @@ -282,11 +293,11 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement, idsearch.add(StringConstants::originalVar(), &usesOriginal); idsearch.start(statement); if (usesBase) - value->m_flags |= JSSourceValue::SourceUsesBase; + value->setSourceUsesBase(); if (usesOuter) - value->m_flags |= JSSourceValue::SourceUsesOuter; + value->setSourceUsesOuter(); if (usesOriginal) - value->m_flags |= JSSourceValue::SourceUsesOriginal; + value->setSourceUsesOriginal(); return false; } @@ -321,7 +332,7 @@ Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName, void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src) { int insertPos = 0; - for (Item *child : qAsConst(src->m_children)) { + for (Item *child : std::as_const(src->m_children)) { dst->m_children.insert(insertPos++, child); child->m_parent = dst; } @@ -335,6 +346,9 @@ void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src) dst->setPropertyDeclaration(pd.name(), pd); } + std::unique_ptr<ModuleItemLocker> locker; + if (src->type() == ItemType::Module) + locker = std::make_unique<ModuleItemLocker>(*src); for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) { ValuePtr &v = dst->m_properties[it.key()]; if (!v) { @@ -369,24 +383,10 @@ void ItemReaderASTVisitor::checkDeprecationStatus(ItemType itemType, const QStri const CodeLocation &itemLocation) { const ItemDeclaration itemDecl = BuiltinDeclarations::instance().declarationsForType(itemType); - const DeprecationInfo &di = itemDecl.deprecationInfo(); - if (!di.isValid()) - return; - if (di.removalVersion() <= LanguageInfo::qbsVersion()) { - QString message = Tr::tr("The item '%1' cannot be used anymore. " - "It was removed in qbs %2.") - .arg(itemName, di.removalVersion().toString()); - ErrorInfo error(message, itemLocation); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); + const ErrorInfo error = itemDecl.checkForDeprecation(m_visitorState.deprecationWarningMode(), + itemName, itemLocation, m_logger); + if (error.hasError()) throw error; - } - QString warning = Tr::tr("The item '%1' is deprecated and will be removed in " - "qbs %2.").arg(itemName, di.removalVersion().toString()); - ErrorInfo error(warning, itemLocation); - if (!di.additionalUserInfo().isEmpty()) - error.append(di.additionalUserInfo()); - m_logger.printWarning(error); } void ItemReaderASTVisitor::doCheckItemTypes(const Item *item) diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/loader/itemreaderastvisitor.h index 963b78471..c47501f2f 100644 --- a/src/lib/corelib/language/itemreaderastvisitor.h +++ b/src/lib/corelib/loader/itemreaderastvisitor.h @@ -40,8 +40,8 @@ #ifndef QBS_ITEMREADERASTVISITOR_H #define QBS_ITEMREADERASTVISITOR_H -#include "forward_decls.h" -#include "itemtype.h" +#include <language/forward_decls.h> +#include <language/itemtype.h> #include <logging/logger.h> #include <parser/qmljsastvisitor_p.h> @@ -49,6 +49,8 @@ #include <QtCore/qhash.h> #include <QtCore/qstringlist.h> +#include <memory.h> + namespace qbs { class CodeLocation; @@ -56,12 +58,15 @@ namespace Internal { class Item; class ItemPool; class ItemReaderVisitorState; +class ModuleItemLocker; class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor { public: ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, FileContextPtr file, ItemPool *itemPool, Logger &logger); + ~ItemReaderASTVisitor(); + void checkItemTypes() { doCheckItemTypes(rootItem()); } Item *rootItem() const { return m_item; } @@ -88,7 +93,8 @@ private: Logger &m_logger; QHash<QStringList, QString> m_typeNameToFile; Item *m_item = nullptr; - ItemType m_instanceItemType = ItemType::ModuleInstance; + std::unique_ptr<ModuleItemLocker> m_moduleItemLocker; + ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder; }; } // namespace Internal diff --git a/src/lib/corelib/language/itemreadervisitorstate.cpp b/src/lib/corelib/loader/itemreadervisitorstate.cpp index 20ddb5cfb..6ea23c021 100644 --- a/src/lib/corelib/language/itemreadervisitorstate.cpp +++ b/src/lib/corelib/loader/itemreadervisitorstate.cpp @@ -38,101 +38,45 @@ ****************************************************************************/ #include "itemreadervisitorstate.h" -#include "asttools.h" -#include "filecontext.h" #include "itemreaderastvisitor.h" +#include "loaderutils.h" +#include <language/asttools.h> +#include <language/filecontext.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 <tools/stringconstants.h> -#include <QtCore/qshareddata.h> +#include <QtCore/qdiriterator.h> #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qshareddata.h> #include <QtCore/qtextstream.h> namespace qbs { namespace Internal { -class ASTCacheValueData : public QSharedData +ItemReaderVisitorState::ItemReaderVisitorState(ItemReaderCache &cache, Logger &logger) + : m_cache(cache), m_logger(logger) { - Q_DISABLE_COPY(ASTCacheValueData) -public: - ASTCacheValueData() - : ast(nullptr) - , processing(false) - { - } - - QString code; - QbsQmlJS::Engine engine; - QbsQmlJS::AST::UiProgram *ast; - bool processing; -}; - -class ASTCacheValue -{ -public: - ASTCacheValue() - : d(new ASTCacheValueData) - { - } - - ASTCacheValue(const ASTCacheValue &other) = default; - - 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 ItemReaderVisitorState::ASTCache : public QHash<QString, ASTCacheValue> {}; - - -ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger) - : m_logger(logger) - , m_astCache(new ASTCache) -{ - -} - -ItemReaderVisitorState::~ItemReaderVisitorState() -{ - delete m_astCache; } Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool) { - 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 { + const auto setupCacheEntry = [&](ItemReaderCache::AstCacheEntry &entry) { QFile file(filePath); if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); - m_filesRead.insert(filePath); QTextStream stream(&file); - stream.setCodec("UTF-8"); + setupDefaultCodec(stream); const QString &code = stream.readAll(); - QbsQmlJS::Lexer lexer(cacheValue.engine()); + QbsQmlJS::Lexer lexer(&entry.engine); lexer.setCode(code, 1); - QbsQmlJS::Parser parser(cacheValue.engine()); + QbsQmlJS::Parser parser(&entry.engine); file.close(); if (!parser.parse()) { @@ -145,42 +89,49 @@ Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringLis } } - cacheValue.setCode(code); - cacheValue.setAst(parser.ast()); - } + entry.code = code; + entry.ast = parser.ast(); + }; + ItemReaderCache::AstCacheEntry &cacheEntry = m_cache.retrieveOrSetupCacheEntry( + filePath, setupCacheEntry); const FileContextPtr file = FileContext::create(); file->setFilePath(QFileInfo(filePath).absoluteFilePath()); - file->setContent(cacheValue.code()); + file->setContent(cacheEntry.code); file->setSearchPaths(searchPaths); ItemReaderASTVisitor astVisitor(*this, file, itemPool, m_logger); { class ProcessingFlagManager { public: - ProcessingFlagManager(ASTCacheValue &v) : m_cacheValue(v) { v.setProcessingFlag(true); } - ~ProcessingFlagManager() { m_cacheValue.setProcessingFlag(false); } + ProcessingFlagManager(ItemReaderCache::AstCacheEntry &e, const QString &filePath) + : m_cacheEntry(e) + { + if (!e.addProcessingThread()) + throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); + } + ~ProcessingFlagManager() { m_cacheEntry.removeProcessingThread(); } + private: - ASTCacheValue &m_cacheValue; - } processingFlagManager(cacheValue); - cacheValue.ast()->accept(&astVisitor); + ItemReaderCache::AstCacheEntry &m_cacheEntry; + } processingFlagManager(cacheEntry, filePath); + cacheEntry.ast->accept(&astVisitor); } astVisitor.checkItemTypes(); return astVisitor.rootItem(); } -void ItemReaderVisitorState::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) +void ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const { - m_directoryEntries.insert(dirPath, entries); -} - -bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const -{ - const auto it = m_directoryEntries.constFind(dirPath); - if (it == m_directoryEntries.constEnd()) - return false; - *entries = it.value(); - return true; + *entries = m_cache.retrieveOrSetDirectoryEntries(dirPath, [&dirPath] { + QStringList fileNames; + QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards()); + while (dirIter.hasNext()) { + dirIter.next(); + fileNames << dirIter.fileName(); + } + return fileNames; + }); } Item *ItemReaderVisitorState::mostDerivingItem() const diff --git a/src/lib/corelib/language/itemreadervisitorstate.h b/src/lib/corelib/loader/itemreadervisitorstate.h index 6db9d3613..cbf1966b6 100644 --- a/src/lib/corelib/language/itemreadervisitorstate.h +++ b/src/lib/corelib/loader/itemreadervisitorstate.h @@ -39,40 +39,42 @@ #ifndef QBS_ITEMREADERVISITORSTATE_H #define QBS_ITEMREADERVISITORSTATE_H -#include <logging/logger.h> +#include <tools/deprecationwarningmode.h> #include <tools/set.h> #include <QtCore/qstringlist.h> +#include <memory> + namespace qbs { namespace Internal { class Item; class ItemPool; +class ItemReaderCache; +class Logger; class ItemReaderVisitorState { public: - ItemReaderVisitorState(Logger &logger); - ~ItemReaderVisitorState(); + ItemReaderVisitorState(ItemReaderCache &cache, Logger &logger); - Set<QString> filesRead() const { return m_filesRead; } + Logger &logger() { return m_logger; } Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool); - void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); - bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; + void findDirectoryEntries(const QString &dirPath, QStringList *entries) const; Item *mostDerivingItem() const; void setMostDerivingItem(Item *item); + void setDeprecationWarningMode(DeprecationWarningMode mode) { m_deprecationWarningMode = mode; } + DeprecationWarningMode deprecationWarningMode() const { return m_deprecationWarningMode; } + private: + DeprecationWarningMode m_deprecationWarningMode = defaultDeprecationWarningMode(); + ItemReaderCache &m_cache; Logger &m_logger; - Set<QString> m_filesRead; - QHash<QString, QStringList> m_directoryEntries; Item *m_mostDerivingItem = nullptr; - - class ASTCache; - ASTCache * const m_astCache; }; } // namespace Internal diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp new file mode 100644 index 000000000..05a077dbe --- /dev/null +++ b/src/lib/corelib/loader/loaderutils.cpp @@ -0,0 +1,961 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "loaderutils.h" + +#include "itemreader.h" + +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/itempool.h> +#include <language/language.h> +#include <language/resolvedfilecontext.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/progressobserver.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +namespace qbs::Internal { + +QString fullProductDisplayName(const QString &name, const QString &multiplexId) +{ + static const auto multiplexIdToString =[](const QString &id) { + return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8())); + }; + QString result = name; + if (!multiplexId.isEmpty()) + result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexId)); + return result; +} + +void adjustParametersScopes(Item *item, Item *scope) +{ + if (item->type() == ItemType::ModuleParameters) { + item->setScope(scope); + return; + } + + for (const auto &value : item->properties()) { + if (value->type() == Value::ItemValueType) + adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope); + } +} + +QString ProductContext::uniqueName() const +{ + return ResolvedProduct::uniqueName(name, multiplexConfigurationId); +} + +QString ProductContext::displayName() const +{ + return fullProductDisplayName(name, multiplexConfigurationId); +} + +void ProductContext::handleError(const ErrorInfo &error) +{ + const bool alreadyHadError = delayedError.hasError(); + if (!alreadyHadError) { + delayedError.append(Tr::tr("Error while handling product '%1':") + .arg(name), item->location()); + } + if (error.isInternalError()) { + if (alreadyHadError) { + qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString() + << "in product" << name; + return; + } + } + const auto errorItems = error.items(); + for (const ErrorItem &ei : errorItems) + delayedError.append(ei.description(), ei.codeLocation()); + project->topLevelProject->addDisabledItem(item); +} + +TopLevelProjectContext::~TopLevelProjectContext() { qDeleteAll(m_projects); } + +bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator) +{ + if (evaluator.boolValue(item, StringConstants::conditionProperty())) + return true; + addDisabledItem(item); + return false; +} + +void TopLevelProjectContext::checkCancelation() +{ + if (m_progressObserver && m_progressObserver->canceled()) + m_canceled = true; + if (m_canceled) + throw CancelException(); +} + +QString TopLevelProjectContext::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) +{ + const auto sourceCodeGuard = m_sourceCode.lock(); + QString &code = sourceCodeGuard.get()[value->sourceCode()]; + if (!code.isNull()) + return code; + code = value->sourceCodeForEvaluation(); + return code; +} + +ScriptFunctionPtr TopLevelProjectContext::scriptFunctionValue(Item *item, const QString &name) +{ + const JSSourceValuePtr value = item->sourceProperty(name); + QBS_CHECK(value); + const auto scriptFunctionMapGuard = m_scriptFunctionMap.lock(); + ScriptFunctionPtr &script = scriptFunctionMapGuard.get()[value->location()]; + if (!script.get()) { + script = ScriptFunction::create(); + const PropertyDeclaration decl = item->propertyDeclaration(name); + script->sourceCode = sourceCodeAsFunction(value, decl); + script->location = value->location(); + script->fileContext = resolvedFileContext(value->file()); + } + return script; +} + +QString TopLevelProjectContext::sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) +{ + const auto scriptFunctionMapGuard = m_scriptFunctions.lock(); + QString &scriptFunction = scriptFunctionMapGuard.get()[std::make_pair(value->sourceCode(), + decl.functionArgumentNames())]; + if (!scriptFunction.isNull()) + return scriptFunction; + const QString args = decl.functionArgumentNames().join(QLatin1Char(',')); + if (value->hasFunctionForm()) { + // Insert the argument list. + scriptFunction = value->sourceCodeForEvaluation(); + scriptFunction.insert(10, args); + // Remove the function application "()" that has been + // added in ItemReaderASTVisitor::visitStatement. + scriptFunction.chop(2); + } else { + scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ") + + value->sourceCode().toString() + QLatin1String(";})"); + } + return scriptFunction; +} + +const ResolvedFileContextPtr &TopLevelProjectContext::resolvedFileContext( + const FileContextConstPtr &ctx) +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) + result = ResolvedFileContext::create(*ctx); + return result; +} + +void TopLevelProjectContext::removeProductToHandle(const ProductContext &product) +{ + std::unique_lock lock(m_productsToHandle.mutex); + m_productsToHandle.data.remove(&product); +} + +bool TopLevelProjectContext::isProductQueuedForHandling(const ProductContext &product) const +{ + std::shared_lock lock(m_productsToHandle.mutex); + return m_productsToHandle.data.contains(&product); +} + +void TopLevelProjectContext::addDisabledItem(Item *item) +{ + m_disabledItems.lock().get() << item; +} + +bool TopLevelProjectContext::isDisabledItem(const Item *item) const +{ + return m_disabledItems.lock_shared().get().contains(item); +} + +void TopLevelProjectContext::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +ProgressObserver *TopLevelProjectContext::progressObserver() const { return m_progressObserver; } + +void TopLevelProjectContext::addQueuedError(const ErrorInfo &error) +{ + m_queuedErrors.lock().get() << error; +} + +void TopLevelProjectContext::addProfileConfig(const QString &profileName, + const QVariantMap &profileConfig) +{ + m_profileConfigs.insert(profileName, profileConfig); +} + +std::optional<QVariantMap> TopLevelProjectContext::profileConfig(const QString &profileName) const +{ + const auto it = m_profileConfigs.constFind(profileName); + if (it == m_profileConfigs.constEnd()) + return {}; + return it.value().toMap(); +} + +void TopLevelProjectContext::addProjectLevelProbe(const ProbeConstPtr &probe) +{ + m_probesInfo.projectLevelProbes << probe; +} + +const std::vector<ProbeConstPtr> TopLevelProjectContext::projectLevelProbes() const +{ + return m_probesInfo.projectLevelProbes; +} + +void TopLevelProjectContext::addProduct(ProductContext &product) +{ + m_productsByName.insert({product.name, &product}); +} + +void TopLevelProjectContext::addProductByType(ProductContext &product, const FileTags &tags) +{ + const auto productsByTypeGuard = m_productsByType.lock(); + for (const FileTag &tag : tags) + productsByTypeGuard.get().insert({tag, &product}); +} + +ProductContext *TopLevelProjectContext::productWithNameAndConstraint( + const QString &name, const std::function<bool (ProductContext &)> &constraint) +{ + const auto candidates = m_productsByName.equal_range(name); + for (auto it = candidates.first; it != candidates.second; ++it) { + ProductContext * const candidate = it->second; + if (constraint(*candidate)) + return candidate; + } + return nullptr; +} + +std::vector<ProductContext *> TopLevelProjectContext::productsWithNameAndConstraint( + const QString &name, const std::function<bool (ProductContext &)> &constraint) +{ + std::vector<ProductContext *> result; + const auto candidates = m_productsByName.equal_range(name); + for (auto it = candidates.first; it != candidates.second; ++it) { + ProductContext * const candidate = it->second; + if (constraint(*candidate)) + result << candidate; + } + return result; +} + +std::vector<ProductContext *> TopLevelProjectContext::productsWithTypeAndConstraint( + const FileTags &tags, const std::function<bool (ProductContext &)> &constraint) +{ + const auto productsByTypeGuard = m_productsByType.lock_shared(); + std::vector<ProductContext *> matchingProducts; + for (const FileTag &typeTag : tags) { + const auto range = productsByTypeGuard.get().equal_range(typeTag); + for (auto it = range.first; it != range.second; ++it) { + if (constraint(*it->second)) + matchingProducts.push_back(it->second); + } + } + return matchingProducts; +} + +std::vector<std::pair<ProductContext *, CodeLocation>> +TopLevelProjectContext::finishedProductsWithBulkDependency(const FileTag &tag) const +{ + return m_reverseBulkDependencies.value(tag); +} + +void TopLevelProjectContext::registerBulkDependencies(ProductContext &product) +{ + for (const auto &tagAndLoc : product.bulkDependencies) { + for (const FileTag &tag : tagAndLoc.first) + m_reverseBulkDependencies[tag].emplace_back(&product, tagAndLoc.second); + } +} + +void TopLevelProjectContext::addProjectNameUsedInOverrides(const QString &name) +{ + m_projectNamesUsedInOverrides << name; +} + +const Set<QString> &TopLevelProjectContext::projectNamesUsedInOverrides() const +{ + return m_projectNamesUsedInOverrides; +} + +void TopLevelProjectContext::addProductNameUsedInOverrides(const QString &name) +{ + m_productNamesUsedInOverrides << name; +} + +const Set<QString> &TopLevelProjectContext::productNamesUsedInOverrides() const +{ + return m_productNamesUsedInOverrides; +} + +void TopLevelProjectContext::addMultiplexConfiguration(const QString &id, const QVariantMap &config) +{ + m_multiplexConfigsById.insert(std::make_pair(id, config)); +} + +QVariantMap TopLevelProjectContext::multiplexConfiguration(const QString &id) const +{ + if (id.isEmpty()) + return {}; + const auto it = m_multiplexConfigsById.find(id); + QBS_CHECK(it != m_multiplexConfigsById.end() && !it->second.isEmpty()); + return it->second; +} + +std::lock_guard<std::mutex> TopLevelProjectContext::moduleProvidersCacheLock() +{ + return std::lock_guard<std::mutex>(m_moduleProvidersCacheMutex); +} + +void TopLevelProjectContext::setModuleProvidersCache(const ModuleProvidersCache &cache) +{ + m_moduleProvidersCache = cache; +} + +ModuleProviderInfo *TopLevelProjectContext::moduleProvider(const ModuleProvidersCacheKey &key) +{ + if (const auto it = m_moduleProvidersCache.find(key); it != m_moduleProvidersCache.end()) + return &(*it); + return nullptr; +} + +ModuleProviderInfo &TopLevelProjectContext::addModuleProvider(const ModuleProvidersCacheKey &key, + const ModuleProviderInfo &provider) +{ + return m_moduleProvidersCache[key] = provider; +} + +void TopLevelProjectContext::addParameterDeclarations(const Item *moduleProto, + const Item::PropertyDeclarationMap &decls) +{ + m_parameterDeclarations.lock().get().insert({moduleProto, decls}); +} + +Item::PropertyDeclarationMap TopLevelProjectContext::parameterDeclarations(Item *moduleProto) const +{ + const auto parameterDeclarationsGuard = m_parameterDeclarations.lock_shared(); + const auto &declarations = parameterDeclarationsGuard.get(); + if (const auto it = declarations.find(moduleProto); it != declarations.end()) { + return it->second; + } + return {}; +} + +void TopLevelProjectContext::setParameters(const Item *moduleProto, const QVariantMap ¶meters) +{ + m_parameters.lock().get().insert({moduleProto, parameters}); +} + +QVariantMap TopLevelProjectContext::parameters(Item *moduleProto) const +{ + const auto parametersGuard = m_parameters.lock_shared(); + const auto ¶meters = parametersGuard.get(); + if (const auto it = parameters.find(moduleProto); it != parameters.end()) { + return it->second; + } + return {}; +} + +void TopLevelProjectContext::addCodeLink(const QString &sourceFile, const CodeRange &sourceRange, + const CodeLocation &target) +{ + const auto codeLinksGuard = m_codeLinks.lock(); + QList<CodeLocation> &links = codeLinksGuard.get()[sourceFile][sourceRange]; + if (!links.contains(target)) + links << target; +} + +QString TopLevelProjectContext::findModuleDirectory( + const QualifiedId &module, const QString &searchPath, + const std::function<QString()> &findOnDisk) +{ + const auto modulePathCacheGuard = m_modulePathCache.lock(); + auto &path = modulePathCacheGuard.get()[{searchPath, module}]; + if (!path) + path = findOnDisk(); + return *path; +} + +QStringList TopLevelProjectContext::getModuleFilesForDirectory( + const QString &dir, const std::function<QStringList ()> &findOnDisk) +{ + const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock(); + auto &list = moduleFilesGuard.get()[dir]; + if (!list) + list = findOnDisk(); + return *list; +} + +void TopLevelProjectContext::removeModuleFileFromDirectoryCache(const QString &filePath) +{ + const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock(); + auto &moduleFiles = moduleFilesGuard.get(); + const auto it = moduleFiles.find(FileInfo::path(filePath)); + QBS_CHECK(it != moduleFiles.end()); + auto &files = it->second; + QBS_CHECK(files); + files->removeOne(filePath); +} + +void TopLevelProjectContext::addUnknownProfilePropertyError(const Item *moduleProto, + const ErrorInfo &error) +{ + m_unknownProfilePropertyErrors.lock().get()[moduleProto].push_back(error); +} + +const std::vector<ErrorInfo> &TopLevelProjectContext::unknownProfilePropertyErrors( + const Item *moduleProto) const +{ + const auto errorsGuard = m_unknownProfilePropertyErrors.lock_shared(); + const auto &errors = errorsGuard.get(); + if (const auto it = errors.find(moduleProto); it != errors.end()) { + return it->second; + } + static const std::vector<ErrorInfo> empty; + return empty; +} + +Item *TopLevelProjectContext::getModulePrototype(const QString &filePath, const QString &profile, + const std::function<Item *()> &produce) +{ + const auto modulePrototypesGuard = m_modulePrototypes.lock(); + auto &prototypeList = modulePrototypesGuard.get()[filePath]; + for (const auto &prototype : prototypeList) { + if (prototype.second == profile) + return prototype.first; + } + Item * const module = produce(); + if (module) + prototypeList.emplace_back(module, profile); + return module; +} + +void TopLevelProjectContext::addLocalProfile(const QString &name, const QVariantMap &values, + const CodeLocation &location) +{ + if (m_localProfiles.contains(name)) + throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(name), location); + m_localProfiles.insert(name, values); +} + +void TopLevelProjectContext::checkForLocalProfileAsTopLevelProfile(const QString &topLevelProfile) +{ + for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) { + if (it.key() != topLevelProfile) + continue; + + // This covers the edge case that a locally defined profile was specified as the + // top-level profile, in which case we must invalidate the qbs module prototype that was + // created in early setup before local profiles were handled. + const auto prototypesGuard = m_modulePrototypes.lock(); + auto &modulePrototypes = prototypesGuard.get(); + QBS_CHECK(modulePrototypes.size() == 1); + modulePrototypes.clear(); + break; + } +} + +std::lock_guard<std::mutex> TopLevelProjectContext::probesCacheLock() +{ + return std::lock_guard<std::mutex>(m_probesMutex); +} + +void TopLevelProjectContext::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) +{ + for (const ProbeConstPtr& probe : oldProbes) + m_probesInfo.oldProjectProbes[probe->globalId()] << probe; +} + +ProbeConstPtr TopLevelProjectContext::findOldProjectProbe(const QString &id, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProjectProbes.value(id)) { + if (filter(oldProbe)) + return oldProbe; + } + return {}; +} + +void TopLevelProjectContext::setOldProductProbes( + const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) +{ + m_probesInfo.oldProductProbes = oldProbes; +} + +ProbeConstPtr TopLevelProjectContext::findOldProductProbe(const QString &productName, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProductProbes.value(productName)) { + if (filter(oldProbe)) + return oldProbe; + } + return {}; +} + +void TopLevelProjectContext::addNewlyResolvedProbe(const ProbeConstPtr &probe) +{ + m_probesInfo.currentProbes[probe->location()] << probe; +} + +ProbeConstPtr TopLevelProjectContext::findCurrentProbe(const CodeLocation &location, + const ProbeFilter &filter) const +{ + for (const ProbeConstPtr &probe : m_probesInfo.currentProbes.value(location)) { + if (filter(probe)) + return probe; + } + return {}; +} + +void TopLevelProjectContext::collectDataFromEngine(const ScriptEngine &engine) +{ + const auto project = dynamic_cast<TopLevelProject *>(m_projects.front()->project.get()); + QBS_CHECK(project); + project->canonicalFilePathResults.insert(engine.canonicalFilePathResults()); + project->fileExistsResults.insert(engine.fileExistsResults()); + project->directoryEntriesResults.insert(engine.directoryEntriesResults()); + project->fileLastModifiedResults.insert(engine.fileLastModifiedResults()); + project->environment.insert(engine.environment()); + project->buildSystemFiles.unite(engine.imports()); +} + +ItemPool &TopLevelProjectContext::createItemPool() +{ + m_itemPools.push_back(std::make_unique<ItemPool>()); + return *m_itemPools.back(); +} + +class LoaderState::Private +{ +public: + Private(LoaderState &q, const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, ItemPool &itemPool, ScriptEngine &engine, + Logger &&logger) + : parameters(parameters), topLevelProject(topLevelProject), itemPool(itemPool), + logger(std::move(logger)), itemReader(q), evaluator(&engine) + { + this->logger.clearWarnings(); + this->logger.storeWarnings(); + } + + const SetupProjectParameters ¶meters; + TopLevelProjectContext &topLevelProject; + ItemPool &itemPool; + + Logger logger; + ItemReader itemReader; + Evaluator evaluator; +}; + +LoaderState::LoaderState(const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, ItemPool &itemPool, + ScriptEngine &engine, Logger logger) + : d(makePimpl<Private>(*this, parameters, topLevelProject, itemPool, engine, std::move(logger))) +{ + d->itemReader.init(); +} + +LoaderState::~LoaderState() = default; +const SetupProjectParameters &LoaderState::parameters() const { return d->parameters; } +ItemPool &LoaderState::itemPool() { return d->itemPool; } +Evaluator &LoaderState::evaluator() { return d->evaluator; } +Logger &LoaderState::logger() { return d->logger; } +ItemReader &LoaderState::itemReader() { return d->itemReader; } +TopLevelProjectContext &LoaderState::topLevelProject() { return d->topLevelProject; } + +static QString verbatimValue(LoaderState &state, const ValueConstPtr &value) +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = std::static_pointer_cast<const JSSourceValue>( + value); + result = state.topLevelProject().sourceCodeForEvaluation(sourceValue); + } + return result; +} + +static void resolveRuleArtifactBinding( + LoaderState &state, const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, QualifiedIdSet *seenBindings) +{ + for (auto it = item->properties().constBegin(); it != item->properties().constEnd(); ++it) { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(state, ruleArtifact, + std::static_pointer_cast<ItemValue>(it.value())->item(), + name, seenBindings); + } else if (it.value()->type() == Value::JSSourceValueType) { + const auto insertResult = seenBindings->insert(name); + if (!insertResult.second) + continue; + JSSourceValuePtr sourceValue = std::static_pointer_cast<JSSourceValue>(it.value()); + RuleArtifact::Binding rab; + rab.name = name; + rab.code = state.topLevelProject().sourceCodeForEvaluation(sourceValue); + rab.location = sourceValue->location(); + ruleArtifact->bindings.push_back(rab); + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +static void resolveRuleArtifact(LoaderState &state, const RulePtr &rule, Item *item) +{ + RuleArtifactPtr artifact = RuleArtifact::create(); + rule->artifacts.push_back(artifact); + artifact->location = item->location(); + + if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty())) + artifact->filePathLocation = sourceProperty->location(); + + artifact->filePath = verbatimValue(state, item->property(StringConstants::filePathProperty())); + artifact->fileTags = state.evaluator().fileTagsValue(item, StringConstants::fileTagsProperty()); + artifact->alwaysUpdated = state.evaluator().boolValue( + item, StringConstants::alwaysUpdatedProperty()); + + QualifiedIdSet 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( + state, artifact, std::static_pointer_cast<ItemValue>(it.value())->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) + return; + + RulePtr rule = Rule::create(); + + // read artifacts + bool hasArtifactChildren = false; + for (Item * const child : item->children()) { + if (Q_UNLIKELY(child->type() != ItemType::Artifact)) { + throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), + child->location()); + } + hasArtifactChildren = true; + resolveRuleArtifact(state, rule, child); + } + + rule->name = evaluator.stringValue(item, StringConstants::nameProperty()); + rule->prepareScript.initialize(state.topLevelProject().scriptFunctionValue( + item, StringConstants::prepareProperty())); + rule->outputArtifactsScript.initialize(state.topLevelProject().scriptFunctionValue( + item, StringConstants::outputArtifactsProperty())); + rule->outputFileTags = evaluator.fileTagsValue(item, StringConstants::outputFileTagsProperty()); + if (rule->outputArtifactsScript.isValid()) { + if (hasArtifactChildren) + throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " + "that contain Artifact items."), + item->location()); + } + if (!hasArtifactChildren && rule->outputFileTags.empty()) { + throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty " + "outputFileTags property."), item->location()); + } + rule->multiplex = evaluator.boolValue(item, StringConstants::multiplexProperty()); + rule->alwaysRun = evaluator.boolValue(item, StringConstants::alwaysRunProperty()); + rule->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty()); + rule->inputsFromDependencies + = evaluator.fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); + bool requiresInputsSet = false; + rule->requiresInputs = evaluator.boolValue(item, StringConstants::requiresInputsProperty(), + &requiresInputsSet); + if (!requiresInputsSet) + rule->requiresInputs = rule->declaresInputs(); + rule->auxiliaryInputs + = evaluator.fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); + rule->excludedInputs + = evaluator.fileTagsValue(item, StringConstants::excludedInputsProperty()); + if (rule->excludedInputs.empty()) { + rule->excludedInputs = evaluator.fileTagsValue( + item, StringConstants::excludedAuxiliaryInputsProperty()); + } + rule->explicitlyDependsOn + = evaluator.fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); + rule->explicitlyDependsOnFromDependencies = evaluator.fileTagsValue( + item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); + rule->module = moduleContext ? moduleContext->module : projectContext->dummyModule; + if (!rule->multiplex && !rule->declaresInputs()) { + throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."), + item->location()); + } + if (!rule->multiplex && !rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."), + item->location()); + } + if (!rule->declaresInputs() && rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule " + "does not declare any input tags."), item->location()); + } + if (productContext) { + rule->product = productContext->product.get(); + productContext->product->rules.push_back(rule); + } else { + projectContext->rules.push_back(rule); + } +} + +void resolveFileTagger(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) + return; + std::vector<FileTaggerConstPtr> &fileTaggers = productContext + ? productContext->product->fileTaggers + : projectContext->fileTaggers; + const QStringList patterns = evaluator.stringListValue(item, + StringConstants::patternsProperty()); + if (patterns.empty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + + const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty()); + if (fileTags.empty()) + throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); + + for (const QString &pattern : patterns) { + if (pattern.isEmpty()) + throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); + } + + const int priority = evaluator.intValue(item, StringConstants::priorityProperty()); + fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); +} + +void resolveJobLimit(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext) +{ + Evaluator &evaluator = state.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) + return; + const QString jobPool = evaluator.stringValue(item, StringConstants::jobPoolProperty()); + if (jobPool.isEmpty()) + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.") + .arg(StringConstants::jobPoolProperty()), item->location()); + bool jobCountWasSet; + const int jobCount = evaluator.intValue(item, StringConstants::jobCountProperty(), -1, + &jobCountWasSet); + if (!jobCountWasSet) { + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + if (jobCount < 0) { + throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + JobLimits &jobLimits = moduleContext + ? moduleContext->jobLimits + : productContext ? productContext->product->jobLimits + : projectContext->jobLimits; + JobLimit jobLimit(jobPool, jobCount); + const int oldLimit = jobLimits.getLimit(jobPool); + if (oldLimit == -1 || oldLimit > jobCount) + jobLimits.setJobLimit(jobLimit); +} + +const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +bool ProductContext::dependenciesResolvingPending() const +{ + return (!dependenciesContext || !dependenciesContext->dependenciesResolved) + && !product && !delayedError.hasError(); +} + +std::pair<ProductDependency, ProductContext *> ProductContext::pendingDependency() const +{ + return dependenciesContext ? dependenciesContext->pendingDependency() + : std::make_pair(ProductDependency::None, nullptr); +} + +TimingData &TimingData::operator+=(const TimingData &other) +{ + dependenciesResolving += other.dependenciesResolving; + moduleProviders += other.moduleProviders; + moduleInstantiation += other.moduleInstantiation; + propertyMerging += other.propertyMerging; + groupsSetup += other.groupsSetup; + groupsResolving += other.groupsResolving; + preparingProducts += other.preparingProducts; + resolvingProducts += other.resolvingProducts; + probes += other.probes; + propertyEvaluation += other.propertyEvaluation; + propertyChecking += other.propertyChecking; + return *this; +} + +DependenciesContext::~DependenciesContext() = default; + +ItemReaderCache::AstCacheEntry &ItemReaderCache::retrieveOrSetupCacheEntry( + const QString &filePath, const std::function<void (AstCacheEntry &)> &setup) +{ + const auto astCacheGuard = m_astCache.lock(); + AstCacheEntry &entry = astCacheGuard.get()[filePath]; + if (!entry.ast) { + setup(entry); + m_filesRead << filePath; + } + return entry; +} + +const QStringList &ItemReaderCache::retrieveOrSetDirectoryEntries( + const QString &dir, const std::function<QStringList ()> &findOnDisk) +{ + const auto directoryEntriesGuard = m_directoryEntries.lock(); + auto &entries = directoryEntriesGuard.get()[dir]; + if (!entries) + entries = findOnDisk(); + return *entries; +} + +bool ItemReaderCache::AstCacheEntry::addProcessingThread() +{ + return m_processingThreads.lock().get().insert(std::this_thread::get_id()).second; +} + +void ItemReaderCache::AstCacheEntry::removeProcessingThread() +{ + m_processingThreads.lock().get().remove(std::this_thread::get_id()); +} + +class DependencyParametersMerger +{ +public: + DependencyParametersMerger(std::vector<Item::Module::ParametersWithPriority> &&candidates) + : m_candidates(std::move(candidates)) { } + QVariantMap merge(); + +private: + void merge(QVariantMap ¤t, const QVariantMap &next, int nextPrio); + + const std::vector<Item::Module::ParametersWithPriority> m_candidates; + + struct Conflict { + Conflict(QStringList path, QVariant val1, QVariant val2, int prio) + : path(std::move(path)), val1(std::move(val1)), val2(std::move(val2)), priority(prio) {} + QStringList path; + QVariant val1; + QVariant val2; + int priority; + }; + std::vector<Conflict> m_conflicts; + QVariantMap m_currentValue; + int m_currentPrio = INT_MIN; + QStringList m_path; +}; + +QVariantMap mergeDependencyParameters(std::vector<Item::Module::ParametersWithPriority> &&candidates) +{ + return DependencyParametersMerger(std::move(candidates)).merge(); +} + +QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2) +{ + return mergeDependencyParameters({std::make_pair(m1, 0), std::make_pair(m2, 0)}); +} + +QVariantMap DependencyParametersMerger::merge() +{ + for (const auto &next : m_candidates) { + merge(m_currentValue, next.first, next.second); + m_currentPrio = next.second; + } + + if (!m_conflicts.empty()) { + ErrorInfo error(Tr::tr("Conflicting parameter values encountered:")); + for (const Conflict &conflict : m_conflicts) { + // TODO: Location would be nice ... + error.append(Tr::tr(" Parameter '%1' cannot be both '%2' and '%3'.") + .arg(conflict.path.join(QLatin1Char('.')), + conflict.val1.toString(), conflict.val2.toString())); + } + throw error; + } + + return m_currentValue; +} + +void DependencyParametersMerger::merge(QVariantMap ¤t, const QVariantMap &next, int nextPrio) +{ + for (auto it = next.begin(); it != next.end(); ++it) { + m_path << it.key(); + const QVariant &newValue = it.value(); + QVariant ¤tValue = current[it.key()]; + if (newValue.userType() == QMetaType::QVariantMap) { + QVariantMap mdst = currentValue.toMap(); + merge(mdst, it.value().toMap(), nextPrio); + currentValue = mdst; + } else { + if (m_currentPrio == nextPrio) { + if (currentValue.isValid() && !qVariantsEqual(currentValue, newValue)) + m_conflicts.emplace_back(m_path, currentValue, newValue, m_currentPrio); + } else { + removeIf(m_conflicts, [this](const Conflict &conflict) { + return m_path == conflict.path; + }); + } + currentValue = newValue; + } + m_path.removeLast(); + } +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h new file mode 100644 index 000000000..4a4aadfb8 --- /dev/null +++ b/src/lib/corelib/loader/loaderutils.h @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <language/filetags.h> +#include <language/forward_decls.h> +#include <language/item.h> +#include <language/moduleproviderinfo.h> +#include <language/propertydeclaration.h> +#include <language/qualifiedid.h> +#include <parser/qmljsengine_p.h> +#include <tools/codelocation.h> +#include <tools/filetime.h> +#include <tools/mutexdata.h> +#include <tools/joblimits.h> +#include <tools/pimpl.h> +#include <tools/set.h> +#include <tools/version.h> + +#include <QHash> +#include <QStringList> +#include <QVariant> + +#include <atomic> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <shared_mutex> +#include <thread> +#include <utility> +#include <vector> + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class Evaluator; +class ItemPool; +class ItemReader; +class Logger; +class ProductContext; +class ProgressObserver; +class ProjectContext; +class ScriptEngine; + +using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>; +using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>; + +enum class Deferral { Allowed, NotAllowed }; +enum class ProductDependency { None, Single, Bulk }; + +class CancelException { }; + +template<typename DataType, typename MutexType = std::shared_mutex> +struct GuardedData { + DataType data; + mutable MutexType mutex; +}; + +class TimingData +{ +public: + TimingData &operator+=(const TimingData &other); + + qint64 dependenciesResolving = 0; + qint64 moduleProviders = 0; + qint64 moduleInstantiation = 0; + qint64 propertyMerging = 0; + qint64 groupsSetup = 0; + qint64 groupsResolving = 0; + qint64 preparingProducts = 0; + qint64 resolvingProducts = 0; + qint64 schedulingProducts = 0; + qint64 probes = 0; + qint64 propertyEvaluation = 0; + qint64 propertyChecking = 0; +}; + +class ItemReaderCache +{ +public: + class AstCacheEntry + { + public: + QString code; + QbsQmlJS::Engine engine; + QbsQmlJS::AST::UiProgram *ast = nullptr; + + bool addProcessingThread(); + void removeProcessingThread(); + + private: + MutexData<Set<std::thread::id>, std::recursive_mutex> m_processingThreads; + }; + + const Set<QString> &filesRead() const { return m_filesRead; } + AstCacheEntry &retrieveOrSetupCacheEntry(const QString &filePath, + const std::function<void(AstCacheEntry &)> &setup); + const QStringList &retrieveOrSetDirectoryEntries( + const QString &dir, const std::function<QStringList()> &findOnDisk); + +private: + Set<QString> m_filesRead; + MutexData<std::unordered_map<QString, std::optional<QStringList>>, std::mutex> m_directoryEntries; // TODO: Merge with module dir entries cache? + MutexData<std::unordered_map<QString, AstCacheEntry>, std::mutex> m_astCache; +}; + +class DependenciesContext +{ +public: + virtual ~DependenciesContext(); + virtual std::pair<ProductDependency, ProductContext *> pendingDependency() const = 0; + + bool dependenciesResolved = false; +}; + +class ProductContext +{ +public: + QString uniqueName() const; + QString displayName() const; + void handleError(const ErrorInfo &error); + bool dependenciesResolvingPending() const; + std::pair<ProductDependency, ProductContext *> pendingDependency() const; + + QString name; + QString buildDirectory; + Item *item = nullptr; + Item *scope = nullptr; + ProjectContext *project = nullptr; + std::unique_ptr<ProductContext> shadowProduct; + Item *mergedExportItem = nullptr; + std::vector<ProbeConstPtr> probes; + ModulePropertiesPerGroup modulePropertiesSetInGroups; + ErrorInfo delayedError; + QString profileName; + QString multiplexConfigurationId; + QVariantMap profileModuleProperties; // Tree-ified module properties from profile. + QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values. + std::optional<QVariantMap> providerConfig; + std::optional<QVariantMap> providerQbsModule; + QVariantMap defaultParameters; // In Export item. + QStringList searchPaths; + ResolvedProductPtr product; + TimingData timingData; + std::unique_ptr<DependenciesContext> dependenciesContext; + + // This is needed because complex cyclic dependencies that involve Depends.productTypes + // may only be detected after a product has already been fully resolved. + std::vector<std::pair<FileTags, CodeLocation>> bulkDependencies; + + // The keys are module prototypes, the values specify whether the module's + // condition is true for this product. + std::unordered_map<Item *, bool> modulePrototypeEnabledInfo; + + int dependsItemCount = -1; +}; + +class TopLevelProjectContext +{ +public: + TopLevelProjectContext() = default; + TopLevelProjectContext(const TopLevelProjectContext &) = delete; + TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete; + ~TopLevelProjectContext(); + + bool checkItemCondition(Item *item, Evaluator &evaluator); + QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value); + ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name); + QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl); + + void setCanceled() { m_canceled = true; } + void checkCancelation(); + bool isCanceled() const { return m_canceled; } + + int productCount() const { return m_productsByName.size(); } + + void addProductToHandle(const ProductContext &product) { m_productsToHandle.data << &product; } + void removeProductToHandle(const ProductContext &product); + bool isProductQueuedForHandling(const ProductContext &product) const; + int productsToHandleCount() const { return m_productsToHandle.data.size(); } + + void addDisabledItem(Item *item); + bool isDisabledItem(const Item *item) const; + + void setProgressObserver(ProgressObserver *observer); + ProgressObserver *progressObserver() const; + + void addProject(ProjectContext *project) { m_projects.push_back(project); } + const std::vector<ProjectContext *> &projects() const { return m_projects; } + + using QueuedErrors = MutexData<std::vector<ErrorInfo>, std::mutex>; + void addQueuedError(const ErrorInfo &error); + QueuedErrors::UniqueConstGuard queuedErrors() const { return m_queuedErrors.lock(); } + + void setProfileConfigs(const QVariantMap &profileConfigs) { m_profileConfigs = profileConfigs; } + void addProfileConfig(const QString &profileName, const QVariantMap &profileConfig); + const QVariantMap &profileConfigs() const { return m_profileConfigs; } + std::optional<QVariantMap> profileConfig(const QString &profileName) const; + + void addProduct(ProductContext &product); + void addProductByType(ProductContext &product, const FileTags &tags); + ProductContext *productWithNameAndConstraint( + const QString &name, const std::function<bool(ProductContext &)> &constraint); + std::vector<ProductContext *> productsWithNameAndConstraint( + const QString &name, const std::function<bool(ProductContext &)> &constraint); + std::vector<ProductContext *> productsWithTypeAndConstraint( + const FileTags &tags, const std::function<bool(ProductContext &)> &constraint); + std::vector<std::pair<ProductContext *, CodeLocation>> + finishedProductsWithBulkDependency(const FileTag &tag) const; + void registerBulkDependencies(ProductContext &product); + + void addProjectNameUsedInOverrides(const QString &name); + const Set<QString> &projectNamesUsedInOverrides() const; + + void addProductNameUsedInOverrides(const QString &name); + const Set<QString> &productNamesUsedInOverrides() const; + + void setBuildDirectory(const QString &buildDir) { m_buildDirectory = buildDir; } + const QString &buildDirectory() const { return m_buildDirectory; } + + void addMultiplexConfiguration(const QString &id, const QVariantMap &config); + QVariantMap multiplexConfiguration(const QString &id) const; + + void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } + const FileTime &lastResolveTime() const { return m_lastResolveTime; } + + Set<QString> buildSystemFiles() const { return m_itemReaderCache.filesRead(); } + + std::lock_guard<std::mutex> moduleProvidersCacheLock(); + void setModuleProvidersCache(const ModuleProvidersCache &cache); + const ModuleProvidersCache &moduleProvidersCache() const { return m_moduleProvidersCache; } + ModuleProviderInfo *moduleProvider(const ModuleProvidersCacheKey &key); + ModuleProviderInfo &addModuleProvider(const ModuleProvidersCacheKey &key, + const ModuleProviderInfo &provider); + + void addParameterDeclarations(const Item *moduleProto, + const Item::PropertyDeclarationMap &decls); + Item::PropertyDeclarationMap parameterDeclarations(Item *moduleProto) const; + + void setParameters(const Item *moduleProto, const QVariantMap ¶meters); + QVariantMap parameters(Item *moduleProto) const; + + void addCodeLink(const QString &sourceFile, const CodeRange &sourceRange, + const CodeLocation &target); + CodeLinks codeLinks() const { return m_codeLinks.lock().get(); } + + // An empty string means no matching module directory was found. + QString findModuleDirectory(const QualifiedId &module, const QString &searchPath, + const std::function<QString()> &findOnDisk); + + QStringList getModuleFilesForDirectory(const QString &dir, + const std::function<QStringList()> &findOnDisk); + void removeModuleFileFromDirectoryCache(const QString &filePath); + + void addUnknownProfilePropertyError(const Item *moduleProto, const ErrorInfo &error); + const std::vector<ErrorInfo> &unknownProfilePropertyErrors(const Item *moduleProto) const; + + Item *getModulePrototype(const QString &filePath, const QString &profile, + const std::function<Item *()> &produce); + + void addLocalProfile(const QString &name, const QVariantMap &values, + const CodeLocation &location); + const QVariantMap localProfiles() { return m_localProfiles; } + void checkForLocalProfileAsTopLevelProfile(const QString &topLevelProfile); + + using ProbeFilter = std::function<bool(const ProbeConstPtr &)>; + std::lock_guard<std::mutex> probesCacheLock(); + void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes); + void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); + void addNewlyResolvedProbe(const ProbeConstPtr &probe); + void addProjectLevelProbe(const ProbeConstPtr &probe); + const std::vector<ProbeConstPtr> projectLevelProbes() const; + ProbeConstPtr findOldProjectProbe(const QString &id, const ProbeFilter &filter) const; + ProbeConstPtr findOldProductProbe(const QString &productName, const ProbeFilter &filter) const; + ProbeConstPtr findCurrentProbe(const CodeLocation &location, const ProbeFilter &filter) const; + void incrementProbesCount() { ++m_probesInfo.probesEncountered; } + void incrementReusedCurrentProbesCount() { ++m_probesInfo.probesCachedCurrent; } + void incrementReusedOldProbesCount() { ++m_probesInfo.probesCachedOld; } + void incrementRunProbesCount() { ++m_probesInfo.probesRun; } + int probesEncounteredCount() const { return m_probesInfo.probesEncountered; } + int probesRunCount() const { return m_probesInfo.probesRun; } + int reusedOldProbesCount() const { return m_probesInfo.probesCachedOld; } + int reusedCurrentProbesCount() const { return m_probesInfo.probesCachedCurrent; } + + TimingData &timingData() { return m_timingData; } + ItemReaderCache &itemReaderCache() { return m_itemReaderCache; } + + void incProductDeferrals() { ++m_productDeferrals; } + int productDeferrals() const { return m_productDeferrals; } + + void collectDataFromEngine(const ScriptEngine &engine); + + ItemPool &createItemPool(); + +private: + const ResolvedFileContextPtr &resolvedFileContext(const FileContextConstPtr &ctx); + + std::vector<ProjectContext *> m_projects; + GuardedData<Set<const ProductContext *>> m_productsToHandle; + std::multimap<QString, ProductContext *> m_productsByName; + MutexData<std::unordered_map<QStringView, QString>, std::mutex> m_sourceCode; + std::unordered_map<QString, QVariantMap> m_multiplexConfigsById; + MutexData<QHash<CodeLocation, ScriptFunctionPtr>, std::mutex> m_scriptFunctionMap; + MutexData<std::unordered_map<std::pair<QStringView, QStringList>, QString>, + std::mutex> m_scriptFunctions; + std::unordered_map<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap; + Set<QString> m_projectNamesUsedInOverrides; + Set<QString> m_productNamesUsedInOverrides; + MutexData<Set<const Item *>> m_disabledItems; + QueuedErrors m_queuedErrors; + QString m_buildDirectory; + QVariantMap m_profileConfigs; + ProgressObserver *m_progressObserver = nullptr; + TimingData m_timingData; + ModuleProvidersCache m_moduleProvidersCache; + std::mutex m_moduleProvidersCacheMutex; + QVariantMap m_localProfiles; + ItemReaderCache m_itemReaderCache; + QHash<FileTag, std::vector<std::pair<ProductContext *, CodeLocation>>> m_reverseBulkDependencies; + + // For fast look-up when resolving Depends.productTypes. + // The contract is that it contains fully handled, error-free, enabled products. + MutexData<std::multimap<FileTag, ProductContext *>> m_productsByType; + + // The keys are module prototypes. + MutexData<std::unordered_map<const Item *, + Item::PropertyDeclarationMap>> m_parameterDeclarations; + MutexData<std::unordered_map<const Item *, QVariantMap>> m_parameters; + MutexData<std::unordered_map<const Item *, + std::vector<ErrorInfo>>> m_unknownProfilePropertyErrors; + + // The keys are search path + module name, the values are directories. + MutexData<QHash<std::pair<QString, QualifiedId>, std::optional<QString>>, + std::mutex> m_modulePathCache; + + // The keys are file paths, the values are module prototype items accompanied by a profile. + MutexData<std::unordered_map<QString, std::vector<std::pair<Item *, QString>>>, + std::mutex> m_modulePrototypes; + + MutexData<std::map<QString, std::optional<QStringList>>, + std::mutex> m_moduleFilesPerDirectory; + MutexData<CodeLinks> m_codeLinks; + + struct { + QHash<QString, std::vector<ProbeConstPtr>> oldProjectProbes; + QHash<QString, std::vector<ProbeConstPtr>> oldProductProbes; + QHash<CodeLocation, std::vector<ProbeConstPtr>> currentProbes; + std::vector<ProbeConstPtr> projectLevelProbes; + + quint64 probesEncountered = 0; + quint64 probesRun = 0; + quint64 probesCachedCurrent = 0; + quint64 probesCachedOld = 0; + } m_probesInfo; + std::mutex m_probesMutex; + + std::vector<std::unique_ptr<ItemPool>> m_itemPools; + + FileTime m_lastResolveTime; + + std::atomic_bool m_canceled = false; + int m_productDeferrals = 0; +}; + +class ProjectContext +{ +public: + QString name; + Item *item = nullptr; + Item *scope = nullptr; + TopLevelProjectContext *topLevelProject = nullptr; + ProjectContext *parent = nullptr; + std::vector<ProjectContext *> children; + std::vector<ProductContext> products; + std::vector<QStringList> searchPathsStack; + ResolvedProjectPtr project; + std::vector<FileTaggerConstPtr> fileTaggers; + std::vector<RulePtr> rules; + JobLimits jobLimits; + ResolvedModulePtr dummyModule; +}; + +class ModuleContext +{ +public: + ResolvedModulePtr module; + JobLimits jobLimits; +}; + +class LoaderState +{ +public: + LoaderState(const SetupProjectParameters ¶meters, TopLevelProjectContext &topLevelProject, + ItemPool &itemPool, ScriptEngine &engine, Logger logger); + ~LoaderState(); + + Evaluator &evaluator(); + ItemPool &itemPool(); + ItemReader &itemReader(); + Logger &logger(); + const SetupProjectParameters ¶meters() const; + TopLevelProjectContext &topLevelProject(); + +private: + class Private; + Pimpl<Private> d; +}; + +// List must be sorted by priority in ascending order. +[[nodiscard]] QVariantMap mergeDependencyParameters( + std::vector<Item::Module::ParametersWithPriority> &&candidates); +[[nodiscard]] QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2); + +QString fullProductDisplayName(const QString &name, const QString &multiplexId); +void adjustParametersScopes(Item *item, Item *scope); +void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext); +void resolveJobLimit(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext, ModuleContext *moduleContext); +void resolveFileTagger(LoaderState &state, Item *item, ProjectContext *projectContext, + ProductContext *productContext); +const FileTag unknownFileTag(); + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/localprofiles.cpp b/src/lib/corelib/loader/localprofiles.cpp new file mode 100644 index 000000000..e72128fff --- /dev/null +++ b/src/lib/corelib/loader/localprofiles.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "localprofiles.h" + +#include "loaderutils.h" + +#include <language/evaluator.h> +#include <language/item.h> +#include <language/qualifiedid.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/translator.h> +#include <tools/profile.h> +#include <tools/scripttools.h> +#include <tools/stringconstants.h> + +namespace qbs::Internal { +class LocalProfiles +{ +public: + LocalProfiles(LoaderState &loaderState) : m_loaderState(loaderState) {} + + void collectProfiles(Item *productOrProject, Item *projectScope); + +private: + void handleProfile(Item *profileItem); + void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, + QVariantMap &values); + LoaderState &m_loaderState; +}; + +void collectProfilesFromItems(Item *productOrProject, Item *projectScope, LoaderState &loaderState) +{ + LocalProfiles(loaderState).collectProfiles(productOrProject, projectScope); +} + +void LocalProfiles::handleProfile(Item *profileItem) +{ + QVariantMap values; + evaluateProfileValues(QualifiedId(), profileItem, profileItem, values); + const bool condition = values.take(StringConstants::conditionProperty()).toBool(); + if (!condition) + return; + const QString profileName = values.take(StringConstants::nameProperty()).toString(); + if (profileName.isEmpty()) + throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location()); + if (profileName == Profile::fallbackName()) { + throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.") + .arg(profileName), profileItem->location()); + } + m_loaderState.topLevelProject().addLocalProfile(profileName, values, profileItem->location()); +} + +void LocalProfiles::evaluateProfileValues(const QualifiedId &namePrefix, Item *item, + Item *profileItem, QVariantMap &values) +{ + const Item::PropertyMap &props = item->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QualifiedId name = namePrefix; + name << it.key(); + switch (it.value()->type()) { + case Value::ItemValueType: + evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(), + profileItem, values); + break; + case Value::VariantValueType: + values.insert(name.join(QLatin1Char('.')), + std::static_pointer_cast<VariantValue>(it.value())->value()); + break; + case Value::JSSourceValueType: + if (item != profileItem) + item->setScope(profileItem); + const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(), + m_loaderState.evaluator().value(item, it.key())); + values.insert(name.join(QLatin1Char('.')), + getJsVariant(m_loaderState.evaluator().engine()->context(), sv)); + break; + } + } +} + +void LocalProfiles::collectProfiles(Item *productOrProject, Item *projectScope) +{ + Item * scope = productOrProject->type() == ItemType::Project ? projectScope : nullptr; + for (auto it = productOrProject->children().begin(); + it != productOrProject->children().end();) { + Item * const childItem = *it; + if (childItem->type() == ItemType::Profile) { + if (!scope) { + const ItemValuePtr itemValue = ItemValue::create(productOrProject); + scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope); + scope->setProperty(StringConstants::productVar(), itemValue); + scope->setFile(productOrProject->file()); + scope->setScope(projectScope); + } + childItem->setScope(scope); + try { + handleProfile(childItem); + } catch (const ErrorInfo &e) { + handlePropertyError(e, m_loaderState.parameters(), m_loaderState.logger()); + } + it = productOrProject->children().erase(it); // TODO: delete item and scope + } else { + if (childItem->type() == ItemType::Product) + collectProfiles(childItem, projectScope); + ++it; + } + } +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/localprofiles.h b/src/lib/corelib/loader/localprofiles.h new file mode 100644 index 000000000..7d9eee88e --- /dev/null +++ b/src/lib/corelib/loader/localprofiles.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +namespace qbs::Internal { +class Item; +class LoaderState; + +void collectProfilesFromItems(Item *productOrProject, Item *projectScope, LoaderState &loaderState); + +} // namespace qbs::Internal + diff --git a/src/lib/corelib/loader/moduleinstantiator.cpp b/src/lib/corelib/loader/moduleinstantiator.cpp new file mode 100644 index 000000000..1c0359217 --- /dev/null +++ b/src/lib/corelib/loader/moduleinstantiator.cpp @@ -0,0 +1,340 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "moduleinstantiator.h" + +#include "loaderutils.h" +#include "modulepropertymerger.h" + +#include <language/item.h> +#include <language/itempool.h> +#include <language/qualifiedid.h> +#include <language/value.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/profiling.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <utility> + +namespace qbs::Internal { + +static std::pair<const Item *, Item *> +getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName, + const QString &id, bool replace, LoaderState &loaderState); + +class ModuleInstantiator +{ +public: + ModuleInstantiator(const InstantiationContext &context, LoaderState &loaderState) + : context(context), loaderState(loaderState) {} + + void instantiate(); + +private: + void overrideProperties(); + void setupScope(); + void exchangePlaceholderItem(Item *loadingItem, Item *moduleItemForItemValues); + + const InstantiationContext &context; + LoaderState &loaderState; +}; + +void ModuleInstantiator::instantiate() +{ + AccumulatingTimer timer(loaderState.parameters().logElapsedTime() + ? &context.product.timingData.moduleInstantiation : nullptr); + + // This part needs to be done only once per module and product, and only if the module + // was successfully loaded. + if (context.module && !context.alreadyLoaded) { + context.module->setType(ItemType::ModuleInstance); + overrideProperties(); + setupScope(); + } + + // This strange-looking code deals with the fact that our syntax cannot properly handle + // dependencies on several multiplexed variants of the same product. + // See getOrSetModuleInstanceItem() below for details. + Item * const moduleItemForItemValues + = context.moduleWithSameName ? context.moduleWithSameName + : context.module; + + // Now attach the module instance as an item value to the loading item, potentially + // evicting a previously attached placeholder item and merging its values into the instance. + // Note that we potentially do this twice, once for the actual loading item and once + // for the product item, if the two are different. The reason is this: + // For convenience, in the product item we allow attaching to properties from indirectly + // loaded modules. For instance: + // Product { + // Depends { name: "Qt.core" } + // cpp.cxxLanguageVersion: "c++20" // Works even though there is no Depends item for cpp + // } + // It's debatable whether that's a good feature, but it has been working (accidentally?) + // for a long time, and removing it now would break a lot of existing projects. + exchangePlaceholderItem(context.loadingItem, moduleItemForItemValues); + + if (!context.alreadyLoaded && context.product.item + && context.product.item != context.loadingItem) { + exchangePlaceholderItem(context.product.item, moduleItemForItemValues); + } +} + +void ModuleInstantiator::exchangePlaceholderItem(Item *loadingItem, Item *moduleItemForItemValues) +{ + // If we have a module item, set an item value pointing to it as a property on the loading item. + // Evict a possibly existing placeholder item, and return it to us, so we can merge its values + // into the instance. + const auto &[oldItem, newItem] = getOrSetModuleInstanceItem( + loadingItem, moduleItemForItemValues, context.moduleName, context.id, true, loaderState); + + // The new item always exists, even if we don't have a module item. In that case, the + // function created a placeholder item for us, which we then have to turn into a + // non-present module. + QBS_CHECK(newItem); + if (!moduleItemForItemValues) { + createNonPresentModule(loaderState.itemPool(), context.moduleName.toString(), + QLatin1String("not found"), newItem); + return; + } + + if (!moduleItemForItemValues->isPresentModule()) + return; + + // This will yield false negatives for the case where there is an invalid property attached + // for a module that is actually found by pkg-config via the fallback provider. + // However, this is extremely rare compared to the case where the presence of the fallback + // module simply indicates "not present". + if (moduleItemForItemValues->isFallbackModule()) + return; + + // If the old and the new items are the same, it means the existing item value already + // pointed to a module instance (rather than a placeholder). + // This can happen in two cases: + // a) Multiple identical Depends items on the same level (easily possible with inheritance). + // b) Dependencies to multiplexed variants of the same product + // (see getOrSetModuleInstanceItem() below for details). + if (oldItem == newItem) { + QBS_CHECK(oldItem->type() == ItemType::ModuleInstance); + QBS_CHECK(context.alreadyLoaded || context.exportingProduct); + return; + } + + // In all other cases, our request to set the module instance item must have been honored. + QBS_CHECK(newItem == moduleItemForItemValues); + + // If there was no placeholder item, we don't have to merge any values and are done. + if (!oldItem) + return; + + // If an item was replaced, then it must have been a placeholder. + QBS_CHECK(oldItem->type() == ItemType::ModuleInstancePlaceholder); + + // Prevent setting read-only properties. + for (auto it = oldItem->properties().cbegin(); it != oldItem->properties().cend(); ++it) { + const PropertyDeclaration &pd = moduleItemForItemValues->propertyDeclaration(it.key()); + if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) { + throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), + it.value()->location()); + } + } + + // Now merge the locally attached values into the actual module instance. + mergeFromLocalInstance(context.product, loadingItem, context.loadingName, oldItem, + moduleItemForItemValues, loaderState); + + // TODO: We'd like to delete the placeholder item here, because it's not + // being referenced anymore and there's a lot of them. However, this + // is not supported by ItemPool. Investigate the use of std::pmr. +} + +Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name, + LoaderState &loaderState) +{ + return getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false, loaderState).second; +} + +Item *retrieveQbsItem(Item *containerItem, LoaderState &loaderState) +{ + return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule(), loaderState); +} + +void ModuleInstantiator::overrideProperties() +{ + // Users can override module properties on the command line with the + // modules.<module-name>.<property-name>:<value> syntax. + // For simplicity and backwards compatibility, qbs properties can also be given without + // the "modules." prefix, i.e. just qbs.<property-name>:<value>. + // In addition, users can override module properties just for certain products + // using the products.<product-name>.<module-name>.<property-name>:<value> syntax. + // Such product-specific overrides have higher precedence. + const QString fullName = context.moduleName.toString(); + const QString generalOverrideKey = QStringLiteral("modules.") + fullName; + const QString perProductOverrideKey = StringConstants::productsOverridePrefix() + + context.product.name + QLatin1Char('.') + fullName; + const SetupProjectParameters ¶meters = loaderState.parameters(); + Logger &logger = loaderState.logger(); + context.module->overrideProperties(parameters.overriddenValuesTree(), generalOverrideKey, + parameters, logger); + if (fullName == StringConstants::qbsModule()) { + context.module->overrideProperties(parameters.overriddenValuesTree(), fullName, parameters, + logger); + } + context.module->overrideProperties(parameters.overriddenValuesTree(), perProductOverrideKey, + parameters, logger); +} + +void ModuleInstantiator::setupScope() +{ + Item * const scope = Item::create(&loaderState.itemPool(), ItemType::Scope); + QBS_CHECK(context.module->file()); + scope->setFile(context.module->file()); + QBS_CHECK(context.product.project->scope); + context.product.project->scope->copyProperty(StringConstants::projectVar(), scope); + if (context.product.scope) + context.product.scope->copyProperty(StringConstants::productVar(), scope); + else + QBS_CHECK(context.moduleName.toString() == StringConstants::qbsModule()); // Dummy product. + + if (!context.module->id().isEmpty()) + scope->setProperty(context.module->id(), ItemValue::create(context.module)); + for (Item * const child : context.module->children()) { + child->setScope(scope); + if (!child->id().isEmpty()) + scope->setProperty(child->id(), ItemValue::create(child)); + } + context.module->setScope(scope); + + if (context.exportingProduct) { + QBS_CHECK(context.exportingProduct->type() == ItemType::Product); + + const auto exportingProductItemValue = ItemValue::create(context.exportingProduct); + scope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue); + + const auto importingProductItemValue = ItemValue::create(context.product.item); + scope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue); + + // FIXME: This looks wrong. Introduce exportingProject variable? + scope->setProperty(StringConstants::projectVar(), + ItemValue::create(context.exportingProduct->parent())); + + PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(), + PropertyDeclaration::String, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + context.module->setPropertyDeclaration(pd.name(), pd); + context.module->setProperty(pd.name(), context.exportingProduct->property( + StringConstants::sourceDirectoryProperty())); + } +} + +void instantiateModule(const InstantiationContext &context, LoaderState &loaderState) +{ + ModuleInstantiator(context, loaderState).instantiate(); +} + +// This important function deals with retrieving and setting (pseudo-)module instances from/on +// items. +// Use case 1: Suppose we resolve the dependency cpp in module Qt.core, which also contains +// property bindings for cpp such as "cpp.defines: [...]". +// The "cpp" part of this binding is represented by an ItemValue whose +// item is of type ModuleInstancePlaceholder, originally set up by ItemReaderASTVisitor. +// This function will be called with the actual cpp module item and will +// replace the placeholder item in the item value. It will also return +// the placeholder item for subsequent merging of its properties with the +// ones of the actual module (in ModulePropertyMerger::mergeFromLocalInstance()). +// If there were no cpp property bindings defined in Qt.core, then we'd still +// have to replace the placeholder item, because references to "cpp" on the +// right-hand-side of other properties must refer to the module item. +// This is the common use of this function as employed by resolveProdsucDependencies(). +// Note that if a product has dependencies on more than one variant of a multiplexed +// product, these dependencies are competing for the item value property name, +// i.e. this case is not properly supported by the syntax. You must therefore not +// export properties from multiplexed products that will be different between the +// variants. In this function, the condition manifests itself by a module instance +// being encountered instead of a module instance placeholder, in which case +// nothing is done at all. +// Use case 2: We inject a fake qbs module into a project item, so qbs properties set in profiles +// can be accessed in the project level. Doing this is discouraged, and the +// functionality is kept mostly for backwards compatibility. The moduleItem +// parameter is null in this case, and the item will be created by the function itself. +// Use case 3: A temporary qbs module is attached to a product during low-level setup, namely +// in product multiplexing and setting qbs.profile. +// Use case 4: Module propagation to the the Group level. +// In all cases, the first returned item is the existing one, and the second returned item +// is the new one. Depending on the use case, they might be null and might also be the same item. +std::pair<const Item *, Item *> getOrSetModuleInstanceItem( + Item *container, Item *moduleItem, const QualifiedId &moduleName, const QString &id, + bool replace, LoaderState &loaderState) +{ + Item *instance = container; + const QualifiedId itemValueName + = !id.isEmpty() ? QualifiedId::fromString(id) : moduleName; + for (int i = 0; i < itemValueName.size(); ++i) { + const QString &moduleNameSegment = itemValueName.at(i); + const ValuePtr v = instance->ownProperty(itemValueName.at(i)); + if (v && v->type() == Value::ItemValueType) { + ItemValue * const itemValue = std::static_pointer_cast<ItemValue>(v).get(); + instance = itemValue->item(); + if (i == itemValueName.size() - 1) { + if (replace && instance != moduleItem + && instance->type() == ItemType::ModuleInstancePlaceholder) { + if (!moduleItem) { + moduleItem = Item::create(&loaderState.itemPool(), + ItemType::ModuleInstancePlaceholder); + } + itemValue->setItem(moduleItem); + } + return {instance, itemValue->item()}; + } + } else { + Item *newItem = i < itemValueName.size() - 1 + ? Item::create(&loaderState.itemPool(), ItemType::ModulePrefix) + : moduleItem + ? moduleItem + : Item::create(&loaderState.itemPool(), + ItemType::ModuleInstancePlaceholder); + instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); + instance = newItem; + } + } + return {nullptr, instance}; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/moduleinstantiator.h b/src/lib/corelib/loader/moduleinstantiator.h new file mode 100644 index 000000000..5e9ad2f44 --- /dev/null +++ b/src/lib/corelib/loader/moduleinstantiator.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs::Internal { +class Item; +class LoaderState; +class ProductContext; +class QualifiedId; + +class InstantiationContext { +public: + ProductContext &product; + Item * const loadingItem; + const QString &loadingName; + Item * const module; + Item * const moduleWithSameName; + Item * const exportingProduct; + const QualifiedId &moduleName; + const QString &id; + const bool alreadyLoaded; +}; + +// This function is responsible for setting up a proper module instance from a bunch of items: +// - Set the item type to ItemType::ModuleInstance (from Module or Export). +// - Apply possible command-line overrides for module properties. +// - Replace a possible module instance placeholder in the loading item with the actual instance +// and merge their values employing the ModulePropertyMerger. +// - Setting up the module instance scope. +void instantiateModule(const InstantiationContext &context, LoaderState &loaderState); + +// Helper functions for retrieving/setting module instance items for special purposes. +// Note that these will also create the respective item value if it does not exist yet. +Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name, + LoaderState &loaderState); +Item *retrieveQbsItem(Item *containerItem, LoaderState &loaderState); + +} // namespace qbs::Internal + diff --git a/src/lib/corelib/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp new file mode 100644 index 000000000..d26849655 --- /dev/null +++ b/src/lib/corelib/loader/moduleloader.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "moduleloader.h" + +#include "itemreader.h" +#include "loaderutils.h" +#include "moduleproviderloader.h" + +#include <api/languageinfo.h> +#include <language/evaluator.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/codelocation.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/hostosinfo.h> +#include <tools/profiling.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <QDirIterator> +#include <QHash> + +#include <unordered_map> +#include <utility> + +namespace qbs::Internal { + +class ModuleLoader +{ +public: + ModuleLoader( + LoaderState &loaderState, + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName) + : m_loaderState(loaderState) + , m_product(product) + , m_dependsItemLocation(dependsItemLocation) + , m_moduleName(moduleName) + {} + + Item *load(); + +private: + std::pair<Item *, bool> loadModuleFile(const QString &moduleName, const QString &filePath); + Item *getModulePrototype(const QString &moduleName, const QString &filePath); + Item *createAndInitModuleItem(const QString &moduleName, const QString &filePath); + bool evaluateModuleCondition(Item *module, const QString &fullModuleName); + void checkForUnknownProfileProperties(const Item *module); + QString findModuleDirectory(const QString &searchPath); + QStringList findModuleDirectories(); + QStringList getModuleFilePaths(const QString &dir); + + LoaderState &m_loaderState; + ProductContext &m_product; + const CodeLocation &m_dependsItemLocation; + const QualifiedId &m_moduleName; +}; + +struct PrioritizedItem +{ + PrioritizedItem(Item *item, int priority, int searchPathIndex) + : item(item), priority(priority), searchPathIndex(searchPathIndex) { } + + Item * const item; + int priority = 0; + const int searchPathIndex; +}; + +static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates, + const QString &moduleName) +{ + // TODO: This should also consider the version requirement. + + auto maxIt = std::max_element( + candidates.begin(), candidates.end(), + [] (const PrioritizedItem &a, const PrioritizedItem &b) { + if (a.priority < b.priority) + return true; + if (a.priority > b.priority) + return false; + return a.searchPathIndex > b.searchPathIndex; + }); + + size_t nmax = std::count_if( + candidates.begin(), candidates.end(), + [maxIt] (const PrioritizedItem &i) { + return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex; + }); + + if (nmax > 1) { + ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.") + .arg(moduleName)); + for (size_t i = 0; i < candidates.size(); ++i) { + const auto candidate = candidates.at(i); + if (candidate.priority == maxIt->priority) { + //: The %1 denotes the number of the candidate. + e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location()); + } + } + throw e; + } + + return maxIt->item; +} + +Item *searchAndLoadModuleFile( + LoaderState &loaderState, + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName) +{ + return ModuleLoader(loaderState, product, dependsItemLocation, moduleName).load(); +} + +Item *ModuleLoader::load() +{ + SearchPathsManager searchPathsManager(m_loaderState.itemReader()); + + QStringList existingPaths = findModuleDirectories(); + if (existingPaths.isEmpty()) { // no suitable names found, try to use providers + AccumulatingTimer providersTimer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.moduleProviders : nullptr); + auto result = ModuleProviderLoader(m_loaderState) + .executeModuleProviders(m_product, m_dependsItemLocation, m_moduleName); + if (result.searchPaths) { + qCDebug(lcModuleLoader) << "Re-checking for module" << m_moduleName.toString() + << "with newly added search paths from module provider"; + m_loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths); + existingPaths = findModuleDirectories(); + } + } + + const QString fullName = m_moduleName.toString(); + bool triedToLoadModule = false; + std::vector<PrioritizedItem> candidates; + candidates.reserve(size_t(existingPaths.size())); + for (int i = 0; i < existingPaths.size(); ++i) { + const QStringList &moduleFileNames = getModuleFilePaths(existingPaths.at(i)); + for (const QString &filePath : moduleFileNames) { + const auto [module, triedToLoad] = loadModuleFile(fullName, filePath); + if (module) + candidates.emplace_back(module, 0, i); + if (!triedToLoad) + m_loaderState.topLevelProject().removeModuleFileFromDirectoryCache(filePath); + triedToLoadModule = triedToLoadModule || triedToLoad; + } + } + + if (candidates.empty()) + return nullptr; + + Item *moduleItem = nullptr; + if (candidates.size() == 1) { + moduleItem = candidates.at(0).item; + } else { + for (auto &candidate : candidates) { + ModuleItemLocker lock(*candidate.item); + candidate.priority = m_loaderState.evaluator() + .intValue(candidate.item, StringConstants::priorityProperty(), + candidate.priority); + } + moduleItem = chooseModuleCandidate(candidates, fullName); + } + + checkForUnknownProfileProperties(moduleItem); + return moduleItem; +} + +std::pair<Item *, bool> ModuleLoader::loadModuleFile(const QString &moduleName, + const QString &filePath) +{ + qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath; + + Item * const module = getModulePrototype(moduleName, filePath); + if (!module) + return {nullptr, false}; + + const auto it = m_product.modulePrototypeEnabledInfo.find(module); + if (it != m_product.modulePrototypeEnabledInfo.end()) { + qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; + return {it->second ? module : nullptr, true}; + } + + if (!evaluateModuleCondition(module, moduleName)) { + qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false"; + m_product.modulePrototypeEnabledInfo.insert({module, false}); + return {nullptr, true}; + } + + m_product.modulePrototypeEnabledInfo.insert({module, true}); + return {module, true}; +} + +Item * ModuleLoader::getModulePrototype(const QString &moduleName, const QString &filePath) +{ + bool fromCache = true; + Item * const module = m_loaderState.topLevelProject().getModulePrototype( + filePath, m_product.profileName, [&] { + fromCache = false; + return createAndInitModuleItem(moduleName, filePath); + }); + + if (fromCache) + qCDebug(lcModuleLoader) << "prototype cache hit (level 1)"; + return module; +} + +Item *ModuleLoader::createAndInitModuleItem(const QString &moduleName, const QString &filePath) +{ + Item * const module = m_loaderState.itemReader().setupItemFromFile(filePath, {}); + if (module->type() != ItemType::Module) { + qCDebug(lcModuleLoader).nospace() + << "Alleged module " << moduleName << " has type '" + << module->typeName() << "', so it's not a module after all."; + return nullptr; + } + + // Not technically needed, but we want to keep the invariant in item.cpp. + ModuleItemLocker locker(*module); + + module->setProperty(StringConstants::nameProperty(), VariantValue::create(moduleName)); + if (moduleName == StringConstants::qbsModule()) { + module->setProperty(QStringLiteral("hostPlatform"), + VariantValue::create(HostOsInfo::hostOSIdentifier())); + module->setProperty(QStringLiteral("hostArchitecture"), + VariantValue::create(HostOsInfo::hostOSArchitecture())); + module->setProperty(QStringLiteral("libexecPath"), + VariantValue::create(m_loaderState.parameters().libexecPath())); + + const Version qbsVersion = LanguageInfo::qbsVersion(); + module->setProperty(QStringLiteral("versionMajor"), + VariantValue::create(qbsVersion.majorVersion())); + module->setProperty(QStringLiteral("versionMinor"), + VariantValue::create(qbsVersion.minorVersion())); + module->setProperty(QStringLiteral("versionPatch"), + VariantValue::create(qbsVersion.patchLevel())); + } else { + Item::PropertyDeclarationMap decls; + const auto &moduleChildren = module->children(); + for (Item *param : moduleChildren) { + if (param->type() == ItemType::Parameter) { + const auto ¶mDecls = param->propertyDeclarations(); + for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) + decls.insert(it.key(), it.value()); + } else if (param->type() == ItemType::Parameters) { + adjustParametersScopes(param, param); + Evaluator &evaluator = m_loaderState.evaluator(); + QVariantMap parameters = getJsVariant(evaluator.engine()->context(), + evaluator.scriptValue(param)).toMap(); + m_loaderState.topLevelProject().setParameters(module, parameters); + } + } + m_loaderState.topLevelProject().addParameterDeclarations(module, decls); + } + + // Module properties that are defined in the profile are used as default values. + // This is the reason we need to have different items per profile. + const QVariantMap profileModuleProperties + = m_product.profileModuleProperties.value(moduleName).toMap(); + for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) { + if (Q_UNLIKELY(!module->hasProperty(it.key()))) { + m_loaderState.topLevelProject().addUnknownProfilePropertyError( + module, {Tr::tr("Unknown property: %1.%2").arg(moduleName, it.key())}); + continue; + } + const PropertyDeclaration decl = module->propertyDeclaration(it.key()); + VariantValuePtr v = VariantValue::create( + PropertyDeclaration::convertToPropertyType(it.value(), decl.type(), + QStringList(moduleName), it.key())); + v->markAsSetByProfile(); + module->setProperty(it.key(), v); + } + + return module; +} + +bool ModuleLoader::evaluateModuleCondition(Item *module, const QString &fullModuleName) +{ + ModuleItemLocker locker(*module); + + // Temporarily make the product's qbs module instance available, so the condition + // can use qbs.targetOS etc. + class TempQbsModuleProvider { + public: + TempQbsModuleProvider(const ProductContext &product, + Item *module, const QString &moduleName) + : m_module(module), m_needsQbsItem(moduleName != StringConstants::qbsModule()) + { + if (m_needsQbsItem) { + m_prevQbsItemValue = module->property(StringConstants::qbsModule()); + module->setProperty(StringConstants::qbsModule(), + product.item->property(StringConstants::qbsModule())); + } + } + ~TempQbsModuleProvider() + { + if (!m_needsQbsItem) + return; + if (m_prevQbsItemValue) + m_module->setProperty(StringConstants::qbsModule(), m_prevQbsItemValue); + else + m_module->removeProperty(StringConstants::qbsModule()); + } + private: + Item * const m_module; + ValuePtr m_prevQbsItemValue; + const bool m_needsQbsItem; + }; + + const TempQbsModuleProvider tempQbs(m_product, module, fullModuleName); + return m_loaderState.evaluator().boolValue(module, StringConstants::conditionProperty()); +} + +void ModuleLoader::checkForUnknownProfileProperties(const Item *module) +{ + const std::vector<ErrorInfo> &errors + = m_loaderState.topLevelProject().unknownProfilePropertyErrors(module); + if (errors.empty()) + return; + + ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values " + "in profile '%3':") + .arg(m_moduleName.toString(), m_product.displayName(), m_product.profileName)); + for (const ErrorInfo &e : errors) + error.append(e.toString()); + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); +} + +QString ModuleLoader::findModuleDirectory(const QString &searchPath) +{ + // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the + // modules and search paths we've already processed + return m_loaderState.topLevelProject().findModuleDirectory(m_moduleName, searchPath, + [&] { + QString dirPath = searchPath + QStringLiteral("/modules"); + for (const QString &moduleNamePart : m_moduleName) { + dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); + if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) + return QString(); + } + return dirPath; + }); +} + +QStringList ModuleLoader::findModuleDirectories() +{ + const QStringList &searchPaths = m_loaderState.itemReader().allSearchPaths(); + QStringList result; + result.reserve(searchPaths.size()); + for (const auto &path: searchPaths) { + const QString dirPath = findModuleDirectory(path); + if (!dirPath.isEmpty()) + result.append(dirPath); + } + return result; +} + +QStringList ModuleLoader::getModuleFilePaths(const QString &dir) +{ + return m_loaderState.topLevelProject().getModuleFilesForDirectory(dir, [&] { + QStringList moduleFiles; + QDirIterator dirIter(dir, StringConstants::qbsFileWildcards()); + while (dirIter.hasNext()) + moduleFiles += dirIter.next(); + return moduleFiles; + }); +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/language/evaluationdata.h b/src/lib/corelib/loader/moduleloader.h index 791b2f234..b07f01836 100644 --- a/src/lib/corelib/language/evaluationdata.h +++ b/src/lib/corelib/loader/moduleloader.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -37,30 +37,22 @@ ** ****************************************************************************/ -#ifndef QBS_EVALUATIONDATA_H -#define QBS_EVALUATIONDATA_H - -#include <QtCore/qhash.h> -#include <QtCore/qvariant.h> - -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> +#pragma once namespace qbs { +class CodeLocation; namespace Internal { - -class Evaluator; class Item; +class LoaderState; +class ProductContext; +class QualifiedId; -class EvaluationData -{ -public: - Evaluator *evaluator = nullptr; - const Item *item = nullptr; - mutable QHash<QScriptString, QScriptValue> valueCache; -}; +Item *searchAndLoadModuleFile( + LoaderState &loaderState, + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName); } // namespace Internal } // namespace qbs -#endif // QBS_EVALUATIONDATA_H diff --git a/src/lib/corelib/loader/modulepropertymerger.cpp b/src/lib/corelib/loader/modulepropertymerger.cpp new file mode 100644 index 000000000..c9dd56ccf --- /dev/null +++ b/src/lib/corelib/loader/modulepropertymerger.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "modulepropertymerger.h" + +#include "loaderutils.h" + +#include <language/evaluator.h> +#include <language/item.h> +#include <language/value.h> +#include <logging/translator.h> +#include <tools/profiling.h> +#include <tools/set.h> +#include <tools/setupprojectparameters.h> + +#include <unordered_set> + +namespace qbs::Internal { +class ModulePropertyMerger +{ +public: + ModulePropertyMerger(ProductContext &product, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState) {} + + void mergeFromLocalInstance(Item *loadingItem, const QString &loadingName, + const Item *localInstance, Item *globalInstance); + void doFinalMerge(); + +private: + int compareValuePriorities(const ValueConstPtr &v1, const ValueConstPtr &v2); + ValuePtr mergeListValues(const ValuePtr ¤tHead, const ValuePtr &newElem); + void mergePropertyFromLocalInstance(Item *loadingItem, const QString &loadingName, + Item *globalInstance, const QString &name, + const ValuePtr &value); + bool doFinalMerge(Item *moduleItem); + bool doFinalMerge(const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue); + + ProductContext & m_product; + LoaderState &m_loaderState; +}; + +void mergeFromLocalInstance(ProductContext &product, Item *loadingItem, const QString &loadingName, + const Item *localInstance, Item *globalInstance, + LoaderState &loaderState) +{ + ModulePropertyMerger(product, loaderState).mergeFromLocalInstance( + loadingItem, loadingName, localInstance, globalInstance); +} + +void doFinalMerge(ProductContext &product, LoaderState &loaderState) +{ + ModulePropertyMerger(product, loaderState).doFinalMerge(); +} + +void ModulePropertyMerger::mergeFromLocalInstance(Item *loadingItem, const QString &loadingName, + const Item *localInstance, Item *globalInstance) +{ + AccumulatingTimer t(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.propertyMerging : nullptr); + + for (auto it = localInstance->properties().constBegin(); + it != localInstance->properties().constEnd(); ++it) { + mergePropertyFromLocalInstance(loadingItem, loadingName, globalInstance, it.key(), + it.value()); + } +} + +void ModulePropertyMerger::doFinalMerge() +{ + AccumulatingTimer t(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.propertyMerging : nullptr); + + std::unordered_set<const Item *> itemsToInvalidate; + for (const Item::Module &module : m_product.item->modules()) { + if (doFinalMerge(module.item)) + itemsToInvalidate.insert(module.item); + } + + // For each module item, if it requires invalidation, we also add modules that depend on + // that module item, all the way up to the Product item. + std::unordered_set<const Item *> visitedItems; + const auto collectDependentItems = + [&itemsToInvalidate, &visitedItems](const Item *item, const auto &collect) -> bool + { + const bool alreadyInSet = itemsToInvalidate.count(item); + if (!visitedItems.insert(item).second) // item handled already + return alreadyInSet; + bool addItem = false; + for (const Item::Module &m : item->modules()) { + if (collect(m.item, collect)) + addItem = true; + } + if (addItem && !alreadyInSet) + itemsToInvalidate.insert(item); + return addItem || alreadyInSet; + }; + collectDependentItems(m_product.item, collectDependentItems); + for (const Item * const item : itemsToInvalidate) + m_loaderState.evaluator().clearCache(item); +} + +int ModulePropertyMerger::compareValuePriorities(const ValueConstPtr &v1, const ValueConstPtr &v2) +{ + QBS_CHECK(v1); + QBS_CHECK(v2); + QBS_CHECK(v1->scope() != v2->scope()); + QBS_CHECK(v1->type() == Value::JSSourceValueType || v2->type() == Value::JSSourceValueType); + + const int prio1 = v1->priority(m_product.item); + const int prio2 = v2->priority(m_product.item); + if (prio1 != prio2) + return prio1 - prio2; + const int prioDiff = v1->scopeName().compare(v2->scopeName()); // Sic! See 8ff1dd0044 + QBS_CHECK(prioDiff != 0); + return prioDiff; +} + +ValuePtr ModulePropertyMerger::mergeListValues(const ValuePtr ¤tHead, const ValuePtr &newElem) +{ + QBS_CHECK(newElem); + QBS_CHECK(!newElem->next()); + + if (!currentHead) + return !newElem->expired(m_product.item) ? newElem : newElem->next(); + + QBS_CHECK(!currentHead->expired(m_product.item)); + + if (newElem->expired(m_product.item)) + return currentHead; + + if (compareValuePriorities(currentHead, newElem) < 0) { + newElem->setNext(currentHead); + return newElem; + } + currentHead->setNext(mergeListValues(currentHead->next(), newElem)); + return currentHead; +} + +void ModulePropertyMerger::mergePropertyFromLocalInstance( + Item *loadingItem, const QString &loadingName, Item *globalInstance, + const QString &name, const ValuePtr &value) +{ + if (loadingItem->type() == ItemType::Project) { + throw ErrorInfo(Tr::tr("Module properties cannot be set in Project items."), + value->location()); + } + + const PropertyDeclaration decl = globalInstance->propertyDeclaration(name); + if (!decl.isValid()) { + if (value->type() == Value::ItemValueType || value->createdByPropertiesBlock()) + return; + throw ErrorInfo(Tr::tr("Property '%1' is not declared.") + .arg(name), value->location()); + } + if (const ErrorInfo error = decl.checkForDeprecation( + m_loaderState.parameters().deprecationWarningMode(), value->location(), + m_loaderState.logger()); error.hasError()) { + handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger()); + return; + } + if (value->setInternally()) { // E.g. qbs.architecture after multiplexing. + globalInstance->setProperty(decl.name(), value); + return; + } + QBS_CHECK(value->type() != Value::ItemValueType); + const ValuePtr globalVal = globalInstance->ownProperty(decl.name()); + value->setScope(loadingItem, loadingName); + QBS_CHECK(globalVal); + + // Values set internally cannot be overridden by JS values. + // The same goes for values set on the command line. + // Note that in both cases, there is no merging for list properties: The override is absolute. + if (globalVal->setInternally() || globalVal->setByCommandLine()) + return; + + QBS_CHECK(value->type() == Value::JSSourceValueType); + + if (decl.isScalar()) { + QBS_CHECK(!globalVal->expired(m_product.item)); + QBS_CHECK(!value->expired(m_product.item)); + if (compareValuePriorities(globalVal, value) < 0) { + value->setCandidates(globalVal->candidates()); + globalVal->setCandidates({}); + value->addCandidate(globalVal); + globalInstance->setProperty(decl.name(), value); + } else { + globalVal->addCandidate(value); + } + } else { + if (const ValuePtr &newChainStart = mergeListValues(globalVal, value); + newChainStart != globalVal) { + globalInstance->setProperty(decl.name(), newChainStart); + } + } +} + +bool ModulePropertyMerger::doFinalMerge(Item *moduleItem) +{ + if (!moduleItem->isPresentModule()) + return false; + bool mustInvalidateCache = false; + for (auto it = moduleItem->properties().begin(); it != moduleItem->properties().end(); ++it) { + if (doFinalMerge(moduleItem->propertyDeclaration(it.key()), it.value())) + mustInvalidateCache = true; + } + return mustInvalidateCache; +} + +bool ModulePropertyMerger::doFinalMerge(const PropertyDeclaration &propertyDecl, + ValuePtr &propertyValue) +{ + if (propertyValue->type() == Value::VariantValueType) { + QBS_CHECK(!propertyValue->next()); + return false; + } + if (!propertyDecl.isValid()) + return false; // Caught later by dedicated checker. + propertyValue->resetPriority(); + if (propertyDecl.isScalar()) { + if (propertyValue->candidates().empty()) + return false; + std::pair<int, std::vector<ValuePtr>> candidatesWithHighestPrio; + candidatesWithHighestPrio.first = propertyValue->priority(m_product.item); + candidatesWithHighestPrio.second.push_back(propertyValue); + for (const ValuePtr &v : propertyValue->candidates()) { + const int prio = v->priority(m_product.item); + if (prio < candidatesWithHighestPrio.first) + continue; + if (prio > candidatesWithHighestPrio.first) { + candidatesWithHighestPrio.first = prio; + candidatesWithHighestPrio.second = {v}; + continue; + } + candidatesWithHighestPrio.second.push_back(v); + } + ValuePtr chosenValue = candidatesWithHighestPrio.second.front(); + if (int(candidatesWithHighestPrio.second.size()) > 1) { + ErrorInfo error(Tr::tr("Conflicting scalar values for property '%1'.") + .arg(propertyDecl.name())); + error.append({}, chosenValue->location()); + QBS_CHECK(chosenValue->type() == Value::JSSourceValueType); + QStringView sourcCode = static_cast<JSSourceValue *>( + chosenValue.get())->sourceCode(); + for (int i = 1; i < int(candidatesWithHighestPrio.second.size()); ++i) { + const ValuePtr &v = candidatesWithHighestPrio.second.at(i); + QBS_CHECK(v->type() == Value::JSSourceValueType); + + // Note that this is a bit silly: The source code could still evaluate to + // different values in the end. + if (static_cast<JSSourceValue *>(v.get())->sourceCode() != sourcCode) + error.append({}, v->location()); + } + if (error.items().size() > 2) + m_loaderState.logger().printWarning(error); + } + + if (propertyValue == chosenValue) + return false; + std::vector<ValuePtr> candidates = propertyValue->candidates(); + candidates.erase(std::find(candidates.begin(), candidates.end(), chosenValue)); + chosenValue->setCandidates(candidates); + chosenValue->addCandidate(propertyValue); + propertyValue->setCandidates({}); + propertyValue = chosenValue; + return true; + } + if (!propertyValue->next()) + return false; + std::vector<ValuePtr> singleValuesBefore; + for (ValuePtr current = propertyValue; current;) { + singleValuesBefore.push_back(current); + const ValuePtr next = current->next(); + if (next) + current->setNext({}); + current = next; + } + ValuePtr newValue; + for (const ValuePtr &v : singleValuesBefore) + newValue = mergeListValues(newValue, v); + std::vector<ValuePtr> singleValuesAfter; + for (ValuePtr current = propertyValue; current; current = current->next()) + singleValuesAfter.push_back(current); + propertyValue = newValue; + return singleValuesBefore != singleValuesAfter; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/modulepropertymerger.h b/src/lib/corelib/loader/modulepropertymerger.h new file mode 100644 index 000000000..a6643216f --- /dev/null +++ b/src/lib/corelib/loader/modulepropertymerger.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs::Internal { +class Item; +class LoaderState; +class ProductContext; + +// This module comprises functions for collecting values attached to module properties +// in different contexts. +// For example, in the Qt.core module you will find a property binding such as this: +// cpp.defines: "QT_CORE_LIB" +// while in the Qt.widgets module, it will look like this: +// cpp.defines: "QT_WIDGETS_LIB" +// A product with a dependency on both these modules will end up with a value of +// ["QT_WIDGETS_LIB", "QT_CORE_LIB"], plus potentially other defines set elsewhere. +// Each of these values is assigned a priority that roughly corresponds to the "level" at which +// the module containing the property binding resides in the dependency hierarchy. +// For list properties, the priorities determine the order of the respecive values in the +// final array, for scalar values they determine which one survives. Different scalar values +// with the same priority trigger a warning message. +// Since the right-hand side of a binding can refer to properties of the surrounding context, +// each such value gets its own scope. + +// This function is called when a module is loaded via a Depends item. +// loadingItem is the product or module containing the Depends item. +// loadingName is the name of that module. It is used as a tie-breaker for list property values +// with equal priority. +// localInstance is the module instance placeholder in the ItemValue of a property binding, +// i.e. the "cpp" in "cpp.defines". +// globalInstance is the actual module into which the properties from localInstance get merged. +void mergeFromLocalInstance(ProductContext &product, Item *loadingItem, + const QString &loadingName, const Item *localInstance, + Item *globalInstance, LoaderState &loaderState); + +// This function is called after all dependencies have been resolved. It uses its global +// knowledge of module priorities to potentially adjust the order of list values or +// favor different scalar values. It can also remove previously merged-in values again; +// this can happen if a module fails to load after it already merged some values, or +// if it fails validation in the end. +void doFinalMerge(ProductContext &product, LoaderState &loaderState); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/moduleproviderloader.cpp b/src/lib/corelib/loader/moduleproviderloader.cpp new file mode 100644 index 000000000..d0789facd --- /dev/null +++ b/src/lib/corelib/loader/moduleproviderloader.cpp @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "moduleproviderloader.h" + +#include "itemreader.h" +#include "probesresolver.h" + +#include <language/builtindeclarations.h> +#include <language/evaluator.h> +#include <language/item.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/jsliterals.h> +#include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> +#include <tools/stlutils.h> +#include <tools/stringconstants.h> + +#include <QtCore/qcryptographichash.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> + +namespace qbs { +namespace Internal { + +static QString getConfigHash(const QVariantMap& config) +{ + QJsonDocument doc; + doc.setObject(QJsonObject::fromVariantMap(config)); + return QString::fromLatin1( + QCryptographicHash::hash(doc.toJson(), QCryptographicHash::Sha1).toHex().left(16)); +} + +ModuleProviderLoader::ModuleProviderLoader(LoaderState &loaderState) + : m_loaderState(loaderState) {} + +ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders( + ProductContext &productContext, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName) +{ + ModuleProviderLoader::ModuleProviderResult result; + try { + std::vector<Provider> providersToRun; + qCDebug(lcModuleLoader) << "Module" << moduleName.toString() + << "not found, checking for module providers"; + const auto providerNames = getModuleProviders(productContext.item); + if (providerNames) { + providersToRun = transformed<std::vector<Provider>>(*providerNames, [](const auto &name) { + return Provider{name, ModuleProviderLookup::Named}; }); + } else { + for (QualifiedId providerName = moduleName; !providerName.empty(); + providerName.pop_back()) { + providersToRun.push_back({providerName, ModuleProviderLookup::Scoped}); + } + } + result = executeModuleProvidersHelper( + productContext, dependsItemLocation, moduleName, providersToRun); + } catch (const ErrorInfo &error) { + auto ei = error; + ei.prepend( + Tr::tr("Error executing provider for module '%1':").arg(moduleName.toString()), + dependsItemLocation); + productContext.handleError(ei); + } + return result; +} + +ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper( + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + const std::vector<Provider> &providers) +{ + if (providers.empty()) + return {}; + QStringList allSearchPaths; + ModuleProviderResult result; + setupModuleProviderConfig(product); + const auto qbsModule = evaluateQbsModule(product); + for (const auto &[name, lookupType] : providers) { + const auto &[info, fromCache] = findOrCreateProviderInfo( + product, dependsItemLocation, moduleName, name, lookupType, qbsModule); + if (info.providerFile.isEmpty()) { + if (lookupType == ModuleProviderLookup::Named) + throw ErrorInfo(Tr::tr("Unknown provider '%1'").arg(name.toString())); + continue; + } + if (fromCache) + qCDebug(lcModuleLoader) << "Re-using provider" << name.toString() << "from cache"; + + result.providerFound = true; + if (info.searchPaths.empty()) { + qCDebug(lcModuleLoader) + << "Module provider did run, but did not set up any modules."; + continue; + } + qCDebug(lcModuleLoader) << "Module provider added" << info.searchPaths.size() + << "new search path(s)"; + + allSearchPaths << info.searchPaths; + } + if (allSearchPaths.isEmpty()) + return result; + + result.searchPaths = std::move(allSearchPaths); + + return result; +} + +std::pair<const ModuleProviderInfo &, bool /*fromCache*/> +ModuleProviderLoader::findOrCreateProviderInfo( + ProductContext &product, const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, const QualifiedId &name, ModuleProviderLookup lookupType, + const QVariantMap &qbsModule) +{ + QBS_CHECK(product.providerConfig); + const QVariantMap config = product.providerConfig->value(name.toString()).toMap(); + std::lock_guard lock(m_loaderState.topLevelProject().moduleProvidersCacheLock()); + ModuleProvidersCacheKey cacheKey{name.toString(), {}, config, qbsModule, int(lookupType)}; + // TODO: get rid of non-eager providers and eliminate following if-logic + // first, try to find eager provider (stored with an empty module name) + if (ModuleProviderInfo *provider = m_loaderState.topLevelProject().moduleProvider(cacheKey)) + return {*provider, true}; + // second, try to find non-eager provider for a specific module name + std::get<1>(cacheKey) = moduleName.toString(); // override moduleName + if (ModuleProviderInfo *provider = m_loaderState.topLevelProject().moduleProvider(cacheKey)) + return {*provider, true}; + bool isEager = false; + ModuleProviderInfo info; + info.name = name; + info.config = config; + info.providerFile = findModuleProviderFile(name, lookupType); + if (!info.providerFile.isEmpty()) { + qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile; + std::tie(info.searchPaths, isEager) = evaluateModuleProvider( + product, + dependsItemLocation, + moduleName, + name, + info.providerFile, + config, + qbsModule); + info.transientOutput = m_loaderState.parameters().dryRun(); + } + std::get<1>(cacheKey) = isEager ? QString() : moduleName.toString(); + return {m_loaderState.topLevelProject().addModuleProvider(cacheKey, info), false}; +} + +void ModuleProviderLoader::setupModuleProviderConfig(ProductContext &product) +{ + if (product.providerConfig) + return; + QVariantMap providerConfig; + const ItemValueConstPtr configItemValue = + product.item->itemProperty(StringConstants::moduleProviders(), m_loaderState.itemPool()); + if (configItemValue) { + const std::function<void(const Item *, QualifiedId)> collectMap + = [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) { + const Item::PropertyMap &props = item->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QVariant value; + switch (it.value()->type()) { + case Value::ItemValueType: { + const auto childItem = static_cast<ItemValue *>(it.value().get())->item(); + childItem->setScope(item->scope()); + collectMap(childItem, QualifiedId(name) << it.key()); + continue; + } + case Value::JSSourceValueType: { + it.value()->setScope(item->scope(), {}); + const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(), + m_loaderState.evaluator().value(item, it.key())); + value = getJsVariant(m_loaderState.evaluator().engine()->context(), sv); + break; + } + case Value::VariantValueType: + value = static_cast<VariantValue *>(it.value().get())->value(); + break; + } + QVariantMap m = providerConfig.value(name.toString()).toMap(); + m.insert(it.key(), value); + providerConfig.insert(name.toString(), m); + } + }; + configItemValue->item()->setScope(product.item); + collectMap(configItemValue->item(), QualifiedId()); + } + for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) { + if (!it.key().startsWith(QStringLiteral("moduleProviders."))) + continue; + const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size()); + const QVariantMap providerConfigFromBuildConfig = it.value().toMap(); + if (providerConfigFromBuildConfig.empty()) + continue; + QVariantMap currentMapForProvider = providerConfig.value(provider).toMap(); + for (auto propIt = providerConfigFromBuildConfig.begin(); + propIt != providerConfigFromBuildConfig.end(); ++propIt) { + currentMapForProvider.insert(propIt.key(), propIt.value()); + } + providerConfig.insert(provider, currentMapForProvider); + } + product.providerConfig = providerConfig; +} + +std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item) +{ + while (item) { + const auto providers = m_loaderState.evaluator().optionalStringListValue( + item, StringConstants::qbsModuleProviders()); + if (providers) { + return transformed<std::vector<QualifiedId>>(*providers, [](const auto &provider) { + return QualifiedId::fromString(provider); }); + } + item = item->parent(); + } + return std::nullopt; +} + +QString ModuleProviderLoader::findModuleProviderFile( + const QualifiedId &name, ModuleProviderLookup lookupType) +{ + for (const QString &path : m_loaderState.itemReader().allSearchPaths()) { + QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers")); + switch (lookupType) { + case ModuleProviderLookup::Named: { + const auto result = + FileInfo::resolvePath(fullPath, name.toString() + QStringLiteral(".qbs")); + if (FileInfo::exists(result)) { + fullPath = result; + break; + } + [[fallthrough]]; + } + case ModuleProviderLookup::Scoped: + for (const QString &component : name) + fullPath = FileInfo::resolvePath(fullPath, component); + fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs")); + break; + } + if (!FileInfo::exists(fullPath)) { + qCDebug(lcModuleLoader) << "No module provider found at" << fullPath; + continue; + } + return fullPath; + } + return {}; +} + +QVariantMap ModuleProviderLoader::evaluateQbsModule(ProductContext &product) const +{ + if (product.providerQbsModule) + return *product.providerQbsModule; + const QString properties[] = { + QStringLiteral("sysroot"), + QStringLiteral("toolchain"), + }; + const auto qbsItemValue = std::static_pointer_cast<ItemValue>( + product.item->property(StringConstants::qbsModule())); + QVariantMap result; + for (const auto &property : properties) { + const ScopedJsValue val(m_loaderState.evaluator().engine()->context(), + m_loaderState.evaluator().value(qbsItemValue->item(), property)); + auto value = getJsVariant(m_loaderState.evaluator().engine()->context(), val); + if (!value.isValid()) + continue; + + // The xcode module sets qbs.sysroot; the resulting value is bogus before the probes + // have run. + if (property == QLatin1String("sysroot") && !FileInfo::isAbsolute(value.toString())) + continue; + + result[property] = std::move(value); + } + return *(product.providerQbsModule = result); +} + +Item *ModuleProviderLoader::createProviderScope( + const ProductContext &product, const QVariantMap &qbsModule) +{ + const auto qbsItemValue = std::static_pointer_cast<ItemValue>( + product.item->property(StringConstants::qbsModule())); + + Item *fakeQbsModule = Item::create(&m_loaderState.itemPool(), ItemType::Scope); + + for (auto it = qbsModule.begin(), end = qbsModule.end(); it != end; ++it) { + fakeQbsModule->setProperty(it.key(), VariantValue::create(it.value())); + } + + Item *scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope); + scope->setFile(qbsItemValue->item()->file()); + scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule)); + return scope; +} + +ModuleProviderLoader::EvaluationResult ModuleProviderLoader::evaluateModuleProvider( + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + const QualifiedId &name, + const QString &providerFile, + const QVariantMap &moduleConfig, + const QVariantMap &qbsModule) +{ + qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile; + const QString projectBuildDir = product.project->item->variantProperty( + StringConstants::buildDirectoryProperty())->value().toString(); + const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); + + // include qbs module into hash + auto jsConfig = moduleConfig; + jsConfig[StringConstants::qbsModule()] = qbsModule; + + QString outputBaseDir = searchPathBaseDir + QLatin1Char('/') + getConfigHash(jsConfig); + Item * const providerItem = m_loaderState.itemReader().setupItemFromFile( + providerFile, dependsItemLocation); + if (providerItem->type() != ItemType::ModuleProvider) { + throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', " + "but '%3' was expected.") + .arg(providerFile, providerItem->typeName(), + BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider))); + } + + Item * const scope = createProviderScope(product, qbsModule); + for (auto it = providerItem->properties().begin(); it != providerItem->properties().end(); ++it) + it.value()->setScope(scope, {}); + providerItem->setProperty( + StringConstants::nameProperty(), + VariantValue::create(name.toString())); + providerItem->setProperty( + QStringLiteral("outputBaseDir"), + VariantValue::create(outputBaseDir)); + providerItem->overrideProperties(moduleConfig, name, + m_loaderState.parameters(), m_loaderState.logger()); + + const bool isEager = m_loaderState.evaluator().boolValue( + providerItem, StringConstants::isEagerProperty()); + if (!isEager) { + providerItem->setProperty( + StringConstants::moduleNameProperty(), + VariantValue::create(moduleName.toString())); + } + + ProbesResolver(m_loaderState).resolveProbes(product, providerItem); + + EvalContextSwitcher contextSwitcher(m_loaderState.evaluator().engine(), + EvalContext::ModuleProvider); + + checkAllowedValues(providerItem); + auto searchPaths = m_loaderState.evaluator().stringListValue( + providerItem, QStringLiteral("relativeSearchPaths")); + auto prependBaseDir = [&outputBaseDir](const auto &path) { + return outputBaseDir + QLatin1Char('/') + path; + }; + std::transform(searchPaths.begin(), searchPaths.end(), searchPaths.begin(), prependBaseDir); + return {searchPaths, isEager}; +} + +void ModuleProviderLoader::checkAllowedValues(Item *providerItem) +{ + for (const auto &propertyDeclaration : providerItem->propertyDeclarations()) { + if (!propertyDeclaration.shouldCheckAllowedValues()) + continue; + const auto &name = propertyDeclaration.name(); + if (name == QStringLiteral("relativeSearchPaths")) + continue; + const auto value = m_loaderState.evaluator().variantValue(providerItem, name); + const auto propertyValue = providerItem->property(name); + propertyDeclaration.checkAllowedValues( + value, propertyValue->location(), name, m_loaderState); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h new file mode 100644 index 000000000..471cfbb02 --- /dev/null +++ b/src/lib/corelib/loader/moduleproviderloader.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MODULEPROVIDERLOADER_H +#define MODULEPROVIDERLOADER_H + +#include "loaderutils.h" + +#include <language/forward_decls.h> +#include <language/moduleproviderinfo.h> + +#include <QtCore/qvariant.h> + +#include <optional> +#include <utility> +#include <vector> + +namespace qbs::Internal { +class Item; +class LoaderState; +class ProductContext; + +class ModuleProviderLoader +{ +public: + explicit ModuleProviderLoader(LoaderState &loaderState); + + struct ModuleProviderResult { + bool providerFound = false; + std::optional<QStringList> searchPaths; + }; + ModuleProviderResult executeModuleProviders( + ProductContext &productContext, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName); + +private: + enum class ModuleProviderLookup { Scoped, Named }; + struct Provider { + QualifiedId name; + ModuleProviderLookup lookup; + }; + ModuleProviderResult executeModuleProvidersHelper( + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + const std::vector<Provider> &providers); + std::pair<const ModuleProviderInfo &, bool> + findOrCreateProviderInfo(ProductContext &product, const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, const QualifiedId &name, + ModuleProviderLookup lookupType, const QVariantMap &qbsModule); + void setupModuleProviderConfig(ProductContext &product); + + std::optional<std::vector<QualifiedId>> getModuleProviders(Item *item); + + QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType); + QVariantMap evaluateQbsModule(ProductContext &product) const; + Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule); + using EvaluationResult = std::pair<QStringList, bool /*isEager*/>; + EvaluationResult evaluateModuleProvider( + ProductContext &product, + const CodeLocation &dependsItemLocation, + const QualifiedId &moduleName, + const QualifiedId &name, + const QString &providerFile, + const QVariantMap &moduleConfig, + const QVariantMap &qbsModule); + void checkAllowedValues(Item *providerItem); + + LoaderState &m_loaderState; +}; + +} // namespace qbs::Internal + +#endif // MODULEPROVIDERLOADER_H diff --git a/src/lib/corelib/loader/probesresolver.cpp b/src/lib/corelib/loader/probesresolver.cpp new file mode 100644 index 000000000..269e9c4b6 --- /dev/null +++ b/src/lib/corelib/loader/probesresolver.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2022 Raphaël Cotty <raphael.cotty@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "probesresolver.h" + +#include "itemreader.h" +#include "loaderutils.h" + +#include <api/languageinfo.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/item.h> +#include <language/language.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/logger.h> +#include <logging/translator.h> +#include <tools/profiling.h> +#include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <quickjs.h> + +namespace qbs { +namespace Internal { + +static QString probeGlobalId(Item *probe) +{ + QString id; + + for (Item *obj = probe; obj; obj = obj->prototype()) { + if (!obj->id().isEmpty()) { + id = obj->id(); + break; + } + } + + if (id.isEmpty()) + return {}; + + QBS_CHECK(probe->file()); + return id + QLatin1Char('_') + probe->file()->filePath(); +} + +ProbesResolver::ProbesResolver(LoaderState &loaderState) : m_loaderState(loaderState) {} + +void ProbesResolver::resolveProbes(ProductContext &productContext, Item *item) +{ + AccumulatingTimer probesTimer(m_loaderState.parameters().logElapsedTime() + ? &productContext.timingData.probes : nullptr); + + EvalContextSwitcher evalContextSwitcher(m_loaderState.evaluator().engine(), + EvalContext::ProbeExecution); + for (Item * const child : item->children()) { + if (child->type() == ItemType::Probe) + resolveProbe(productContext, item, child); + } +} + +void ProbesResolver::resolveProbe(ProductContext &productContext, Item *parent, + Item *probe) +{ + qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString(); + const QString &probeId = probeGlobalId(probe); + if (Q_UNLIKELY(probeId.isEmpty())) + throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location()); + const bool isProjectLevelProbe + = parent->type() == ItemType::Project + || productContext.name.startsWith(StringConstants::shadowProductPrefix()); + const JSSourceValueConstPtr configureScript + = probe->sourceProperty(StringConstants::configureProperty()); + QBS_CHECK(configureScript); + if (Q_UNLIKELY(configureScript->sourceCode() == StringConstants::undefinedValue())) + throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); + using ProbeProperty = std::pair<QString, ScopedJsValue>; + std::vector<ProbeProperty> probeBindings; + Evaluator &evaluator = m_loaderState.evaluator(); + ScriptEngine * const engine = evaluator.engine(); + JSContext * const ctx = engine->context(); + QVariantMap initialProperties; + for (Item *obj = probe; obj; obj = obj->prototype()) { + const Item::PropertyMap &props = obj->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString &name = it.key(); + if (name == StringConstants::configureProperty()) + continue; + const JSValue value = evaluator.value(probe, name); + probeBindings.emplace_back(name, ScopedJsValue(ctx, value)); + if (name != StringConstants::conditionProperty()) + initialProperties.insert(name, getJsVariant(ctx, value)); + } + } + const bool condition = evaluator.boolValue(probe, StringConstants::conditionProperty()); + const QString &sourceCode = configureScript->sourceCode().toString(); + ProbeConstPtr resolvedProbe; + std::lock_guard lock(m_loaderState.topLevelProject().probesCacheLock()); + m_loaderState.topLevelProject().incrementProbesCount(); + if (isProjectLevelProbe) { + resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); + } else { + resolvedProbe = findOldProductProbe(productContext.uniqueName(), condition, + initialProperties, sourceCode); + } + if (!resolvedProbe) { + resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties); + if (resolvedProbe) { + qCDebug(lcModuleLoader) << "probe results cached from current run"; + m_loaderState.topLevelProject().incrementReusedCurrentProbesCount(); + } + } else { + qCDebug(lcModuleLoader) << "probe results cached from earlier run"; + m_loaderState.topLevelProject().incrementReusedOldProbesCount(); + } + ScopedJsValue configureScope(ctx, JS_UNDEFINED); + std::vector<QString> importedFilesUsedInConfigure; + if (!condition) { + qCDebug(lcModuleLoader) << "Probe disabled; skipping"; + } else if (!resolvedProbe) { + m_loaderState.topLevelProject().incrementRunProbesCount(); + qCDebug(lcModuleLoader) << "configure script needs to run"; + const Evaluator::FileContextScopes fileCtxScopes + = evaluator.fileContextScopes(configureScript->file()); + configureScope.setValue(engine->newObject()); + for (const ProbeProperty &b : probeBindings) + setJsProperty(ctx, configureScope, b.first, JS_DupValue(ctx, b.second)); + engine->clearRequestedProperties(); + ScopedJsValue sv(ctx, engine->evaluate(JsValueOwner::Caller, + configureScript->sourceCodeForEvaluation(), {}, 1, + {fileCtxScopes.fileScope, fileCtxScopes.importScope, configureScope})); + if (JsException ex = engine->checkAndClearException(configureScript->location())) + throw ex.toErrorInfo(); + importedFilesUsedInConfigure = engine->importedFilesUsedInScript(); + } else { + importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed(); + } + QVariantMap properties; + VariantValuePtr storedValue; + QMap<QString, VariantValuePtr> storedValues; + for (const ProbeProperty &b : probeBindings) { + QVariant newValue; + if (resolvedProbe) { + newValue = resolvedProbe->properties().value(b.first); + } else { + if (condition) { + JSValue v = getJsProperty(ctx, configureScope, b.first); + const JSValue saved = v; + ScopedJsValue valueMgr(ctx, saved); + const PropertyDeclaration decl = probe->propertyDeclaration(b.first); + evaluator.convertToPropertyType(decl, probe->location(), v); + + // If the value was converted from scalar to array as per our convenience + // functionality, then the original value is now the only element of a + // newly allocated array and thus gets deleted via that array. + // The array itself is owned by the script engine, so we must stay out of + // memory management here. + if (v != saved) + valueMgr.setValue(JS_UNDEFINED); + + if (JsException ex = engine->checkAndClearException({})) + throw ex.toErrorInfo(); + newValue = getJsVariant(ctx, v); + // Special case, string and path lists are represented as js arrays but we don't + // want to make them const as we do for object lists. Converting to QStringList + // allows to distinguish between these two cases in ScriptEngine::asJsValue + if (newValue.userType() == QMetaType::QVariantList + && (decl.type() == PropertyDeclaration::StringList + || decl.type() == PropertyDeclaration::PathList)) { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + newValue.convert(QMetaType(QMetaType::QStringList)); +#else + newValue.convert(QMetaType::QStringList); +#endif + } + } else { + newValue = initialProperties.value(b.first); + } + } + if (!qVariantsEqual(newValue, getJsVariant(ctx, b.second))) { + if (!resolvedProbe) + storedValue = VariantValue::createStored(newValue); + else + storedValue = resolvedProbe->values().value(b.first); + + probe->setProperty(b.first, storedValue); + } + if (!resolvedProbe) { + properties.insert(b.first, newValue); + storedValues[b.first] = storedValue; + } + } + if (!resolvedProbe) { + resolvedProbe = Probe::create(probeId, probe->location(), condition, + sourceCode, properties, initialProperties, storedValues, + importedFilesUsedInConfigure); + m_loaderState.topLevelProject().addNewlyResolvedProbe(resolvedProbe); + } + if (isProjectLevelProbe) + m_loaderState.topLevelProject().addProjectLevelProbe(resolvedProbe); + else + productContext.probes << resolvedProbe; +} + +ProbeConstPtr ProbesResolver::findOldProjectProbe( + const QString &globalId, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_loaderState.parameters().forceProbeExecution()) + return {}; + return m_loaderState.topLevelProject().findOldProjectProbe(globalId, + [&](const ProbeConstPtr &oldProbe) { + return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes); + }); +} + +ProbeConstPtr ProbesResolver::findOldProductProbe( + const QString &productName, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_loaderState.parameters().forceProbeExecution()) + return {}; + return m_loaderState.topLevelProject().findOldProductProbe(productName, + [&](const ProbeConstPtr &oldProbe) { + return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes); + }); +} + +ProbeConstPtr ProbesResolver::findCurrentProbe( + const CodeLocation &location, + bool condition, + const QVariantMap &initialProperties) const +{ + return m_loaderState.topLevelProject().findCurrentProbe(location, + [&](const ProbeConstPtr &probe) { + return probeMatches(probe, condition, initialProperties, QString(), CompareScript::No); + }); +} + +bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const +{ + return probe->condition() == condition + && qVariantMapsEqual(probe->initialProperties(), initialProperties) + && (compareScript == CompareScript::No + || (probe->configureScript() == configureScript + && !probe->needsReconfigure(m_loaderState.topLevelProject().lastResolveTime()))); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/probesresolver.h b/src/lib/corelib/loader/probesresolver.h new file mode 100644 index 000000000..4c49861d3 --- /dev/null +++ b/src/lib/corelib/loader/probesresolver.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Copyright (C) 2022 Raphaël Cotty <raphael.cotty@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROBESRESOLVER_H +#define PROBESRESOLVER_H + +#include <language/forward_decls.h> +#include <tools/codelocation.h> + +#include <QString> + +namespace qbs::Internal { +class Item; +class LoaderState; +class ProductContext; + +class ProbesResolver +{ +public: + explicit ProbesResolver(LoaderState &loaderState); + void resolveProbes(ProductContext &productContext, Item *item); + +private: + ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findOldProductProbe(const QString &productName, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findCurrentProbe(const CodeLocation &location, bool condition, + const QVariantMap &initialProperties) const; + enum class CompareScript { No, Yes }; + bool probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const; + void resolveProbe(ProductContext &productContext, Item *parent, Item *probe); + + LoaderState &m_loaderState; +}; + +} // namespace qbs::Internal + +#endif // PROBESRESOLVER_H diff --git a/src/lib/corelib/loader/productitemmultiplexer.cpp b/src/lib/corelib/loader/productitemmultiplexer.cpp new file mode 100644 index 000000000..0adb3d7fd --- /dev/null +++ b/src/lib/corelib/loader/productitemmultiplexer.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "productitemmultiplexer.h" + +#include "loaderutils.h" +#include "moduleinstantiator.h" + +#include <language/evaluator.h> +#include <language/item.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/translator.h> +#include <tools/scripttools.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <QJsonDocument> + +#include <vector> + +// This module deals with product multiplexing over the various defined axes. +// For instance, a product with qbs.architectures: ["x86", "arm"] will get multiplexed into +// two products with qbs.architecture: "x86" and qbs.architecture: "arm", respectively. +namespace qbs::Internal { +namespace { +using MultiplexRow = std::vector<VariantValuePtr>; +using MultiplexTable = std::vector<MultiplexRow>; +class MultiplexInfo +{ +public: + std::vector<QString> properties; + MultiplexTable table; + bool aggregate = false; + VariantValuePtr multiplexedType; + + QString toIdString(size_t row, LoaderState &loaderState) const; +}; +} // namespace + +class ProductItemMultiplexer +{ +public: + ProductItemMultiplexer(const QString &productName, Item *productItem, Item *tempQbsModuleItem, + const std::function<void()> &dropTempQbsModule, LoaderState &loaderState) + : m_loaderState(loaderState), m_productName(productName), m_productItem(productItem), + m_tempQbsModuleItem(tempQbsModuleItem), m_dropTempQbsModule(dropTempQbsModule) {} + + QList<Item *> multiplex(); +private: + MultiplexInfo extractMultiplexInfo(); + MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values); + + LoaderState &m_loaderState; + const QString &m_productName; + Item * const m_productItem; + Item * const m_tempQbsModuleItem; + const std::function<void()> &m_dropTempQbsModule; +}; + +QList<Item *> ProductItemMultiplexer::multiplex() +{ + const auto multiplexInfo = extractMultiplexInfo(); + m_dropTempQbsModule(); + if (multiplexInfo.table.size() > 1) + m_productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue()); + VariantValuePtr productNameValue = VariantValue::create(m_productName); + Item *aggregator = multiplexInfo.aggregate ? m_productItem->clone(m_loaderState.itemPool()) + : nullptr; + QList<Item *> additionalProductItems; + std::vector<VariantValuePtr> multiplexConfigurationIdValues; + for (size_t row = 0; row < multiplexInfo.table.size(); ++row) { + Item *item = m_productItem; + const auto &mprow = multiplexInfo.table.at(row); + QBS_CHECK(mprow.size() == multiplexInfo.properties.size()); + if (row > 0) { + item = m_productItem->clone(m_loaderState.itemPool()); + additionalProductItems.push_back(item); + } + const QString multiplexConfigurationId = multiplexInfo.toIdString(row, m_loaderState); + const VariantValuePtr multiplexConfigurationIdValue + = VariantValue::create(multiplexConfigurationId); + if (multiplexInfo.table.size() > 1 || aggregator) { + multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue); + item->setProperty(StringConstants::multiplexConfigurationIdProperty(), + multiplexConfigurationIdValue); + } + if (multiplexInfo.multiplexedType) + item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType); + for (size_t column = 0; column < mprow.size(); ++column) { + Item * const qbsItem = retrieveQbsItem(item, m_loaderState); + const QString &propertyName = multiplexInfo.properties.at(column); + const VariantValuePtr &mpvalue = mprow.at(column); + qbsItem->setProperty(propertyName, mpvalue); + } + } + + if (aggregator) { + additionalProductItems << aggregator; + + // Add dependencies to all multiplexed instances. + for (const auto &v : multiplexConfigurationIdValues) { + Item *dependsItem = Item::create(&m_loaderState.itemPool(), ItemType::Depends); + dependsItem->setProperty(StringConstants::nameProperty(), productNameValue); + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), v); + dependsItem->setProperty(StringConstants::profilesProperty(), + VariantValue::create(QStringList())); + dependsItem->setFile(aggregator->file()); + dependsItem->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(), + m_loaderState.logger()); + Item::addChild(aggregator, dependsItem); + } + } + + return additionalProductItems; +} + +MultiplexInfo ProductItemMultiplexer::extractMultiplexInfo() +{ + static const QString mpmKey = QStringLiteral("multiplexMap"); + + Evaluator &evaluator = m_loaderState.evaluator(); + JSContext * const ctx = evaluator.engine()->context(); + const ScopedJsValue multiplexMap(ctx, evaluator.value(m_tempQbsModuleItem, mpmKey)); + const QStringList multiplexByQbsProperties = evaluator.stringListValue( + m_productItem, StringConstants::multiplexByQbsPropertiesProperty()); + + MultiplexInfo multiplexInfo; + multiplexInfo.aggregate = evaluator.boolValue( + m_productItem, StringConstants::aggregateProperty()); + + const QString multiplexedType = evaluator.stringValue( + m_productItem, StringConstants::multiplexedTypeProperty()); + if (!multiplexedType.isEmpty()) + multiplexInfo.multiplexedType = VariantValue::create(multiplexedType); + + Set<QString> uniqueMultiplexByQbsProperties; + for (const QString &key : multiplexByQbsProperties) { + const QString mappedKey = getJsStringProperty(ctx, multiplexMap, key); + if (mappedKey.isEmpty()) + throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key)); + + if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) + continue; + + const ScopedJsValue arr(ctx, evaluator.value(m_tempQbsModuleItem, key)); + if (JS_IsUndefined(arr)) + continue; + if (!JS_IsArray(ctx, arr)) + throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key)); + + const quint32 arrlen = getJsIntProperty(ctx, arr, StringConstants::lengthProperty()); + if (arrlen == 0) + continue; + + MultiplexRow mprow; + mprow.reserve(arrlen); + QVariantList entriesForKey; + for (quint32 i = 0; i < arrlen; ++i) { + const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, arr, i)); + const QVariant value = getJsVariant(ctx, sv); + if (entriesForKey.contains(value)) + continue; + entriesForKey << value; + mprow.push_back(VariantValue::create(value)); + } + multiplexInfo.table = combine(multiplexInfo.table, mprow); + multiplexInfo.properties.push_back(mappedKey); + } + return multiplexInfo; +} + +MultiplexTable ProductItemMultiplexer::combine(const MultiplexTable &table, + const MultiplexRow &values) +{ + MultiplexTable result; + if (table.empty()) { + result.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) { + MultiplexRow row; + row.resize(1); + row[0] = values.at(i); + result[i] = row; + } + } else { + for (const auto &row : table) { + for (const auto &value : values) { + MultiplexRow newRow = row; + newRow.push_back(value); + result.push_back(newRow); + } + } + } + return result; +} + +QString MultiplexInfo::toIdString(size_t row, LoaderState &loaderState) const +{ + const auto &mprow = table.at(row); + QVariantMap multiplexConfiguration; + for (size_t column = 0; column < mprow.size(); ++column) { + const QString &propertyName = properties.at(column); + const VariantValuePtr &mpvalue = mprow.at(column); + multiplexConfiguration.insert(propertyName, mpvalue->value()); + } + QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration) + .toJson(QJsonDocument::Compact) + .toBase64()); + + // Cache for later use in multiplexIdToVariantMap() + loaderState.topLevelProject().addMultiplexConfiguration(id, multiplexConfiguration); + + return id; +} + +QList<Item *> multiplex(const QString &productName, Item *productItem, Item *tempQbsModuleItem, + const std::function<void ()> &dropTempQbsModule, LoaderState &loaderState) +{ + return ProductItemMultiplexer(productName, productItem, tempQbsModuleItem, dropTempQbsModule, + loaderState).multiplex(); +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productitemmultiplexer.h b/src/lib/corelib/loader/productitemmultiplexer.h new file mode 100644 index 000000000..e02e21793 --- /dev/null +++ b/src/lib/corelib/loader/productitemmultiplexer.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QList> + +#include <functional> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs::Internal { +class Item; +class LoaderState; + +// Checks whether the product item is to be multiplexed and returns the list of additional +// product items. In the normal, non-multiplex case, this list is empty. +QList<Item *> multiplex( + const QString &productName, + Item *productItem, + Item *tempQbsModuleItem, + const std::function<void()> &dropTempQbsModule, + LoaderState &loaderState + ); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productresolver.cpp b/src/lib/corelib/loader/productresolver.cpp new file mode 100644 index 000000000..a026a76d6 --- /dev/null +++ b/src/lib/corelib/loader/productresolver.cpp @@ -0,0 +1,1618 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "productresolver.h" + +#include "dependenciesresolver.h" +#include "groupshandler.h" +#include "loaderutils.h" +#include "modulepropertymerger.h" +#include "probesresolver.h" + +#include <jsextensions/jsextensions.h> +#include <jsextensions/moduleproperties.h> +#include <language/artifactproperties.h> +#include <language/builtindeclarations.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/item.h> +#include <language/language.h> +#include <language/propertymapinternal.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/jsliterals.h> +#include <tools/profiling.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <algorithm> + +namespace qbs::Internal { + +class PropertiesEvaluator +{ +public: + PropertiesEvaluator(ProductContext &product, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState) {} + + QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); + QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, + const QVariantMap &tmplt, bool lookupPrototype, + bool checkErrors); + void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, + QVariantMap &result, bool checkErrors); + +private: + ProductContext &m_product; + LoaderState &m_loaderState; +}; + +// Dependency resolving, Probe execution. +// Run for real products and shadow products. +class ProductResolverStage1 +{ +public: + ProductResolverStage1(ProductContext &product, Deferral deferral, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState), m_deferral(deferral) {} + void start(); + +private: + void resolveProbes(); + void resolveProbes(Item *item); + void runModuleProbes(const Item::Module &module); + void updateModulePresentState(const Item::Module &module); + bool validateModule(const Item::Module &module); + void handleModuleSetupError(const Item::Module &module, const ErrorInfo &error); + void checkPropertyDeclarations(); + void mergeDependencyParameters(); + void checkDependencyParameterDeclarations(const Item *productItem, + const QString &productName) const; + void collectCodeLinks(); + + ProductContext &m_product; + LoaderState &m_loaderState; + const Deferral m_deferral; +}; + +// Setting up ResolvedProduct, incuding property evaluation and handling Product child items. +// Run only for real products. +class ProductResolverStage2 +{ +public: + ProductResolverStage2(ProductContext &product, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState) {} + void start(); + +private: + void resolveProductFully(); + void createProductConfig(); + void resolveGroup(Item *item); + void resolveGroupFully(Item *item, bool isEnabled); + QVariantMap resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues); + SourceArtifactPtr createSourceArtifact(const QString &fileName, const GroupPtr &group, + bool wildcard, const CodeLocation &filesLocation, + ErrorInfo *errorInfo); + void resolveExport(Item *exportItem); + std::unique_ptr<ExportedItem> resolveExportChild(const Item *item, + const ExportedModule &module); + void setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector<ExportedProperty> &properties); + QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true); + + void resolveScanner(Item *item, ModuleContext &moduleContext); + void resolveModules(); + void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits); + void applyFileTaggers(); + void finalizeArtifactProperties(); + void collectProductDependencies(); + + ProductContext &m_product; + LoaderState &m_loaderState; + GroupConstPtr m_currentGroup; + FileLocations m_sourceArtifactLocations; + PropertiesEvaluator m_propertiesEvaluator{m_product, m_loaderState}; + + using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>; + QHash<QStringList, ArtifactPropertiesInfo> m_artifactPropertiesPerFilter; +}; + +class ExportsResolver +{ +public: + ExportsResolver(ProductContext &product, LoaderState &loaderState) + : m_product(product), m_loaderState(loaderState) {} + void start(); + +private: + void resolveShadowProduct(); + void collectPropertiesForExportItem(Item *productModuleInstance); + void collectPropertiesForExportItem(const QualifiedId &moduleName, const ValuePtr &value, + Item *moduleInstance, QVariantMap &moduleProps); + void collectPropertiesForModuleInExportItem(const Item::Module &module); + void adaptExportedPropertyValues(); + void collectExportedProductDependencies(); + + ProductContext &m_product; + LoaderState &m_loaderState; + PropertiesEvaluator m_propertiesEvaluator{m_product, m_loaderState}; +}; + +void resolveProduct(ProductContext &product, Deferral deferral, LoaderState &loaderState) +{ + try { + ProductResolverStage1(product, deferral, loaderState).start(); + } catch (const ErrorInfo &err) { + if (err.isCancelException()) { + loaderState.topLevelProject().setCanceled(); + return; + } + product.handleError(err); + } + + if (product.dependenciesResolvingPending()) + return; + + if (product.name.startsWith(StringConstants::shadowProductPrefix())) + return; + + // TODO: The weird double-forwarded error handling can hopefully be simplified now. + try { + ProductResolverStage2(product, loaderState).start(); + } catch (const ErrorInfo &err) { + if (err.isCancelException()) { + loaderState.topLevelProject().setCanceled(); + return; + } + loaderState.topLevelProject().addQueuedError(err); + } +} + +void setupExports(ProductContext &product, LoaderState &loaderState) +{ + ExportsResolver(product, loaderState).start(); +} + +void ProductResolverStage1::start() +{ + TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject(); + topLevelProject.checkCancelation(); + + if (m_product.delayedError.hasError()) + return; + + resolveDependencies(m_product, m_deferral, m_loaderState); + QBS_CHECK(m_product.dependenciesContext); + if (!m_product.dependenciesContext->dependenciesResolved) + return; + if (m_product.delayedError.hasError()) + return; + + // Run probes for modules and product. + resolveProbes(); + + // After the probes have run, we can switch on the evaluator cache. + Evaluator &evaluator = m_loaderState.evaluator(); + FileTags fileTags = evaluator.fileTagsValue(m_product.item, StringConstants::typeProperty()); + EvalCacheEnabler cacheEnabler(&evaluator, evaluator.stringValue( + m_product.item, + StringConstants::sourceDirectoryProperty())); + + // Run module validation scripts. + for (const Item::Module &module : m_product.item->modules()) { + if (!validateModule(module)) + return; + fileTags += evaluator.fileTagsValue( + module.item, StringConstants::additionalProductTypesProperty()); + } + + // Disable modules that have been pulled in only by now-disabled modules. + // Note that this has to happen in the reverse order compared to the other loops, + // with the leaves checked last. + for (auto it = m_product.item->modules().rbegin(); it != m_product.item->modules().rend(); ++it) + updateModulePresentState(*it); + + // Now do the canonical module property values merge. Note that this will remove + // previously attached values from modules that failed validation. + // Evaluator cache entries that could potentially change due to this will be purged. + doFinalMerge(m_product, m_loaderState); + + const bool enabled = topLevelProject.checkItemCondition(m_product.item, evaluator); + + collectCodeLinks(); + mergeDependencyParameters(); + checkDependencyParameterDeclarations(m_product.item, m_product.name); + + setupGroups(m_product, m_loaderState); + + // Collect the full list of fileTags, including the values contributed by modules. + if (!m_product.delayedError.hasError() && enabled + && !m_product.name.startsWith(StringConstants::shadowProductPrefix())) { + topLevelProject.addProductByType(m_product, fileTags); + m_product.item->setProperty(StringConstants::typeProperty(), + VariantValue::create(sorted(fileTags.toStringList()))); + } + + checkPropertyDeclarations(); +} + +void ProductResolverStage1::resolveProbes() +{ + for (const Item::Module &module : m_product.item->modules()) { + runModuleProbes(module); + if (m_product.delayedError.hasError()) + return; + } + resolveProbes(m_product.item); +} + +void ProductResolverStage1::resolveProbes(Item *item) +{ + ProbesResolver(m_loaderState).resolveProbes(m_product, item); +} + +void ProductResolverStage1::runModuleProbes(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + if (module.product && m_loaderState.topLevelProject().isDisabledItem(module.product->item)) { + createNonPresentModule(m_loaderState.itemPool(), module.name.toString(), + QLatin1String("module's exporting product is disabled"), + module.item); + return; + } + try { + resolveProbes(module.item); + if (module.versionRange.minimum.isValid() + || module.versionRange.maximum.isValid()) { + if (module.versionRange.maximum.isValid() + && module.versionRange.minimum >= module.versionRange.maximum) { + throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module " + "'%3'").arg(module.versionRange.minimum.toString(), + module.versionRange.maximum.toString(), + module.name.toString())); + } + const Version moduleVersion = Version::fromString( + m_loaderState.evaluator().stringValue(module.item, + StringConstants::versionProperty())); + if (moduleVersion < module.versionRange.minimum) { + throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " + "at least %3.").arg(module.name.toString(), + moduleVersion.toString(), + module.versionRange.minimum.toString())); + } + if (module.versionRange.maximum.isValid() + && moduleVersion >= module.versionRange.maximum) { + throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " + "lower than %3.").arg(module.name.toString(), + moduleVersion.toString(), + module.versionRange.maximum.toString())); + } + } + } catch (const ErrorInfo &error) { + handleModuleSetupError(module, error); + } +} + +void ProductResolverStage1::updateModulePresentState(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + if (module.name.first() == StringConstants::qbsModule()) + return; + bool hasPresentLoadingItem = false; + for (const Item::Module::LoadContext &loadContext : module.loadContexts) { + const Item * const loadingItem = loadContext.loadingItem(); + if (loadingItem == m_product.item) { + hasPresentLoadingItem = true; + break; + } + if (!loadingItem->isPresentModule()) + continue; + if (loadingItem->prototype() && loadingItem->prototype()->type() == ItemType::Export) { + QBS_CHECK(loadingItem->prototype()->parent()->type() == ItemType::Product); + if (m_loaderState.topLevelProject().isDisabledItem(loadingItem->prototype()->parent())) + continue; + } + hasPresentLoadingItem = true; + break; + } + if (!hasPresentLoadingItem) { + createNonPresentModule(m_loaderState.itemPool(), module.name.toString(), + QLatin1String("imported only by disabled module(s)"), + module.item); + } +} + +bool ProductResolverStage1::validateModule(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return true; + try { + m_loaderState.evaluator().boolValue(module.item, StringConstants::validateProperty()); + for (const auto &dep : module.item->modules()) { + if (dep.required && !dep.item->isPresentModule()) { + throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not " + "loaded successfully") + .arg(module.name.toString(), dep.name.toString())); + } + } + } catch (const ErrorInfo &error) { + handleModuleSetupError(module, error); + if (m_product.delayedError.hasError()) + return false; + } + return true; +} + +void ProductResolverStage1::handleModuleSetupError(const Item::Module &module, + const ErrorInfo &error) +{ + if (module.required) { + m_product.handleError(error); + } else { + qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString() + << "found, but not usable in product" << m_product.name + << error.toString(); + createNonPresentModule(m_loaderState.itemPool(), module.name.toString(), + QStringLiteral("failed validation"), module.item); + } +} + +void ProductResolverStage1::checkPropertyDeclarations() +{ + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.propertyChecking : nullptr); + qbs::Internal::checkPropertyDeclarations(m_product.item, m_loaderState); +} + +void ProductResolverStage1::mergeDependencyParameters() +{ + if (m_loaderState.topLevelProject().isDisabledItem(m_product.item)) + return; + + for (Item::Module &module : m_product.item->modules()) { + if (module.name.toString() == StringConstants::qbsModule()) + continue; + if (!module.item->isPresentModule()) + continue; + + using PP = Item::Module::ParametersWithPriority; + std::vector<PP> priorityList; + const QVariantMap defaultParameters = module.product + ? module.product->defaultParameters + : m_loaderState.topLevelProject().parameters(module.item->prototype()); + priorityList.emplace_back(defaultParameters, INT_MIN); + for (const Item::Module::LoadContext &context : module.loadContexts) { + const QVariantMap ¶meters = context.parameters.first; + + // Empty parameter maps and inactive loading modules do not contribute to the + // final parameter map. + if (parameters.isEmpty()) + continue; + if (context.loadingItem()->type() == ItemType::ModuleInstance + && !context.loadingItem()->isPresentModule()) { + continue; + } + + // Build a list sorted by priority. + static const auto cmp = [](const PP &elem, int prio) { return elem.second < prio; }; + const auto it = std::lower_bound(priorityList.begin(), priorityList.end(), + context.parameters.second, cmp); + priorityList.insert(it, context.parameters); + } + + module.parameters = qbs::Internal::mergeDependencyParameters(std::move(priorityList)); + } +} + +class DependencyParameterDeclarationCheck +{ +public: + DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem, + const TopLevelProjectContext &topLevelProject) + : m_productName(productName), m_productItem(productItem), m_topLevelProject(topLevelProject) + {} + + void operator()(const QVariantMap ¶meters) const { check(parameters, QualifiedId()); } + +private: + void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const + { + for (auto it = parameters.begin(); it != parameters.end(); ++it) { + if (it.value().userType() == QMetaType::QVariantMap) { + check(it.value().toMap(), QualifiedId(moduleName) << it.key()); + } else { + const auto &deps = m_productItem->modules(); + auto m = std::find_if(deps.begin(), deps.end(), + [&moduleName] (const Item::Module &module) { + return module.name == moduleName; + }); + + if (m == deps.end()) { + const QualifiedId fullName = QualifiedId(moduleName) << it.key(); + throw ErrorInfo(Tr::tr("Cannot set parameter '%1', " + "because '%2' does not have a dependency on '%3'.") + .arg(fullName.toString(), m_productName, moduleName.toString()), + m_productItem->location()); + } + + const auto decls = m_topLevelProject.parameterDeclarations( + m->item->rootPrototype()); + if (!decls.contains(it.key())) { + const QualifiedId fullName = QualifiedId(moduleName) << it.key(); + throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.") + .arg(fullName.toString()), m_productItem->location()); + } + } + } + } + + bool moduleExists(const QualifiedId &name) const + { + const auto &deps = m_productItem->modules(); + return any_of(deps, [&name](const Item::Module &module) { + return module.name == name; + }); + } + + const QString &m_productName; + const Item * const m_productItem; + const TopLevelProjectContext &m_topLevelProject; +}; + +void ProductResolverStage1::checkDependencyParameterDeclarations(const Item *productItem, + const QString &productName) const +{ + DependencyParameterDeclarationCheck dpdc(productName, productItem, + m_loaderState.topLevelProject()); + for (const Item::Module &dep : productItem->modules()) { + if (!dep.parameters.empty()) + dpdc(dep.parameters); + } +} + +void ProductResolverStage1::collectCodeLinks() +{ + for (const Item::Module &module : m_product.item->modules()) { + if (module.name.first() == StringConstants::qbsModule()) + continue; + for (const Item::Module::LoadContext &context : module.loadContexts) { + m_loaderState.topLevelProject().addCodeLink( + context.dependsItem->location().filePath(), + context.dependsItem->codeRange(), + module.product ? module.product->item->location() + : module.item->location()); + } + } +} + + +void ProductResolverStage2::start() +{ + m_loaderState.evaluator().clearPropertyDependencies(); + + ResolvedProductPtr product = ResolvedProduct::create(); + product->enabled = m_product.project->project->enabled; + product->moduleProperties = PropertyMapInternal::create(); + product->project = m_product.project->project; + m_product.product = product; + product->location = m_product.item->location(); + const auto errorFromDelayedError = [&] { + if (m_product.delayedError.hasError()) { + ErrorInfo errorInfo; + + // First item is "main error", gets prepended again in the catch clause. + const QList<ErrorItem> &items = m_product.delayedError.items(); + for (int i = 1; i < items.size(); ++i) + errorInfo.append(items.at(i)); + + m_product.delayedError.clear(); + return errorInfo; + } + return ErrorInfo(); + }; + + // Even if we previously encountered an error, try to continue for as long as possible + // to provide IDEs with useful data (e.g. the list of files). + // If we encounter a follow-up error, suppress it and report the original one instead. + try { + resolveProductFully(); + if (const ErrorInfo error = errorFromDelayedError(); error.hasError()) + throw error; + } catch (ErrorInfo e) { + if (const ErrorInfo error = errorFromDelayedError(); error.hasError()) + e = error; + QString mainErrorString = !product->name.isEmpty() + ? Tr::tr("Error while handling product '%1':").arg(product->name) + : Tr::tr("Error while handling product:"); + ErrorInfo fullError(mainErrorString, product->location); + appendError(fullError, e); + if (!product->enabled) { + qCDebug(lcProjectResolver) << fullError.toString(); + return; + } + if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict) + throw fullError; + m_loaderState.logger().printWarning(fullError); + m_loaderState.logger().printWarning( + ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") + .arg(product->name), product->location)); + product->enabled = false; + } +} + +void ProductResolverStage2::resolveProductFully() +{ + Item * const item = m_product.item; + const ResolvedProductPtr product = m_product.product; + Evaluator &evaluator = m_loaderState.evaluator(); + product->name = evaluator.stringValue(item, StringConstants::nameProperty()); + + // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. + m_product.buildDirectory = evaluator.stringValue( + item, StringConstants::buildDirectoryProperty()); + product->multiplexConfigurationId = evaluator.stringValue( + item, StringConstants::multiplexConfigurationIdProperty()); + qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName(); + product->enabled = product->enabled + && evaluator.boolValue(item, StringConstants::conditionProperty()); + const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty()); + if (type) + product->fileTags = FileTags::fromStringList(type->value().toStringList()); + product->targetName = evaluator.stringValue(item, StringConstants::targetNameProperty()); + product->sourceDirectory = evaluator.stringValue( + item, StringConstants::sourceDirectoryProperty()); + product->destinationDirectory = evaluator.stringValue( + item, StringConstants::destinationDirProperty()); + + if (product->destinationDirectory.isEmpty()) { + product->destinationDirectory = m_product.buildDirectory; + } else { + product->destinationDirectory = FileInfo::resolvePath( + product->topLevelProject()->buildDirectory, + product->destinationDirectory); + } + product->probes = m_product.probes; + createProductConfig(); + product->productProperties.insert(StringConstants::destinationDirProperty(), + product->destinationDirectory); + ModuleProperties::init(evaluator.engine(), evaluator.scriptValue(item), product.get()); + + QList<Item *> subItems = item->children(); + const ValuePtr filesProperty = item->property(StringConstants::filesProperty()); + if (filesProperty) { + Item *fakeGroup = Item::create(&m_loaderState.itemPool(), ItemType::Group); + fakeGroup->setFile(item->file()); + fakeGroup->setLocation(item->location()); + fakeGroup->setScope(item); + fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name)); + fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty); + fakeGroup->setProperty(StringConstants::excludeFilesProperty(), + item->property(StringConstants::excludeFilesProperty())); + fakeGroup->setProperty(StringConstants::overrideTagsProperty(), + VariantValue::falseValue()); + fakeGroup->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(), + m_loaderState.logger()); + subItems.prepend(fakeGroup); + } + + for (Item * const child : std::as_const(subItems)) { + switch (child->type()) { + case ItemType::Rule: + resolveRule(m_loaderState, child, m_product.project, &m_product, nullptr); + break; + case ItemType::FileTagger: + resolveFileTagger(m_loaderState, child, nullptr, &m_product); + break; + case ItemType::JobLimit: + resolveJobLimit(m_loaderState, child, nullptr, &m_product, nullptr); + break; + case ItemType::Group: + resolveGroup(child); + break; + case ItemType::Export: + resolveExport(child); + break; + default: + break; + } + } + + for (const ProjectContext *p = m_product.project; p; p = p->parent) { + JobLimits tempLimits = p->jobLimits; + product->jobLimits = tempLimits.update(product->jobLimits); + } + + resolveModules(); + applyFileTaggers(); + finalizeArtifactProperties(); + + for (const RulePtr &rule : m_product.project->rules) { + RulePtr clonedRule = rule->clone(); + clonedRule->product = product.get(); + product->rules.push_back(clonedRule); + } + + collectProductDependencies(); +} + +void ProductResolverStage2::createProductConfig() +{ + EvalCacheEnabler cachingEnabler(&m_loaderState.evaluator(), + m_product.product->sourceDirectory); + m_product.product->moduleProperties->setValue(evaluateModuleValues(m_product.item)); + m_product.product->productProperties = m_propertiesEvaluator.evaluateProperties( + m_product.item, m_product.item, QVariantMap(), true, true); +} + +void ProductResolverStage2::resolveGroup(Item *item) +{ + const bool parentEnabled = m_currentGroup ? m_currentGroup->enabled + : m_product.product->enabled; + const bool isEnabled = parentEnabled + && m_loaderState.evaluator().boolValue(item, StringConstants::conditionProperty()); + try { + resolveGroupFully(item, isEnabled); + } catch (const ErrorInfo &error) { + if (!isEnabled) { + qCDebug(lcProjectResolver) << "error resolving group at" << item->location() + << error.toString(); + return; + } + if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_loaderState.logger().printWarning(error); + } +} + +void ProductResolverStage2::resolveGroupFully(Item *item, bool isEnabled) +{ + AccumulatingTimer groupTimer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.groupsResolving + : nullptr); + + const auto getGroupPropertyMap = [&](const ArtifactProperties *existingProps) { + PropertyMapPtr moduleProperties; + bool newPropertyMapRequired = false; + if (existingProps) + moduleProperties = existingProps->propertyMap(); + if (!moduleProperties) { + newPropertyMapRequired = true; + moduleProperties = m_currentGroup + ? m_currentGroup->properties + : m_product.product->moduleProperties; + } + const QVariantMap newModuleProperties = resolveAdditionalModuleProperties( + item, moduleProperties->value()); + if (!newModuleProperties.empty()) { + if (newPropertyMapRequired) + moduleProperties = PropertyMapInternal::create(); + moduleProperties->setValue(newModuleProperties); + } + return moduleProperties; + }; + + Evaluator &evaluator = m_loaderState.evaluator(); + QStringList files = evaluator.stringListValue(item, StringConstants::filesProperty()); + bool fileTagsSet; + const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty(), + &fileTagsSet); + const QStringList fileTagsFilter + = evaluator.stringListValue(item, StringConstants::fileTagsFilterProperty()); + if (!fileTagsFilter.empty()) { + if (Q_UNLIKELY(!files.empty())) + throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), + item->location()); + + if (!isEnabled) + return; + + ArtifactPropertiesInfo &apinfo = m_artifactPropertiesPerFilter[fileTagsFilter]; + if (apinfo.first) { + const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(), + [item](const CodeLocation &loc) { + return item->location().filePath() == loc.filePath(); + }); + if (it != apinfo.second.cend()) { + ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); + error.append(Tr::tr("First item"), *it); + error.append(Tr::tr("Second item"), item->location()); + throw error; + } + } else { + apinfo.first = ArtifactProperties::create(); + apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); + m_product.product->artifactProperties.push_back(apinfo.first); + } + apinfo.second.push_back(item->location()); + apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get())); + apinfo.first->addExtraFileTags(fileTags); + return; + } + QStringList patterns; + for (int i = files.size(); --i >= 0;) { + if (FileInfo::isPattern(files[i])) + patterns.push_back(files.takeAt(i)); + } + GroupPtr group = ResolvedGroup::create(); + bool prefixWasSet = false; + group->prefix = evaluator.stringValue(item, StringConstants::prefixProperty(), QString(), + &prefixWasSet); + if (!prefixWasSet && m_currentGroup) + group->prefix = m_currentGroup->prefix; + if (!group->prefix.isEmpty()) { + for (auto it = files.rbegin(), end = files.rend(); it != end; ++it) + it->prepend(group->prefix); + } + group->location = item->location(); + group->enabled = isEnabled; + group->properties = getGroupPropertyMap(nullptr); + group->fileTags = fileTags; + group->overrideTags = evaluator.boolValue(item, StringConstants::overrideTagsProperty()); + if (group->overrideTags && fileTagsSet) { + if (group->fileTags.empty() ) + group->fileTags.insert(unknownFileTag()); + } else if (m_currentGroup) { + group->fileTags.unite(m_currentGroup->fileTags); + } + + const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location(); + const VariantValueConstPtr moduleProp = item->variantProperty( + StringConstants::modulePropertyInternal()); + if (moduleProp) + group->targetOfModule = moduleProp->value().toString(); + ErrorInfo fileError; + if (!patterns.empty()) { + group->wildcards = std::make_unique<SourceWildCards>(); + SourceWildCards *wildcards = group->wildcards.get(); + wildcards->excludePatterns = evaluator.stringListValue( + item, StringConstants::excludeFilesProperty()); + wildcards->patterns = patterns; + wildcards->prefix = group->prefix; + wildcards->baseDir = FileInfo::path(item->file()->filePath()); + wildcards->buildDir = m_product.project->project->topLevelProject()->buildDirectory; + wildcards->expandPatterns(); + for (const QString &fileName : wildcards->expandedFiles) + createSourceArtifact(fileName, group, true, filesLocation, &fileError); + } + + for (const QString &fileName : std::as_const(files)) + createSourceArtifact(fileName, group, false, filesLocation, &fileError); + if (fileError.hasError()) { + if (group->enabled) { + if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict) + throw ErrorInfo(fileError); + m_loaderState.logger().printWarning(fileError); + } else { + qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString(); + } + } + group->name = evaluator.stringValue(item, StringConstants::nameProperty()); + if (group->name.isEmpty()) + group->name = Tr::tr("Group %1").arg(m_product.product->groups.size()); + m_product.product->groups.push_back(group); + + class GroupContextSwitcher { + public: + GroupContextSwitcher(ProductResolverStage2 &resolver, const GroupConstPtr &newGroup) + : m_resolver(resolver), m_oldGroup(resolver.m_currentGroup) { + resolver.m_currentGroup = newGroup; + } + ~GroupContextSwitcher() { m_resolver.m_currentGroup = m_oldGroup; } + private: + ProductResolverStage2 &m_resolver; + const GroupConstPtr m_oldGroup; + }; + GroupContextSwitcher groupSwitcher(*this, group); + for (Item * const childItem : item->children()) + resolveGroup(childItem); +} + +SourceArtifactPtr ProductResolverStage2::createSourceArtifact( + const QString &fileName, const GroupPtr &group, bool wildcard, + const CodeLocation &filesLocation, ErrorInfo *errorInfo) +{ + const QString &baseDir = FileInfo::path(group->location.filePath()); + const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName)); + if (!wildcard && !FileInfo(absFilePath).exists()) { + if (errorInfo) + errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation); + m_product.product->missingSourceFiles << absFilePath; + return {}; + } + if (group->enabled) { + CodeLocation &loc = m_sourceArtifactLocations[ + std::make_pair(group->targetOfModule, absFilePath)]; + if (loc.isValid()) { + if (errorInfo) { + errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath)); + errorInfo->append(Tr::tr("First occurrence is here."), loc); + errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation); + } + return {}; + } + loc = filesLocation; + } + SourceArtifactPtr artifact = SourceArtifactInternal::create(); + artifact->absoluteFilePath = absFilePath; + artifact->fileTags = group->fileTags; + artifact->overrideFileTags = group->overrideTags; + artifact->properties = group->properties; + artifact->targetOfModule = group->targetOfModule; + artifact->fromWildcard = wildcard; + group->files.push_back(artifact); + return artifact; +} + +static QualifiedIdSet propertiesToEvaluate(std::deque<QualifiedId> initialProps, + const PropertyDependencies &deps) +{ + std::deque<QualifiedId> remainingProps = std::move(initialProps); + QualifiedIdSet allProperties; + while (!remainingProps.empty()) { + const QualifiedId prop = remainingProps.front(); + remainingProps.pop_front(); + const auto insertResult = allProperties.insert(prop); + if (!insertResult.second) + continue; + transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; }); + } + return allProperties; +} + +QVariantMap ProductResolverStage2::resolveAdditionalModuleProperties( + const Item *group, const QVariantMap ¤tValues) +{ + // Step 1: Retrieve the properties directly set in the group + const ModulePropertiesPerGroup &mp = m_product.modulePropertiesSetInGroups; + const auto it = mp.find(group); + if (it == mp.end()) + return {}; + const QualifiedIdSet &propsSetInGroup = it->second; + + // Step 2: Gather all properties that depend on these properties. + const QualifiedIdSet &propsToEval = propertiesToEvaluate( + rangeTo<std::deque<QualifiedId>>(propsSetInGroup), + m_loaderState.evaluator().propertyDependencies()); + + // Step 3: Evaluate all these properties and replace their values in the map + QVariantMap modulesMap = currentValues; + QHash<QString, QStringList> propsPerModule; + for (auto fullPropName : propsToEval) { + const QString moduleName + = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString(); + propsPerModule[moduleName] << fullPropName.last(); + } + EvalCacheEnabler cachingEnabler(&m_loaderState.evaluator(), + m_product.product->sourceDirectory); + for (const Item::Module &module : group->modules()) { + const QString &fullModName = module.name.toString(); + const QStringList propsForModule = propsPerModule.take(fullModName); + if (propsForModule.empty()) + continue; + QVariantMap reusableValues = modulesMap.value(fullModName).toMap(); + for (const QString &prop : std::as_const(propsForModule)) + reusableValues.remove(prop); + modulesMap.insert(fullModName, m_propertiesEvaluator.evaluateProperties( + module.item, module.item, reusableValues, true, true)); + } + return modulesMap; +} + +static QString getLineAtLocation(const CodeLocation &loc, const QString &content) +{ + int pos = 0; + int currentLine = 1; + while (currentLine < loc.line()) { + while (content.at(pos++) != QLatin1Char('\n')) + ; + ++currentLine; + } + const int eolPos = content.indexOf(QLatin1Char('\n'), pos); + return content.mid(pos, eolPos - pos); +} + +static bool usesImport(const ExportedProperty &prop, const QRegularExpression ®ex) +{ + return prop.sourceCode.indexOf(regex) != -1; +} + +static bool usesImport(const ExportedItem &item, const QRegularExpression ®ex) +{ + return any_of(item.properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(item.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +static bool usesImport(const ExportedModule &module, const QString &name) +{ + // Imports are used in three ways: + // (1) var f = new TextFile(...); + // (2) var path = FileInfo.joinPaths(...) + // (3) var obj = DataCollection; + const QString pattern = QStringLiteral("\\b%1\\b"); + + const QRegularExpression regex(pattern.arg(name)); // std::regex is much slower + return any_of(module.m_properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(module.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +void ProductResolverStage2::resolveExport(Item *exportItem) +{ + ExportedModule &exportedModule = m_product.product->exportedModule; + setupExportedProperties(exportItem, QString(), exportedModule.m_properties); + static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) { + return p1.fullName < p2.fullName; + }; + std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc); + + transform(exportItem->children(), exportedModule.children, + [&exportedModule, this](const auto &child) { + return resolveExportChild(child, exportedModule); }); + + for (const JsImport &jsImport : exportItem->file()->jsImports()) { + if (usesImport(exportedModule, jsImport.scopeName)) { + exportedModule.importStatements << getLineAtLocation(jsImport.location, + exportItem->file()->content()); + } + } + const auto builtInImports = JsExtensions::extensionNames(); + for (const QString &builtinImport: builtInImports) { + if (usesImport(exportedModule, builtinImport)) + exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport; + } + exportedModule.importStatements.sort(); +} + +// TODO: This probably wouldn't be necessary if we had item serialization. +std::unique_ptr<ExportedItem> ProductResolverStage2::resolveExportChild( + const Item *item, const ExportedModule &module) +{ + std::unique_ptr<ExportedItem> exportedItem(new ExportedItem); + + // This is the type of the built-in base item. It may turn out that we need to support + // derived items under Export. In that case, we probably need a new Item member holding + // the original type name. + exportedItem->name = item->typeName(); + + transform(item->children(), exportedItem->children, [&module, this](const auto &child) { + return resolveExportChild(child, module); }); + + setupExportedProperties(item, QString(), exportedItem->properties); + return exportedItem; +} + +void ProductResolverStage2::setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector<ExportedProperty> &properties) +{ + const auto &props = item->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString qualifiedName = namePrefix.isEmpty() + ? it.key() : namePrefix + QLatin1Char('.') + it.key(); + if ((item->type() == ItemType::Export || item->type() == ItemType::Properties) + && qualifiedName == StringConstants::prefixMappingProperty()) { + continue; + } + const ValuePtr &v = it.value(); + if (v->type() == Value::ItemValueType) { + setupExportedProperties(std::static_pointer_cast<ItemValue>(v)->item(), + qualifiedName, properties); + continue; + } + ExportedProperty exportedProperty; + exportedProperty.fullName = qualifiedName; + exportedProperty.type = item->propertyDeclaration(it.key()).type(); + if (v->type() == Value::VariantValueType) { + exportedProperty.sourceCode = toJSLiteral( + std::static_pointer_cast<VariantValue>(v)->value()); + } else { + QBS_CHECK(v->type() == Value::JSSourceValueType); + const JSSourceValue * const sv = static_cast<JSSourceValue *>(v.get()); + exportedProperty.sourceCode = sv->sourceCode().toString(); + } + const ItemDeclaration itemDecl + = BuiltinDeclarations::instance().declarationsForType(item->type()); + PropertyDeclaration propertyDecl; + const auto itemProperties = itemDecl.properties(); + for (const PropertyDeclaration &decl : itemProperties) { + if (decl.name() == it.key()) { + propertyDecl = decl; + exportedProperty.isBuiltin = true; + break; + } + } + + // Do not add built-in properties that were left at their default value. + if (!exportedProperty.isBuiltin + || m_loaderState.evaluator().isNonDefaultValue(item, it.key())) { + properties.push_back(exportedProperty); + } + } + + // Order the list of properties, so the output won't look so random. + static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool { + const int p1ComponentCount = p1.fullName.count(QLatin1Char('.')); + const int p2ComponentCount = p2.fullName.count(QLatin1Char('.')); + if (p1.isBuiltin && !p2.isBuiltin) + return true; + if (!p1.isBuiltin && p2.isBuiltin) + return false; + if (p1ComponentCount < p2ComponentCount) + return true; + if (p1ComponentCount > p2ComponentCount) + return false; + return p1.fullName < p2.fullName; + }; + std::sort(properties.begin(), properties.end(), less); +} + +QVariantMap ProductResolverStage2::evaluateModuleValues(Item *item, bool lookupPrototype) +{ + QVariantMap moduleValues; + for (const Item::Module &module : item->modules()) { + if (!module.item->isPresentModule()) + continue; + const QString fullName = module.name.toString(); + moduleValues[fullName] = m_propertiesEvaluator.evaluateProperties( + module.item, lookupPrototype, true); + } + return moduleValues; +} + +void ProductResolverStage2::resolveScanner(Item *item, ModuleContext &moduleContext) +{ + Evaluator &evaluator = m_loaderState.evaluator(); + if (!evaluator.boolValue(item, StringConstants::conditionProperty())) { + qCDebug(lcProjectResolver) << "scanner condition is false"; + return; + } + + ResolvedScannerPtr scanner = ResolvedScanner::create(); + scanner->module = moduleContext.module; + scanner->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty()); + scanner->recursive = evaluator.boolValue(item, StringConstants::recursiveProperty()); + scanner->searchPathsScript.initialize(m_loaderState.topLevelProject().scriptFunctionValue( + item, StringConstants::searchPathsProperty())); + scanner->scanScript.initialize(m_loaderState.topLevelProject().scriptFunctionValue( + item, StringConstants::scanProperty())); + m_product.product->scanners.push_back(scanner); +} + +void ProductResolverStage2::resolveModules() +{ + JobLimits jobLimits; + for (const Item::Module &m : m_product.item->modules()) + resolveModule(m.name, m.item, m.product, m.parameters, jobLimits); + for (int i = 0; i < jobLimits.count(); ++i) { + const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i); + if (m_product.product->jobLimits.getLimit(moduleJobLimit.pool()) == -1) + m_product.product->jobLimits.setJobLimit(moduleJobLimit); + } +} + +void ProductResolverStage2::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits) +{ + if (!item->isPresentModule()) + return; + + ModuleContext moduleContext; + moduleContext.module = ResolvedModule::create(); + + const ResolvedModulePtr &module = moduleContext.module; + module->name = moduleName.toString(); + module->isProduct = isProduct; + module->product = m_product.product.get(); + module->setupBuildEnvironmentScript.initialize(m_loaderState.topLevelProject() + .scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty())); + module->setupRunEnvironmentScript.initialize(m_loaderState.topLevelProject() + .scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty())); + + for (const Item::Module &m : item->modules()) { + if (m.item->isPresentModule()) + module->moduleDependencies += m.name.toString(); + } + + m_product.product->modules.push_back(module); + if (!parameters.empty()) + m_product.product->moduleParameters[module] = parameters; + + for (Item *child : item->children()) { + switch (child->type()) { + case ItemType::Rule: + resolveRule(m_loaderState, child, nullptr, &m_product, &moduleContext); + break; + case ItemType::FileTagger: + resolveFileTagger(m_loaderState, child, nullptr, &m_product); + break; + case ItemType::JobLimit: + resolveJobLimit(m_loaderState, child, nullptr, nullptr, &moduleContext); + break; + case ItemType::Scanner: + resolveScanner(child, moduleContext); + break; + default: + break; + } + } + for (int i = 0; i < moduleContext.jobLimits.count(); ++i) { + const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i); + const int oldLimit = jobLimits.getLimit(newJobLimit.pool()); + if (oldLimit == -1 || oldLimit > newJobLimit.limit()) + jobLimits.setJobLimit(newJobLimit); + } +} + +void ProductResolverStage2::applyFileTaggers() +{ + m_product.product->fileTaggers << m_product.project->fileTaggers; + m_product.product->fileTaggers = sorted(m_product.product->fileTaggers, + [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) { + return a->priority() > b->priority(); + }); + for (const SourceArtifactPtr &artifact : m_product.product->allEnabledFiles()) { + if (!artifact->overrideFileTags || artifact->fileTags.empty()) { + const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); + const FileTags fileTags = m_product.product->fileTagsForFileName(fileName); + artifact->fileTags.unite(fileTags); + if (artifact->fileTags.empty()) + artifact->fileTags.insert(unknownFileTag()); + qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags + << "to" << fileName; + } + } +} + +void ProductResolverStage2::finalizeArtifactProperties() +{ + for (const SourceArtifactPtr &artifact : m_product.product->allEnabledFiles()) { + for (const auto &artifactProperties : m_product.product->artifactProperties) { + if (!artifact->isTargetOfModule() + && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) { + // FIXME: Should be merged, not overwritten. + artifact->properties = artifactProperties->propertyMap(); + } + } + + // Let a positive value of qbs.install imply the file tag "installable". + if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + artifact->fileTags += "installable"; + } +} + +void ProductResolverStage2::collectProductDependencies() +{ + const ResolvedProductPtr &product = m_product.product; + if (!product) + return; + for (const Item::Module &module : m_product.item->modules()) { + if (!module.product) + continue; + const ResolvedProductPtr &dep = module.product->product; + QBS_CHECK(dep); + QBS_CHECK(dep != product); + product->dependencies << dep; + product->dependencyParameters.insert(dep, module.parameters); // TODO: Streamline this with normal module dependencies? + } + + // TODO: We might want to keep the topological sorting and get rid of "module module dependencies". + std::sort(product->dependencies.begin(),product->dependencies.end(), + [](const ResolvedProductPtr &p1, const ResolvedProductPtr &p2) { + return p1->fullDisplayName() < p2->fullDisplayName(); + }); +} + +void ExportsResolver::start() +{ + resolveShadowProduct(); + collectExportedProductDependencies(); +} + +void ExportsResolver::resolveShadowProduct() +{ + if (!m_product.product->enabled) + return; + if (!m_product.shadowProduct) + return; + for (const auto &m : m_product.shadowProduct->item->modules()) { + if (m.name.toString() != m_product.product->name) + continue; + collectPropertiesForExportItem(m.item); + for (const auto &dep : m.item->modules()) + collectPropertiesForModuleInExportItem(dep); + break; + } + try { + adaptExportedPropertyValues(); + } catch (const ErrorInfo &) {} +} + +class TempScopeSetter +{ +public: + TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope()) + { + value->setScope(newScope, {}); + } + ~TempScopeSetter() { if (m_value) m_value->setScope(m_oldScope, {}); } + + TempScopeSetter(const TempScopeSetter &) = delete; + TempScopeSetter &operator=(const TempScopeSetter &) = delete; + TempScopeSetter &operator=(TempScopeSetter &&) = delete; + + TempScopeSetter(TempScopeSetter &&other) noexcept + : m_value(std::move(other.m_value)), m_oldScope(other.m_oldScope) + { + other.m_value.reset(); + other.m_oldScope = nullptr; + } + +private: + ValuePtr m_value; + Item *m_oldScope; +}; + +void ExportsResolver::collectPropertiesForExportItem( + const QualifiedId &moduleName, const ValuePtr &value, Item *moduleInstance, + QVariantMap &moduleProps) +{ + QBS_CHECK(value->type() == Value::ItemValueType); + Item * const itemValueItem = std::static_pointer_cast<ItemValue>(value)->item(); + if (itemValueItem->propertyDeclarations().isEmpty()) { + for (const Item::Module &module : moduleInstance->modules()) { + if (module.name == moduleName) { + itemValueItem->setPropertyDeclarations(module.item->propertyDeclarations()); + break; + } + } + } + if (itemValueItem->type() == ItemType::ModuleInstancePlaceholder) { + struct EvalPreparer { + EvalPreparer(Item *valueItem, const QualifiedId &moduleName) + : valueItem(valueItem), + hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) + { + if (!hadName) { + // Evaluator expects a name here. + valueItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(moduleName.toString())); + } + } + ~EvalPreparer() + { + if (!hadName) + valueItem->removeProperty(StringConstants::nameProperty()); + } + Item * const valueItem; + const bool hadName; + }; + EvalPreparer ep(itemValueItem, moduleName); + std::vector<TempScopeSetter> tss; + for (const ValuePtr &v : itemValueItem->properties()) + tss.emplace_back(v, moduleInstance); + moduleProps.insert(moduleName.toString(), m_propertiesEvaluator.evaluateProperties( + itemValueItem, false, false)); + return; + } + QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix); + const Item::PropertyMap &props = itemValueItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QualifiedId fullModuleName = moduleName; + fullModuleName << it.key(); + collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps); + } +} + +void ExportsResolver::collectPropertiesForExportItem(Item *productModuleInstance) +{ + if (!productModuleInstance->isPresentModule()) + return; + Item * const exportItem = productModuleInstance->prototype(); + QBS_CHECK(exportItem); + QBS_CHECK(exportItem->type() == ItemType::Export); + const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance() + .declarationsForType(ItemType::Export).properties(); + ExportedModule &exportedModule = m_product.product->exportedModule; + const auto &props = exportItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + const auto match + = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); }; + if (it.key() != StringConstants::prefixMappingProperty() && + std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) { + continue; + } + if (it.value()->type() == Value::ItemValueType) { + collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance, + exportedModule.modulePropertyValues); + } else { + TempScopeSetter tss(it.value(), productModuleInstance); + m_propertiesEvaluator.evaluateProperty( + exportItem, it.key(), it.value(), exportedModule.propertyValues, false); + } + } +} + +// Collects module properties assigned to in other (higher-level) modules. +void ExportsResolver::collectPropertiesForModuleInExportItem(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + ExportedModule &exportedModule = m_product.product->exportedModule; + if (module.product || module.name.first() == StringConstants::qbsModule()) + return; + const auto checkName = [module](const ExportedModuleDependency &d) { + return module.name.toString() == d.name; + }; + if (any_of(exportedModule.moduleDependencies, checkName)) + return; + + Item *modulePrototype = module.item->prototype(); + while (modulePrototype && modulePrototype->type() != ItemType::Module) + modulePrototype = modulePrototype->prototype(); + if (!modulePrototype) // Can happen for broken products in relaxed mode. + return; + ModuleItemLocker locker(*modulePrototype); + const Item::PropertyMap &props = modulePrototype->properties(); + ExportedModuleDependency dep; + dep.name = module.name.toString(); + for (auto it = props.begin(); it != props.end(); ++it) { + if (it.value()->type() == Value::ItemValueType) + collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties); + } + exportedModule.moduleDependencies.push_back(dep); + + for (const auto &dep : module.item->modules()) + collectPropertiesForModuleInExportItem(dep); +} + +void ExportsResolver::adaptExportedPropertyValues() +{ + QBS_CHECK(m_product.shadowProduct); + ExportedModule &m = m_product.product->exportedModule; + const QVariantList prefixList = m.propertyValues.take( + StringConstants::prefixMappingProperty()).toList(); + const QString shadowProductName = m_loaderState.evaluator().stringValue( + m_product.shadowProduct->item, StringConstants::nameProperty()); + const QString shadowProductBuildDir = m_loaderState.evaluator().stringValue( + m_product.shadowProduct->item, StringConstants::buildDirectoryProperty()); + QVariantMap prefixMap; + for (const QVariant &v : prefixList) { + const QVariantMap o = v.toMap(); + prefixMap.insert(o.value(QStringLiteral("prefix")).toString(), + o.value(QStringLiteral("replacement")).toString()); + } + const auto valueRefersToImportingProduct + = [shadowProductName, shadowProductBuildDir](const QString &value) { + return value.toLower().contains(shadowProductName.toLower()) + || value.contains(shadowProductBuildDir); + }; + static const auto stringMapper = [](const QVariantMap &mappings, const QString &value) + -> QString { + for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) { + if (value.startsWith(it.key())) + return it.value().toString() + value.mid(it.key().size()); + } + return value; + }; + const auto stringListMapper = [&valueRefersToImportingProduct]( + const QVariantMap &mappings, const QStringList &value) -> QStringList { + QStringList result; + result.reserve(value.size()); + for (const QString &s : value) { + if (!valueRefersToImportingProduct(s)) + result.push_back(stringMapper(mappings, s)); + } + return result; + }; + const std::function<QVariant(const QVariantMap &, const QVariant &)> mapper + = [&stringListMapper, &mapper]( + const QVariantMap &mappings, const QVariant &value) -> QVariant { + switch (static_cast<QMetaType::Type>(value.userType())) { + case QMetaType::QString: + return stringMapper(mappings, value.toString()); + case QMetaType::QStringList: + return stringListMapper(mappings, value.toStringList()); + case QMetaType::QVariantMap: { + QVariantMap m = value.toMap(); + for (auto it = m.begin(); it != m.end(); ++it) + it.value() = mapper(mappings, it.value()); + return m; + } + default: + return value; + } + }; + for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (ExportedModuleDependency &dep : m.moduleDependencies) { + for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + } +} + +void ExportsResolver::collectExportedProductDependencies() +{ + if (!m_product.shadowProduct) + return; + const ResolvedProductPtr exportingProduct = m_product.product; + if (!exportingProduct || !exportingProduct->enabled) + return; + Item * const importingProductItem = m_product.shadowProduct->item; + + std::vector<std::pair<ResolvedProductPtr, QVariantMap>> directDeps; + for (const Item::Module &m : importingProductItem->modules()) { + if (m.name.toString() != exportingProduct->name) + continue; + for (const Item::Module &dep : m.item->modules()) { + if (dep.product) + directDeps.emplace_back(dep.product->product, m.parameters); + } + } + for (const auto &dep : directDeps) { + if (!contains(exportingProduct->exportedModule.productDependencies, + dep.first->uniqueName())) { + exportingProduct->exportedModule.productDependencies.push_back( + dep.first->uniqueName()); + } + if (!dep.second.isEmpty()) { + exportingProduct->exportedModule.dependencyParameters.insert(dep.first, + dep.second); + } + } + auto &productDeps = exportingProduct->exportedModule.productDependencies; + std::sort(productDeps.begin(), productDeps.end()); +} + +QVariantMap PropertiesEvaluator::evaluateProperties( + const Item *item, const Item *propertiesContainer, const QVariantMap &tmplt, + bool lookupPrototype, bool checkErrors) +{ + AccumulatingTimer propEvalTimer(m_loaderState.parameters().logElapsedTime() + ? &m_product.timingData.propertyEvaluation + : nullptr); + QVariantMap result = tmplt; + for (auto it = propertiesContainer->properties().begin(); + it != propertiesContainer->properties().end(); ++it) { + evaluateProperty(item, it.key(), it.value(), result, checkErrors); + } + return lookupPrototype && propertiesContainer->prototype() + && propertiesContainer->prototype()->type() != ItemType::Module + ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors) + : result; +} + +QVariantMap PropertiesEvaluator::evaluateProperties(Item *item, bool lookupPrototype, + bool checkErrors) +{ + const QVariantMap tmplt; + return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors); +} + +void PropertiesEvaluator::evaluateProperty( + const Item *item, const QString &propName, const ValuePtr &propValue, QVariantMap &result, + bool checkErrors) +{ + JSContext * const ctx = m_loaderState.evaluator().engine()->context(); + switch (propValue->type()) { + case Value::ItemValueType: + { + // Ignore items. Those point to module instances + // and are handled in evaluateModuleValues(). + break; + } + case Value::JSSourceValueType: + { + if (result.contains(propName)) + break; + const PropertyDeclaration pd = item->propertyDeclaration(propName); + if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) { + break; + } + const ScopedJsValue scriptValue(ctx, m_loaderState.evaluator().property(item, propName)); + if (JsException ex = m_loaderState.evaluator().engine()->checkAndClearException( + propValue->location())) { + if (checkErrors) + throw ex.toErrorInfo(); + } + + // NOTE: Loses type information if scriptValue.isUndefined == true, + // as such QScriptValues become invalid QVariants. + QVariant v; + if (JS_IsFunction(ctx, scriptValue)) { + v = getJsString(ctx, scriptValue); + } else { + v = getJsVariant(ctx, scriptValue); + QVariantMap m = v.toMap(); + if (m.contains(StringConstants::importScopeNamePropertyInternal())) { + QVariantMap tmp = m; + const ScopedJsValue proto(ctx, JS_GetPrototype(ctx, scriptValue)); + m = getJsVariant(ctx, proto).toMap(); + for (auto it = tmp.begin(); it != tmp.end(); ++it) + m.insert(it.key(), it.value()); + v = m; + } + } + + if (pd.type() == PropertyDeclaration::Path && v.isValid()) { + v = v.toString(); + } else if (pd.type() == PropertyDeclaration::PathList + || pd.type() == PropertyDeclaration::StringList) { + v = v.toStringList(); + } else if (pd.type() == PropertyDeclaration::VariantList) { + v = v.toList(); + } + + // Enforce proper type for undefined values (note that path degrades to string). + if (!v.isValid()) + v = pd.typedNullValue(); + + pd.checkAllowedValues(v, propValue->location(), propName, m_loaderState); + + result[propName] = v; + break; + } + case Value::VariantValueType: + { + if (result.contains(propName)) + break; + VariantValuePtr vvp = std::static_pointer_cast<VariantValue>(propValue); + QVariant v = vvp->value(); + + const PropertyDeclaration pd = item->propertyDeclaration(propName); + if (v.isNull() && !pd.isScalar()) // QTBUG-51237 + v = QStringList(); + + pd.checkAllowedValues(v, propValue->location(), propName, m_loaderState); + result[propName] = v; + break; + } + } +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productresolver.h b/src/lib/corelib/loader/productresolver.h new file mode 100644 index 000000000..34c49b2bb --- /dev/null +++ b/src/lib/corelib/loader/productresolver.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +namespace qbs::Internal { +enum class Deferral; +class LoaderState; +class ProductContext; + +// Responsibilities: +// - Resolving dependencies to modules and other products (via DependenciesResolver). +// - Module validation. +// - Running probes (via ProbesResolver) in Product and Module items. +// - Evaluating product and module properties. +// - Handling all Product child items, such as FileTaggers, Rules and so on. +void resolveProduct(ProductContext &product, Deferral deferral, LoaderState &loaderState); + +void setupExports(ProductContext &product, LoaderState &loaderState); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productscollector.cpp b/src/lib/corelib/loader/productscollector.cpp new file mode 100644 index 000000000..042dbd160 --- /dev/null +++ b/src/lib/corelib/loader/productscollector.cpp @@ -0,0 +1,754 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "productscollector.h" + +#include "dependenciesresolver.h" +#include "itemreader.h" +#include "loaderutils.h" +#include "localprofiles.h" +#include "productitemmultiplexer.h" +#include "probesresolver.h" + +#include <language/builtindeclarations.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/item.h> +#include <language/language.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/profile.h> +#include <tools/profiling.h> +#include <language/scriptengine.h> +#include <tools/set.h> +#include <tools/settings.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <QDir> +#include <QDirIterator> + +namespace qbs::Internal { + +class ProductsCollector::Private +{ +public: + Private(LoaderState &loaderState) : loaderState(loaderState) {} + + void handleProject(Item *projectItem, ProjectContext *parentProject, + const Set<QString> &referencedFilePaths); + QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem); + void prepareProduct(ProjectContext &projectContext, Item *productItem, + ProductContext *mainProduct = nullptr); + void handleSubProject(ProjectContext &projectContext, Item *projectItem, + const Set<QString> &referencedFilePaths); + void copyProperties(const Item *sourceProject, Item *targetProject); + QList<Item *> loadReferencedFile( + const QString &relativePath, const CodeLocation &referencingLocation, + const Set<QString> &referencedFilePaths, ProductContext &dummyContext); + bool mergeExportItems(ProductContext &productContext); + bool checkExportItemCondition(Item *exportItem, const ProductContext &product); + void initProductProperties(const ProductContext &product); + void checkProjectNamesInOverrides(); + void collectProductsByNameAndItem(); + void checkProductNamesInOverrides(); + void mergeProperty(Item *dst, const QString &name, const ValuePtr &value); + + LoaderState &loaderState; + Settings settings{loaderState.parameters().settingsDirectory()}; + Set<QString> disabledProjects; + Version qbsVersion; + Item *tempScopeItem = nullptr; + +private: + // TODO: Put this in loaderutils + class TempBaseModuleAttacher { + public: + TempBaseModuleAttacher(Private *d, ProductContext &product); + ~TempBaseModuleAttacher() { drop(); } + void drop(); + Item *tempBaseModuleItem() const { return m_tempBaseModule; } + + private: + Item * const m_productItem; + ValuePtr m_origQbsValue; + Item *m_tempBaseModule = nullptr; + }; +}; + +ProductsCollector::ProductsCollector(LoaderState &loaderState) + : d(makePimpl<Private>(loaderState)) {} + +ProductsCollector::~ProductsCollector() = default; + +void ProductsCollector::run(Item *rootProject) +{ + d->handleProject(rootProject, nullptr, {rootProject->file()->filePath()}); + d->checkProjectNamesInOverrides(); + d->collectProductsByNameAndItem(); + d->checkProductNamesInOverrides(); + d->loaderState.topLevelProject().checkForLocalProfileAsTopLevelProfile( + d->loaderState.parameters().topLevelProfile()); +} + +void ProductsCollector::Private::handleProject(Item *projectItem, ProjectContext *parentProject, + const Set<QString> &referencedFilePaths) +{ + const SetupProjectParameters ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + ItemReader &itemReader = loaderState.itemReader(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + auto p = std::make_unique<ProjectContext>(); + auto &projectContext = *p; + projectContext.item = projectItem; + projectContext.parent = parentProject; + projectContext.topLevelProject = &topLevelProject; + ItemValuePtr itemValue = ItemValue::create(projectItem); + projectContext.scope = Item::create(&loaderState.itemPool(), ItemType::Scope); + projectContext.scope->setFile(projectItem->file()); + projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); + ProductContext dummyProduct; + dummyProduct.project = &projectContext; + dummyProduct.moduleProperties = parameters.finalBuildConfigurationTree(); + dummyProduct.profileModuleProperties = dummyProduct.moduleProperties; + dummyProduct.profileName = parameters.topLevelProfile(); + loadBaseModule(dummyProduct, projectItem, loaderState); + + projectItem->overrideProperties(parameters.overriddenValuesTree(), + StringConstants::projectPrefix(), parameters, logger); + projectContext.name = evaluator.stringValue(projectItem, StringConstants::nameProperty()); + if (projectContext.name.isEmpty()) { + projectContext.name = FileInfo::baseName(projectItem->location().filePath()); + projectItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(projectContext.name)); + } + if (parentProject) + parentProject->children.push_back(p.get()); + topLevelProject.addProject(p.release()); + projectItem->overrideProperties(parameters.overriddenValuesTree(), + StringConstants::projectsOverridePrefix() + projectContext.name, + parameters, logger); + if (!topLevelProject.checkItemCondition(projectItem, evaluator)) { + disabledProjects.insert(projectContext.name); + return; + } + SearchPathsManager searchPathsManager(itemReader, itemReader.readExtraSearchPaths(projectItem) + << projectItem->file()->dirPath()); + projectContext.searchPathsStack = itemReader.extraSearchPathsStack(); + + const QString minVersionStr + = evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), + QStringLiteral("1.3.0")); + const Version minVersion = Version::fromString(minVersionStr); + if (!minVersion.isValid()) { + throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " + "is not a valid version string.") + .arg(minVersionStr), projectItem->location()); + } + if (!qbsVersion.isValid()) + qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); + if (qbsVersion < minVersion) { + throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " + "this is qbs version %2.").arg(minVersion.toString(), + qbsVersion.toString())); + } + + for (Item * const child : projectItem->children()) + child->setScope(projectContext.scope); + + ProbesResolver(loaderState).resolveProbes(dummyProduct, projectItem); + + collectProfilesFromItems(projectItem, projectContext.scope, loaderState); + + QList<Item *> multiplexedProducts; + for (Item * const child : projectItem->children()) { + if (child->type() == ItemType::Product) + multiplexedProducts << multiplexProductItem(dummyProduct, child); + } + for (Item * const additionalProductItem : std::as_const(multiplexedProducts)) + Item::addChild(projectItem, additionalProductItem); + + const QList<Item *> originalChildren = projectItem->children(); + for (Item * const child : originalChildren) { + switch (child->type()) { + case ItemType::Product: + prepareProduct(projectContext, child); + break; + case ItemType::SubProject: + handleSubProject(projectContext, child, referencedFilePaths); + break; + case ItemType::Project: + copyProperties(projectItem, child); + handleProject(child, &projectContext, referencedFilePaths); + break; + default: + break; + } + } + + const QStringList refs = evaluator.stringListValue( + projectItem, StringConstants::referencesProperty()); + const CodeLocation referencingLocation + = projectItem->property(StringConstants::referencesProperty())->location(); + QList<Item *> additionalProjectChildren; + for (const QString &filePath : refs) { + try { + additionalProjectChildren << loadReferencedFile( + filePath, referencingLocation, referencedFilePaths, dummyProduct); + } catch (const ErrorInfo &error) { + if (parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + } + } + for (Item * const subItem : std::as_const(additionalProjectChildren)) { + Item::addChild(projectContext.item, subItem); + switch (subItem->type()) { + case ItemType::Product: + prepareProduct(projectContext, subItem); + break; + case ItemType::Project: + copyProperties(projectItem, subItem); + handleProject(subItem, &projectContext, + Set<QString>(referencedFilePaths) << subItem->file()->filePath()); + break; + default: + break; + } + } +} + +QList<Item *> ProductsCollector::Private::multiplexProductItem(ProductContext &dummyContext, + Item *productItem) +{ + // Overriding the product item properties must be done here already, because multiplexing + // properties might depend on product properties. + const QString &nameKey = StringConstants::nameProperty(); + QString productName = loaderState.evaluator().stringValue(productItem, nameKey); + if (productName.isEmpty()) { + productName = FileInfo::completeBaseName(productItem->file()->filePath()); + productItem->setProperty(nameKey, VariantValue::create(productName)); + } + productItem->overrideProperties(loaderState.parameters().overriddenValuesTree(), + StringConstants::productsOverridePrefix() + productName, + loaderState.parameters(), loaderState.logger()); + dummyContext.item = productItem; + TempBaseModuleAttacher tbma(this, dummyContext); + return multiplex(productName, productItem, tbma.tempBaseModuleItem(), + [&] { tbma.drop(); }, loaderState); +} + +void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem, + ProductContext *mainProduct) +{ + const SetupProjectParameters ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + AccumulatingTimer timer(parameters.logElapsedTime() + ? &topLevelProject.timingData().preparingProducts : nullptr); + topLevelProject.checkCancelation(); + qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); + + if (mainProduct) + mainProduct->shadowProduct = std::make_unique<ProductContext>(); + else + projectContext.products.emplace_back(); + ProductContext &productContext = mainProduct + ? *mainProduct->shadowProduct : projectContext.products.back(); + productContext.item = productItem; + productContext.project = &projectContext; + + // Retrieve name, profile and multiplex id. + productContext.name = evaluator.stringValue(productItem, StringConstants::nameProperty()); + QBS_CHECK(!productContext.name.isEmpty()); + const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule(), + loaderState.itemPool()); + if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { + TempBaseModuleAttacher tbma(this, productContext); + productContext.profileName = evaluator.stringValue( + tbma.tempBaseModuleItem(), StringConstants::profileProperty(), QString()); + } else { + productContext.profileName = parameters.topLevelProfile(); + } + productContext.multiplexConfigurationId = evaluator.stringValue( + productItem, StringConstants::multiplexConfigurationIdProperty()); + QBS_CHECK(!productContext.profileName.isEmpty()); + + // Set up full module property map based on the profile. + std::optional<QVariantMap> flatConfig + = topLevelProject.profileConfig(productContext.profileName); + if (!flatConfig) { + const Profile profile(productContext.profileName, &settings, + loaderState.topLevelProject().localProfiles()); + if (!profile.exists()) { + ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()), + productItem->location()); + productContext.handleError(error); + return; + } + flatConfig = SetupProjectParameters::expandedBuildConfiguration( + profile, parameters.configurationName()); + topLevelProject.addProfileConfig(productContext.profileName, *flatConfig); + } + productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree( + *flatConfig, {}); + productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( + *flatConfig, parameters.overriddenValues()); + initProductProperties(productContext); + + // Set up product scope. This is mainly for using the "product" and "project" + // variables in some contexts. + ItemValuePtr itemValue = ItemValue::create(productItem); + productContext.scope = Item::create(&loaderState.itemPool(), ItemType::Scope); + productContext.scope->setProperty(StringConstants::productVar(), itemValue); + productContext.scope->setFile(productItem->file()); + productContext.scope->setScope(productContext.project->scope); + + // If there are any child items with an id, set up a scope for them. This is mostly + // relevant for Probe items. While we might get away with using the file's id scope + // in the absence of multiplexing, having a proper per-product scope seems cleaner. + QBS_CHECK(productItem->scope()); + QBS_CHECK(productItem->scope() == productContext.project->scope); + for (Item * const child : productItem->children()) { + if (child->id().isEmpty()) + continue; + if (productItem->scope() == productContext.project->scope) { + productItem->setScope(Item::create(&loaderState.itemPool(), ItemType::Scope)); + productItem->scope()->setScope(productContext.project->scope); + } + const ItemValuePtr childValue = ItemValue::create(child); + productItem->scope()->setProperty(child->id(), childValue); + productContext.scope->setProperty(child->id(), childValue); + } + + const bool hasExportItems = mergeExportItems(productContext); + + setScopeForDescendants(productItem, productContext.scope); + + if (!hasExportItems) + return; + + // This "shadow product" exists only to pull in a dependency on the actual product + // and nothing else, thus providing us with the pure environment that we need to + // evaluate the product's exported properties in isolation in the project resolver. + Item * const importer = Item::create(&loaderState.itemPool(), ItemType::Product); + importer->setProperty(QStringLiteral("name"), + VariantValue::create(StringConstants::shadowProductPrefix() + + productContext.name)); + importer->setFile(productItem->file()); + importer->setLocation(productItem->location()); + importer->setScope(projectContext.scope); + importer->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger()); + Item * const dependsItem = Item::create(&loaderState.itemPool(), ItemType::Depends); + dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); + dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); + dependsItem->setFile(importer->file()); + dependsItem->setLocation(importer->location()); + dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger()); + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + VariantValue::create(productContext.multiplexConfigurationId)); + Item::addChild(importer, dependsItem); + prepareProduct(projectContext, importer, &productContext); +} + +void ProductsCollector::Private::handleSubProject( + ProjectContext &projectContext, Item *projectItem, const Set<QString> &referencedFilePaths) +{ + const SetupProjectParameters ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + ItemReader &itemReader = loaderState.itemReader(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); + + Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); + if (!topLevelProject.checkItemCondition(projectItem, evaluator)) + return; + if (propertiesItem) { + propertiesItem->setScope(projectItem); + if (!topLevelProject.checkItemCondition(propertiesItem, evaluator)) + return; + } + + Item *loadedItem = nullptr; + QString subProjectFilePath; + try { + const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); + const QString relativeFilePath + = evaluator.stringValue(projectItem, StringConstants::filePathProperty()); + subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), projectItem->location()); + loadedItem = itemReader.setupItemFromFile(subProjectFilePath, projectItem->location()); + } catch (const ErrorInfo &error) { + if (parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + return; + } + + loadedItem = itemReader.wrapInProjectIfNecessary(loadedItem); + const bool inheritProperties = evaluator.boolValue( + projectItem, StringConstants::inheritPropertiesProperty()); + + if (inheritProperties) + copyProperties(projectItem->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it) + loadedItem->setProperty(it.key(), it.value()); + } + + Item::addChild(projectItem, loadedItem); + projectItem->setScope(projectContext.scope); + handleProject(loadedItem, &projectContext, Set<QString>(referencedFilePaths) << subProjectFilePath); +} + +void ProductsCollector::Private::copyProperties(const Item *sourceProject, Item *targetProject) +{ + if (!sourceProject) + return; + const QList<PropertyDeclaration> builtinProjectProperties + = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties(); + Set<QString> builtinProjectPropertyNames; + for (const PropertyDeclaration &p : builtinProjectProperties) + builtinProjectPropertyNames << p.name(); + + for (auto it = sourceProject->propertyDeclarations().begin(); + it != sourceProject->propertyDeclarations().end(); ++it) { + + // We must not inherit built-in properties such as "name", + // but there are exceptions. + if (it.key() == StringConstants::qbsSearchPathsProperty() + || it.key() == StringConstants::profileProperty() + || it.key() == StringConstants::buildDirectoryProperty() + || it.key() == StringConstants::sourceDirectoryProperty() + || it.key() == StringConstants::minimumQbsVersionProperty()) { + const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); + QBS_ASSERT(v, continue); + if (v->sourceCode() == StringConstants::undefinedValue()) + sourceProject->copyProperty(it.key(), targetProject); + continue; + } + + if (builtinProjectPropertyNames.contains(it.key())) + continue; + + if (targetProject->hasOwnProperty(it.key())) + continue; // Ignore stuff the target project already has. + + targetProject->setPropertyDeclaration(it.key(), it.value()); + sourceProject->copyProperty(it.key(), targetProject); + } +} + +QList<Item *> ProductsCollector::Private::loadReferencedFile( + const QString &relativePath, const CodeLocation &referencingLocation, + const Set<QString> &referencedFilePaths, ProductContext &dummyContext) +{ + QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), + relativePath); + if (FileInfo(absReferencePath).isDir()) { + QString qbsFilePath; + + QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); + while (dit.hasNext()) { + if (!qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " + "qbs file.").arg(absReferencePath), referencingLocation); + } + qbsFilePath = dit.next(); + } + if (qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") + .arg(absReferencePath), referencingLocation); + } + absReferencePath = qbsFilePath; + } + if (referencedFilePaths.contains(absReferencePath)) + throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), + referencingLocation); + Item * const subItem = loaderState.itemReader().setupItemFromFile( + absReferencePath, referencingLocation); + if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { + ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") + .arg(subItem->typeName())); + error.append(Tr::tr("Item is defined here."), subItem->location()); + error.append(Tr::tr("File is referenced here."), referencingLocation); + throw error; + } + subItem->setScope(dummyContext.project->scope); + subItem->setParent(dummyContext.project->item); + QList<Item *> loadedItems; + loadedItems << subItem; + if (subItem->type() == ItemType::Product) { + collectProfilesFromItems(subItem, dummyContext.project->scope, loaderState); + loadedItems << multiplexProductItem(dummyContext, subItem); + } + return loadedItems; +} + +void ProductsCollector::Private::mergeProperty(Item *dst, const QString &name, + const ValuePtr &value) +{ + if (value->type() == Value::ItemValueType) { + const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value); + const Item * const valueItem = itemValue->item(); + Item * const subItem = dst->itemProperty(name, itemValue, loaderState.itemPool())->item(); + for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it) + mergeProperty(subItem, it.key(), it.value()); + return; + } + + // If the property already exists, set up the base value. + if (value->type() == Value::JSSourceValueType) { + const auto jsValue = static_cast<JSSourceValue *>(value.get()); + if (jsValue->isBuiltinDefaultValue()) + return; + const ValuePtr baseValue = dst->property(name); + if (baseValue) { + QBS_CHECK(baseValue->type() == Value::JSSourceValueType); + const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>( + baseValue->clone(loaderState.itemPool())); + jsValue->setBaseValue(jsBaseValue); + std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives(); + jsValue->clearAlternatives(); + for (JSSourceValue::Alternative &a : alternatives) { + a.value->setBaseValue(jsBaseValue); + jsValue->addAlternative(a); + } + } + } + dst->setProperty(name, value); +} + +bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext) +{ + const SetupProjectParameters ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + std::vector<Item *> exportItems; + QList<Item *> children = productContext.item->children(); + + const auto isExport = [](Item *item) { return item->type() == ItemType::Export; }; + std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport); + qbs::Internal::removeIf(children, isExport); + + // Note that we do not return if there are no Export items: The "merged" item becomes the + // "product module", which always needs to exist, regardless of whether the product sources + // actually contain an Export item or not. + if (!exportItems.empty()) + productContext.item->setChildren(children); + + Item *merged = Item::create(&loaderState.itemPool(), ItemType::Export); + const QString &nameKey = StringConstants::nameProperty(); + const ValuePtr nameValue = VariantValue::create(productContext.name); + merged->setProperty(nameKey, nameValue); + Set<FileContextConstPtr> filesWithExportItem; + QVariantMap defaultParameters; + for (Item * const exportItem : exportItems) { + topLevelProject.checkCancelation(); + if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) + throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), + exportItem->location()); + exportItem->setProperty(nameKey, nameValue); + if (!checkExportItemCondition(exportItem, productContext)) + continue; + filesWithExportItem += exportItem->file(); + for (Item * const child : exportItem->children()) { + if (child->type() == ItemType::Parameters) { + adjustParametersScopes(child, child); + defaultParameters = mergeDependencyParameters(defaultParameters, + getJsVariant(evaluator.engine()->context(), + evaluator.scriptValue(child)).toMap()); + } else { + Item::addChild(merged, child); + } + } + const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); + for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { + const PropertyDeclaration &newDecl = it.value(); + const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); + if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { + ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " + "'%1' with different type.").arg(it.key()), exportItem->location()); + handlePropertyError(error, parameters, logger); + } + merged->setPropertyDeclaration(newDecl.name(), newDecl); + } + for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); + it != exportItem->properties().constEnd(); ++it) { + mergeProperty(merged, it.key(), it.value()); + } + } + merged->setFile(exportItems.empty() + ? productContext.item->file() : exportItems.back()->file()); + merged->setLocation(exportItems.empty() + ? productContext.item->location() : exportItems.back()->location()); + Item::addChild(productContext.item, merged); + merged->setupForBuiltinType(parameters.deprecationWarningMode(), logger); + + productContext.mergedExportItem = merged; + productContext.defaultParameters = defaultParameters; + return !exportItems.empty(); + +} + +// TODO: This seems dubious. Can we merge the conditions instead? +bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem, + const ProductContext &product) +{ + class ScopeHandler { + public: + ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem, + ItemPool &itemPool) : m_exportItem(exportItem), m_itemPool(itemPool) + { + if (!*cachedScopeItem) + *cachedScopeItem = Item::create(&m_itemPool, ItemType::Scope); + Item * const scope = *cachedScopeItem; + QBS_CHECK(productContext.item->file()); + scope->setFile(productContext.item->file()); + scope->setScope(productContext.item); + productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); + productContext.scope->copyProperty(StringConstants::productVar(), scope); + QBS_CHECK(!exportItem->scope()); + exportItem->setScope(scope); + } + ~ScopeHandler() { m_exportItem->setScope(nullptr); } + + private: + Item * const m_exportItem; + ItemPool &m_itemPool; + } scopeHandler(exportItem, product, &tempScopeItem, loaderState.itemPool()); + return loaderState.topLevelProject().checkItemCondition(exportItem, loaderState.evaluator()); +} + +void ProductsCollector::Private::initProductProperties(const ProductContext &product) +{ + QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, + product.multiplexConfigurationId); + buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory(), buildDir); + product.item->setProperty(StringConstants::buildDirectoryProperty(), + VariantValue::create(buildDir)); + const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); + product.item->setProperty(StringConstants::sourceDirectoryProperty(), + VariantValue::create(sourceDir)); +} + +void ProductsCollector::Private::checkProjectNamesInOverrides() +{ + for (const QString &projectNameInOverride + : loaderState.topLevelProject().projectNamesUsedInOverrides()) { + if (disabledProjects.contains(projectNameInOverride)) + continue; + if (!any_of(loaderState.topLevelProject().projects(), + [&projectNameInOverride](const ProjectContext *p) { + return p->name == projectNameInOverride; })) { + handlePropertyError(Tr::tr("Unknown project '%1' in property override.") + .arg(projectNameInOverride), + loaderState.parameters(), loaderState.logger()); + } + } +} + +void ProductsCollector::Private::collectProductsByNameAndItem() +{ + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + for (ProjectContext * const project : topLevelProject.projects()) { + for (ProductContext &product : project->products) + topLevelProject.addProduct(product); + } +} + +void ProductsCollector::Private::checkProductNamesInOverrides() +{ + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + for (const QString &productNameInOverride : topLevelProject.productNamesUsedInOverrides()) { + if (!topLevelProject.productWithNameAndConstraint( + productNameInOverride, [&productNameInOverride](const ProductContext &product) { + // In an override string such as "a.b.c:d, we cannot tell whether we have a product + // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take + // care not to emit false positives here. + return product.name == productNameInOverride + || product.name.startsWith(productNameInOverride + StringConstants::dot()); + })) { + handlePropertyError(Tr::tr("Unknown product '%1' in property override.") + .arg(productNameInOverride), + loaderState.parameters(), loaderState.logger()); + } + } +} + +ProductsCollector::Private::TempBaseModuleAttacher::TempBaseModuleAttacher( + ProductsCollector::Private *d, ProductContext &product) + : m_productItem(product.item) +{ + const ValuePtr qbsValue = m_productItem->property(StringConstants::qbsModule()); + + // Cloning is necessary because the original value will get "instantiated" now. + if (qbsValue) + m_origQbsValue = qbsValue->clone(d->loaderState.itemPool()); + + m_tempBaseModule = loadBaseModule(product, m_productItem, d->loaderState); +} + +void ProductsCollector::Private::TempBaseModuleAttacher::drop() +{ + if (!m_tempBaseModule) + return; + + // "Unload" the qbs module again. + if (m_origQbsValue) + m_productItem->setProperty(StringConstants::qbsModule(), m_origQbsValue); + else + m_productItem->removeProperty(StringConstants::qbsModule()); + m_productItem->removeModules(); + m_tempBaseModule = nullptr; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productscollector.h b/src/lib/corelib/loader/productscollector.h new file mode 100644 index 000000000..246361b37 --- /dev/null +++ b/src/lib/corelib/loader/productscollector.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <tools/pimpl.h> + +namespace qbs::Internal { +class Item; +class LoaderState; + +// Traverses the root project item and fills the TopLevelProjectContext with all the +// product and sub-project information, including those coming from referenced files. +class ProductsCollector +{ +public: + ProductsCollector(LoaderState &loaderState); + ~ProductsCollector(); + + void run(Item *rootProject); + +private: + class Private; + Pimpl<Private> d; +}; + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productsresolver.cpp b/src/lib/corelib/loader/productsresolver.cpp new file mode 100644 index 000000000..997a4dc57 --- /dev/null +++ b/src/lib/corelib/loader/productsresolver.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "productsresolver.h" + +#include "itemreader.h" +#include "loaderutils.h" +#include "productresolver.h" + +#include <language/language.h> +#include <language/scriptengine.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/profiling.h> +#include <tools/progressobserver.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <algorithm> +#include <condition_variable> +#include <future> +#include <mutex> +#include <queue> +#include <system_error> +#include <thread> +#include <unordered_map> +#include <vector> + +namespace qbs::Internal { +namespace { +struct ThreadInfo { + ThreadInfo(std::future<void> &&future, LoaderState &loaderState) + : future(std::move(future)), loaderState(loaderState) + {} + std::future<void> future; + LoaderState &loaderState; + bool done = false; +}; + +struct ProductWithLoaderState { + ProductWithLoaderState(ProductContext &product, LoaderState *loaderState) + : product(&product), loaderState(loaderState) {} + ProductContext * const product; + LoaderState *loaderState; +}; + +class ThreadsLocker { +public: + ThreadsLocker(std::launch mode, std::mutex &mutex) { + if (mode == std::launch::async) + lock = std::make_unique<std::unique_lock<std::mutex>>(mutex); + } + std::unique_ptr<std::unique_lock<std::mutex>> lock; +}; +} // namespace + +class ProductsResolver +{ +public: + ProductsResolver(LoaderState &loaderState) : m_loaderState(loaderState) {} + void resolve(); + +private: + void initialize(); + void initializeProductQueue(); + void initializeLoaderStatePool(); + void runScheduler(); + void scheduleNext(); + bool tryToReserveLoaderState(ProductWithLoaderState &product, Deferral deferral); + std::optional<std::pair<ProductContext *, Deferral>> + unblockProductWaitingForLoaderState(LoaderState &loaderState); + void startJob(const ProductWithLoaderState &product, Deferral deferral); + void checkForCancelation(); + void handleFinishedThreads(); + void queueProductForScheduling(const ProductWithLoaderState &product, Deferral deferral); + void waitForSingleDependency(const ProductWithLoaderState &product, ProductContext &dependency); + void waitForBulkDependency(const ProductWithLoaderState &product); + void unblockProductsWaitingForDependency(ProductContext &finishedProduct); + void postProcess(); + void checkForMissedBulkDependencies(const ProductContext &product); + + static int dependsItemCount(const Item *item); + static int dependsItemCount(ProductContext &product); + + LoaderState &m_loaderState; + std::queue<std::pair<ProductWithLoaderState, int>> m_productsToSchedule; + std::vector<ProductContext *> m_finishedProducts; + std::unordered_map<ProductContext *, + std::vector<ProductWithLoaderState>> m_waitingForSingleDependency; + std::vector<ProductWithLoaderState> m_waitingForBulkDependency; + std::unordered_map<LoaderState *, std::queue<std::pair<ProductContext *, Deferral>>> m_waitingForLoaderState; + std::unordered_map<ProductContext *, ThreadInfo> m_runningThreads; + std::mutex m_threadsMutex; + std::condition_variable m_threadsNotifier; + std::vector<std::unique_ptr<ScriptEngine>> m_enginePool; + std::vector<std::unique_ptr<LoaderState>> m_loaderStatePool; + std::vector<LoaderState *> m_availableLoaderStates; + std::mutex m_cancelingMutex; + std::launch m_asyncMode = std::launch::async; + int m_maxJobCount = m_loaderState.parameters().maxJobCount(); + bool m_canceling = false; +}; + +void resolveProducts(LoaderState &loaderState) +{ + ProductsResolver(loaderState).resolve(); +} + +void ProductsResolver::resolve() +{ + initialize(); + try { + runScheduler(); + } catch (const ErrorInfo &e) { + for (auto &thread : m_runningThreads) + thread.second.future.wait(); + throw e; + } + postProcess(); +} + +void ProductsResolver::initialize() +{ + initializeProductQueue(); + initializeLoaderStatePool(); +} + +void ProductsResolver::initializeProductQueue() +{ + TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject(); + std::vector<ProductContext *> sortedProducts; + for (ProjectContext * const projectContext : topLevelProject.projects()) { + for (ProductContext &product : projectContext->products) { + topLevelProject.addProductToHandle(product); + const auto it = std::lower_bound(sortedProducts.begin(), sortedProducts.end(), product, + [&product](ProductContext *p1, const ProductContext &) { + return dependsItemCount(*p1) < dependsItemCount(product); + }); + sortedProducts.insert(it, &product); + } + } + + for (ProductContext * const product : sortedProducts) { + queueProductForScheduling(ProductWithLoaderState(*product, nullptr), Deferral::Allowed); + if (product->shadowProduct) { + topLevelProject.addProductToHandle(*product->shadowProduct); + queueProductForScheduling(ProductWithLoaderState(*product->shadowProduct, nullptr), + Deferral::Allowed); + } + } +} + +void ProductsResolver::initializeLoaderStatePool() +{ + TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject(); + + // Adapt max job count: It makes no sense to have it be higher than the number of products + // or what can actually be run concurrently. In both cases, we would simply waste resources. + const int maxConcurrency = std::thread::hardware_concurrency(); + if (maxConcurrency > 0 && maxConcurrency < m_maxJobCount) + m_maxJobCount = maxConcurrency; + if (m_maxJobCount > topLevelProject.productsToHandleCount()) + m_maxJobCount = topLevelProject.productsToHandleCount(); + + // The number of engines and loader states we need to allocate here is one less than the + // total number of concurrent jobs, as we already have one loader state that we can re-use. + if (m_maxJobCount > 1) + m_enginePool.reserve(m_maxJobCount - 1); + m_loaderStatePool.reserve(m_enginePool.size()); + m_availableLoaderStates.reserve(m_enginePool.size() + 1); + m_availableLoaderStates.push_back(&m_loaderState); + for (std::size_t i = 0; i < m_enginePool.capacity(); ++i) { + ScriptEngine &engine = *m_enginePool.emplace_back( + ScriptEngine::create(m_loaderState.logger(), EvalContext::PropertyEvaluation)); + ItemPool &itemPool = topLevelProject.createItemPool(); + engine.setEnvironment(m_loaderState.parameters().adjustedEnvironment()); + auto loaderState = std::make_unique<LoaderState>( + m_loaderState.parameters(), topLevelProject, itemPool, engine, + m_loaderState.logger()); + m_loaderStatePool.push_back(std::move(loaderState)); + m_availableLoaderStates.push_back(m_loaderStatePool.back().get()); + if (topLevelProject.progressObserver()) + topLevelProject.progressObserver()->addScriptEngine(m_enginePool.back().get()); + } + qCDebug(lcLoaderScheduling) << "using" << m_availableLoaderStates.size() << "loader states"; + if (int(m_availableLoaderStates.size()) == 1) + m_asyncMode = std::launch::deferred; +} + +void ProductsResolver::runScheduler() +{ + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &m_loaderState.topLevelProject().timingData().resolvingProducts + : nullptr); + + ThreadsLocker threadsLock(m_asyncMode, m_threadsMutex); + while (true) { + scheduleNext(); + if (m_runningThreads.empty()) + break; + if (m_asyncMode == std::launch::async) { + qCDebug(lcLoaderScheduling()) << "scheduling paused, waiting for threads to finish"; + m_threadsNotifier.wait(*threadsLock.lock); + } + checkForCancelation(); + handleFinishedThreads(); + } + + QBS_CHECK(m_productsToSchedule.empty()); + QBS_CHECK(m_loaderState.topLevelProject().productsToHandleCount() == 0); + QBS_CHECK(m_runningThreads.empty()); + QBS_CHECK(m_waitingForSingleDependency.empty()); + QBS_CHECK(m_waitingForBulkDependency.empty()); +} + +void ProductsResolver::scheduleNext() +{ + TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject(); + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &topLevelProject.timingData().schedulingProducts : nullptr); + while (m_maxJobCount > int(m_runningThreads.size()) && !m_productsToSchedule.empty()) { + auto [product, toHandleCountOnInsert] = m_productsToSchedule.front(); + m_productsToSchedule.pop(); + + qCDebug(lcLoaderScheduling) << "potentially scheduling product" + << product.product->displayName() + << "unhandled product count on queue insertion" + << toHandleCountOnInsert << "current unhandled product count" + << topLevelProject.productsToHandleCount(); + + // If the number of unfinished products has shrunk since the last time we tried handling + // this product, there has been forward progress and we can allow a deferral. + const Deferral deferral = toHandleCountOnInsert == -1 + || toHandleCountOnInsert > topLevelProject.productsToHandleCount() + ? Deferral::Allowed : Deferral::NotAllowed; + + if (!tryToReserveLoaderState(product, deferral)) + continue; + + startJob(product, deferral); + } + + // There are jobs running, so forward progress is still possible. + if (!m_runningThreads.empty()) + return; + + QBS_CHECK(m_productsToSchedule.empty()); + + // If we end up here, nothing was scheduled in the loop above, which means that either ... + // a) ... we are done or + // b) ... we finally need to schedule our bulk dependencies or + // c) ... we need to schedule products waiting for an unhandled dependency. + // In the latter case, the project has at least one dependency cycle, and the + // DependencyResolver will emit an error. + + // a) + if (m_waitingForBulkDependency.empty() && m_waitingForSingleDependency.empty()) + return; + + // b) + for (const ProductWithLoaderState &product : m_waitingForBulkDependency) + queueProductForScheduling(product, Deferral::NotAllowed); + if (!m_productsToSchedule.empty()) { + m_waitingForBulkDependency.clear(); + scheduleNext(); + return; + } + + // c) + for (const auto &e : m_waitingForSingleDependency) { + for (const ProductWithLoaderState &p : e.second) + queueProductForScheduling(p, Deferral::NotAllowed); + } + QBS_CHECK(!m_productsToSchedule.empty()); + scheduleNext(); +} + +bool ProductsResolver::tryToReserveLoaderState(ProductWithLoaderState &product, Deferral deferral) +{ + QBS_CHECK(!m_availableLoaderStates.empty()); + if (!product.loaderState) { + product.loaderState = m_availableLoaderStates.back(); + m_availableLoaderStates.pop_back(); + return true; + } + if (const auto it = std::find(m_availableLoaderStates.begin(), m_availableLoaderStates.end(), + product.loaderState); it != m_availableLoaderStates.end()) { + m_availableLoaderStates.erase(it); + return true; + } + qCDebug(lcLoaderScheduling) << "loader state" << product.loaderState << " for product" + << product.product->displayName() + << "not available, adding product to wait queue"; + m_waitingForLoaderState[product.loaderState].push({product.product, deferral}); + return false; +} + +std::optional<std::pair<ProductContext *, Deferral>> +ProductsResolver::unblockProductWaitingForLoaderState(LoaderState &loaderState) +{ + auto &waitingForLoaderState = m_waitingForLoaderState[&loaderState]; + if (waitingForLoaderState.empty()) + return {}; + const auto product = waitingForLoaderState.front(); + waitingForLoaderState.pop(); + qCDebug(lcLoaderScheduling) << "loader state" << &loaderState << "now available for product" + << product.first->displayName(); + return product; +} + +void ProductsResolver::startJob(const ProductWithLoaderState &product, Deferral deferral) +{ + QBS_CHECK(product.loaderState); + qCDebug(lcLoaderScheduling) << "scheduling product" << product.product->displayName() + << "with loader state" << product.loaderState + << "and deferral mode" << int(deferral); + try { + const auto it = m_runningThreads.emplace(product.product, ThreadInfo(std::async(m_asyncMode, + [this, product, deferral] { + product.loaderState->itemReader().setExtraSearchPathsStack( + product.product->project->searchPathsStack); + resolveProduct(*product.product, deferral, *product.loaderState); + + // The search paths stack can change during dependency resolution + // (due to module providers); check that we've rolled back all the changes + QBS_CHECK(product.loaderState->itemReader().extraSearchPathsStack() + == product.product->project->searchPathsStack); + + std::lock_guard cancelingLock(m_cancelingMutex); + if (m_canceling) + return; + ThreadsLocker threadsLock(m_asyncMode, m_threadsMutex); + if (const auto it = m_runningThreads.find(product.product); + it != m_runningThreads.end()) { + it->second.done = true; + qCDebug(lcLoaderScheduling) << "thread for product" + << product.product->displayName() + << "finished, waking up scheduler"; + m_threadsNotifier.notify_one(); + } + }), *product.loaderState)); + + // With just one worker thread, the notify/wait overhead would be excessive, so + // we run the task synchronously. + if (m_asyncMode == std::launch::deferred) { + qCDebug(lcLoaderScheduling) << "blocking on product thread immediately"; + it.first->second.future.wait(); + } + } catch (const std::system_error &e) { + if (e.code() != std::errc::resource_unavailable_try_again) + throw e; + qCWarning(lcLoaderScheduling) << "failed to create thread"; + if (m_maxJobCount >= 2) { + m_maxJobCount /= 2; + qCWarning(lcLoaderScheduling) << "throttling down to" << m_maxJobCount << "jobs"; + } + queueProductForScheduling(product, deferral); + m_availableLoaderStates.push_back(product.loaderState); + } +} + +void ProductsResolver::checkForCancelation() +{ + if (m_loaderState.topLevelProject().isCanceled()) { + m_cancelingMutex.lock(); + m_canceling = true; + m_cancelingMutex.unlock(); + for (auto &thread : m_runningThreads) + thread.second.future.wait(); + throw CancelException(); + } +} + +void ProductsResolver::handleFinishedThreads() +{ + TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject(); + AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime() + ? &topLevelProject.timingData().schedulingProducts : nullptr); + + std::vector<std::pair<ProductWithLoaderState, Deferral>> productsToScheduleDirectly; + for (auto it = m_runningThreads.begin(); it != m_runningThreads.end();) { + ThreadInfo &ti = it->second; + if (!ti.done) { + ++it; + continue; + } + ti.future.wait(); + ProductContext &product = *it->first; + LoaderState &loaderState = ti.loaderState; + it = m_runningThreads.erase(it); + + qCDebug(lcLoaderScheduling) << "handling finished thread for product" + << product.displayName() + << "current unhandled product count is" + << topLevelProject.productsToHandleCount(); + + // If there are products waiting for the loader state used in the finished thread, + // we can start a job for one of them right away (but not in the loop, + // because startJob() modifies the thread list we are currently iterating over). + if (const auto productInfo = unblockProductWaitingForLoaderState(loaderState)) { + productsToScheduleDirectly.emplace_back( + ProductWithLoaderState(*productInfo->first, &loaderState), + productInfo->second); + } else { + qCDebug(lcLoaderScheduling) << "making loader state" << &loaderState + << "available again"; + m_availableLoaderStates.push_back(&loaderState); + } + + // If we encountered a dependency to an in-progress product or to a bulk dependency, + // we defer handling this product. + if (product.dependenciesResolvingPending()) { + qCDebug(lcLoaderScheduling) << "dependencies resolving not finished for product" + << product.displayName(); + const auto pending = product.pendingDependency(); + switch (pending.first) { + case ProductDependency::Single: + waitForSingleDependency(ProductWithLoaderState(product, &loaderState), + *pending.second); + break; + case ProductDependency::Bulk: + waitForBulkDependency(ProductWithLoaderState(product, &loaderState)); + break; + case ProductDependency::None: + // This can happen if the dependency has finished in between the check in + // DependencyResolver and the one here. + QBS_CHECK(pending.second); + queueProductForScheduling(ProductWithLoaderState(product, &loaderState), + Deferral::Allowed); + break; + } + topLevelProject.incProductDeferrals(); + } else { + qCDebug(lcLoaderScheduling) << "product" << product.displayName() << "finished"; + topLevelProject.removeProductToHandle(product); + if (!product.name.startsWith(StringConstants::shadowProductPrefix())) + m_finishedProducts.push_back(&product); + topLevelProject.timingData() += product.timingData; + checkForMissedBulkDependencies(product); + topLevelProject.registerBulkDependencies(product); + unblockProductsWaitingForDependency(product); + } + } + + for (const auto &productInfo : productsToScheduleDirectly) + startJob(productInfo.first, productInfo.second); +} + +void ProductsResolver::queueProductForScheduling(const ProductWithLoaderState &product, + Deferral deferral) +{ + qCDebug(lcLoaderScheduling) << "queueing product" << product.product->displayName() + << "with deferral mode" << int(deferral); + m_productsToSchedule.emplace(product, deferral == Deferral::Allowed + ? -1 : m_loaderState.topLevelProject().productsToHandleCount()); +} + +void ProductsResolver::waitForSingleDependency(const ProductWithLoaderState &product, + ProductContext &dependency) +{ + qCDebug(lcLoaderScheduling) << "product" << product.product->displayName() + << "now waiting for single dependency" + << dependency.displayName(); + m_waitingForSingleDependency[&dependency].push_back(product); +} + +void ProductsResolver::waitForBulkDependency(const ProductWithLoaderState &product) +{ + qCDebug(lcLoaderScheduling) << "product" << product.product->displayName() + << "now waiting for bulk dependency"; + m_waitingForBulkDependency.push_back(product); +} + +void ProductsResolver::unblockProductsWaitingForDependency(ProductContext &finishedProduct) +{ + const auto it = m_waitingForSingleDependency.find(&finishedProduct); + if (it == m_waitingForSingleDependency.end()) + return; + + qCDebug(lcLoaderScheduling) << "unblocking all products waiting for now-finished product" << + finishedProduct.displayName(); + for (const ProductWithLoaderState &p : it->second) { + qCDebug(lcLoaderScheduling) << " unblocking product" << p.product->displayName(); + queueProductForScheduling(p, Deferral::Allowed); + } + m_waitingForSingleDependency.erase(it); +} + +void ProductsResolver::postProcess() +{ + for (ProductContext * const product : m_finishedProducts) { + if (product->product) + product->product->project->products.push_back(product->product); + + // This has to be done in post-processing, because we need both product and shadow product + // to be ready, and contrary to what one might assume, there is no proper ordering + // between them regarding dependency resolving. + setupExports(*product, m_loaderState); + } + + for (const auto &engine : m_enginePool) + m_loaderState.topLevelProject().collectDataFromEngine(*engine); + + QBS_CHECK(!m_loaderState.topLevelProject().projects().empty()); + const auto project = std::dynamic_pointer_cast<TopLevelProject>( + m_loaderState.topLevelProject().projects().front()->project); + QBS_CHECK(project); + for (LoaderState * const loaderState : m_availableLoaderStates) + project->warningsEncountered << loaderState->logger().warnings(); +} + +void ProductsResolver::checkForMissedBulkDependencies(const ProductContext &product) +{ + if (!product.product || !product.product->enabled || !product.bulkDependencies.empty()) + return; + for (const FileTag &tag : product.product->fileTags) { + for (const auto &[p, location] + : m_loaderState.topLevelProject().finishedProductsWithBulkDependency(tag)) { + if (!p->product->enabled) + continue; + if (p->name == product.name) + continue; + ErrorInfo e; + e.append(Tr::tr("Cyclic dependencies detected:")); + e.append(Tr::tr("First product is '%1'.") + .arg(product.displayName()), product.item->location()); + e.append(Tr::tr("Second product is '%1'.") + .arg(p->displayName()), p->item->location()); + e.append(Tr::tr("Dependency from %1 to %2 was established via Depends.productTypes.") + .arg(p->displayName(), product.displayName()), location); + if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict) + throw e; + m_loaderState.logger().printWarning(e); + m_loaderState.logger().printWarning( + ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") + .arg(product.displayName()), product.item->location())); + m_loaderState.logger().printWarning( + ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") + .arg(p->displayName()), p->item->location())); + product.product->enabled = false; + p->product->enabled = false; + } + } +} + +int ProductsResolver::dependsItemCount(const Item *item) +{ + int count = 0; + for (const Item * const child : item->children()) { + if (child->type() == ItemType::Depends) + ++count; + } + return count; +} + +int ProductsResolver::dependsItemCount(ProductContext &product) +{ + if (product.dependsItemCount == -1) + product.dependsItemCount = dependsItemCount(product.item); + return product.dependsItemCount; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productsresolver.h b/src/lib/corelib/loader/productsresolver.h new file mode 100644 index 000000000..b8b4b7516 --- /dev/null +++ b/src/lib/corelib/loader/productsresolver.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +namespace qbs::Internal { +class LoaderState; + +void resolveProducts(LoaderState &loaderState); + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/projectresolver.cpp b/src/lib/corelib/loader/projectresolver.cpp new file mode 100644 index 000000000..be99d547d --- /dev/null +++ b/src/lib/corelib/loader/projectresolver.cpp @@ -0,0 +1,562 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "projectresolver.h" + +#include "itemreader.h" +#include "loaderutils.h" +#include "productscollector.h" +#include "productsresolver.h" + +#include <jsextensions/jsextensions.h> +#include <jsextensions/moduleproperties.h> +#include <language/artifactproperties.h> +#include <language/builtindeclarations.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/filetags.h> +#include <language/item.h> +#include <language/itempool.h> +#include <language/language.h> +#include <language/propertymapinternal.h> +#include <language/resolvedfilecontext.h> +#include <language/scriptengine.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/error.h> +#include <tools/fileinfo.h> +#include <tools/joblimits.h> +#include <tools/jsliterals.h> +#include <tools/profile.h> +#include <tools/profiling.h> +#include <tools/progressobserver.h> +#include <tools/scripttools.h> +#include <tools/qbsassert.h> +#include <tools/qttools.h> +#include <tools/set.h> +#include <tools/settings.h> +#include <tools/setupprojectparameters.h> +#include <tools/stlutils.h> +#include <tools/stringconstants.h> + +#include <quickjs.h> + +#include <QtCore/qdir.h> +#include <QtCore/qregularexpression.h> + +#include <algorithm> +#include <memory> +#include <queue> +#include <utility> +#include <vector> + +namespace qbs { +namespace Internal { + +static SetupProjectParameters finalizedProjectParameters(const SetupProjectParameters &in, + Logger &logger) +{ + SetupProjectParameters params = in; + if (params.topLevelProfile().isEmpty()) { + Settings settings(params.settingsDirectory()); + QString profileName = settings.defaultProfile(); + if (profileName.isEmpty()) { + logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. " + "Using default property values."); + profileName = Profile::fallbackName(); + } + params.setTopLevelProfile(profileName); + params.expandBuildConfiguration(); + } + params.finalizeProjectFilePath(); + + QBS_CHECK(QFileInfo(params.projectFilePath()).isAbsolute()); + QBS_CHECK(FileInfo::isAbsolute(params.buildRoot())); + + return params; +} + +class ProjectResolver::Private +{ +public: + Private(const SetupProjectParameters ¶meters, ScriptEngine *engine, Logger &&logger) + : setupParams(finalizedProjectParameters(parameters, logger)), engine(engine), + logger(std::move(logger)) {} + + TopLevelProjectPtr resolveTopLevelProject(); + void resolveProject(ProjectContext *projectContext); + void resolveProjectFully(ProjectContext *projectContext); + void resolveSubProject(Item *item, ProjectContext *projectContext); + void checkOverriddenValues(); + void collectNameFromOverride(const QString &overrideString); + void loadTopLevelProjectItem(); + void buildProjectTree(); + + void printProfilingInfo(); + + const SetupProjectParameters setupParams; + ScriptEngine * const engine; + mutable Logger logger; + ItemPool itemPool; + TopLevelProjectContext topLevelProject; + LoaderState state{setupParams, topLevelProject, itemPool, *engine, logger}; + Item *rootProjectItem = nullptr; +}; + +ProjectResolver::ProjectResolver(const SetupProjectParameters ¶meters, ScriptEngine *engine, + Logger logger) + : d(makePimpl<Private>(parameters, engine, std::move(logger))) +{ + d->logger.storeWarnings(); +} + +ProjectResolver::~ProjectResolver() = default; + +void ProjectResolver::setProgressObserver(ProgressObserver *observer) +{ + d->state.topLevelProject().setProgressObserver(observer); +} + +void ProjectResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) +{ + d->state.topLevelProject().setOldProjectProbes(oldProbes); +} + +void ProjectResolver::setOldProductProbes( + const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes) +{ + d->state.topLevelProject().setOldProductProbes(oldProbes); +} + +void ProjectResolver::setLastResolveTime(const FileTime &time) +{ + d->state.topLevelProject().setLastResolveTime(time); +} + +void ProjectResolver::setStoredProfiles(const QVariantMap &profiles) +{ + d->state.topLevelProject().setProfileConfigs(profiles); +} + +void ProjectResolver::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo) +{ + d->state.topLevelProject().setModuleProvidersCache(providerInfo.providers); +} + +static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) +{ + const std::vector<ResolvedProductPtr> allProducts = project->allProducts(); + for (size_t i = 0; i < allProducts.size(); ++i) { + const ResolvedProductConstPtr product1 = allProducts.at(i); + const QString productName = product1->uniqueName(); + for (size_t j = i + 1; j < allProducts.size(); ++j) { + const ResolvedProductConstPtr product2 = allProducts.at(j); + if (product2->uniqueName() == productName) { + ErrorInfo error; + error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); + 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() +{ + qCDebug(lcProjectResolver) << "resolving" << d->setupParams.projectFilePath(); + + d->engine->setEnvironment(d->setupParams.adjustedEnvironment()); + d->engine->checkAndClearException({}); + d->engine->clearImportsCache(); + d->engine->clearRequestedProperties(); + d->engine->enableProfiling(d->setupParams.logElapsedTime()); + d->logger.clearWarnings(); + EvalContextSwitcher evalContextSwitcher(d->engine, EvalContext::PropertyEvaluation); + + // 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 (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) { + observer->initialize(Tr::tr("Resolving project for configuration %1") + .arg(TopLevelProject::deriveId(d->setupParams.finalBuildConfigurationTree())), 1); + observer->addScriptEngine(d->engine); + } + + const FileTime resolveTime = FileTime::currentTime(); + TopLevelProjectPtr tlp; + try { + d->checkOverriddenValues(); + d->loadTopLevelProjectItem(); + d->buildProjectTree(); + tlp = d->resolveTopLevelProject(); + } catch (const CancelException &) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.") + .arg(TopLevelProject::deriveId( + d->setupParams.finalBuildConfigurationTree()))); + } + tlp->lastStartResolveTime = resolveTime; + tlp->lastEndResolveTime = FileTime::currentTime(); + + // E.g. if the top-level project is disabled. + if (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) { + observer->setFinished(); + d->printProfilingInfo(); + } + return tlp; +} + +static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) +{ + Set<QString> subProjectNames; + Set<ResolvedProjectPtr> projectsInNeedOfNameChange; + for (const ResolvedProjectPtr &p : std::as_const(parentProject->subProjects)) { + if (!subProjectNames.insert(p->name).second) + projectsInNeedOfNameChange << p; + makeSubProjectNamesUniqe(p); + } + while (!projectsInNeedOfNameChange.empty()) { + auto it = projectsInNeedOfNameChange.begin(); + while (it != projectsInNeedOfNameChange.end()) { + const ResolvedProjectPtr p = *it; + p->name += QLatin1Char('_'); + if (subProjectNames.insert(p->name).second) { + it = projectsInNeedOfNameChange.erase(it); + } else { + ++it; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject() +{ + if (ProgressObserver * const observer = state.topLevelProject().progressObserver()) + observer->setMaximum(state.topLevelProject().productCount()); + TopLevelProjectPtr project = TopLevelProject::create(); + project->buildDirectory = TopLevelProject::deriveBuildDirectory( + setupParams.buildRoot(), + TopLevelProject::deriveId(setupParams.finalBuildConfigurationTree())); + if (!state.topLevelProject().projects().empty()) { + ProjectContext * const projectContext = state.topLevelProject().projects().front(); + QBS_CHECK(projectContext->item == rootProjectItem); + projectContext->project = project; + resolveProject(projectContext); + } + resolveProducts(state); + ErrorInfo accumulatedErrors; + { + const auto queuedErrors = state.topLevelProject().queuedErrors(); + for (const ErrorInfo &e : queuedErrors.get()) + appendError(accumulatedErrors, e); + } + if (accumulatedErrors.hasError()) + throw accumulatedErrors; + + project->buildSystemFiles.unite(state.topLevelProject().buildSystemFiles()); + project->profileConfigs = state.topLevelProject().profileConfigs(); + project->codeLinks = state.topLevelProject().codeLinks(); + const QVariantMap &profiles = state.topLevelProject().localProfiles(); + for (auto it = profiles.begin(); it != profiles.end(); ++it) + project->profileConfigs.remove(it.key()); + project->probes = state.topLevelProject().projectLevelProbes(); + project->moduleProviderInfo.providers = state.topLevelProject().moduleProvidersCache(); + project->setBuildConfiguration(setupParams.finalBuildConfigurationTree()); + project->overriddenValues = setupParams.overriddenValues(); + state.topLevelProject().collectDataFromEngine(*engine); + makeSubProjectNamesUniqe(project); + checkForDuplicateProductNames(project); + project->warningsEncountered << logger.warnings(); + + return project; +} + +void ProjectResolver::Private::resolveProject(ProjectContext *projectContext) +{ + state.topLevelProject().checkCancelation(); + + if (projectContext->parent) { + projectContext->project = ResolvedProject::create(); + projectContext->parent->project->subProjects.push_back(projectContext->project); + projectContext->project->parentProject = projectContext->parent->project; + projectContext->project->enabled = projectContext->parent->project->enabled; + } + projectContext->project->location = projectContext->item->location(); + + try { + resolveProjectFully(projectContext); + } catch (const ErrorInfo &error) { + if (!projectContext->project->enabled) { + qCDebug(lcProjectResolver) << "error resolving project" + << projectContext->project->location << error.toString(); + return; + } + if (setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + } +} + +void ProjectResolver::Private::resolveProjectFully(ProjectContext *projectContext) +{ + Item * const item = projectContext->item; + projectContext->project->enabled = projectContext->project->enabled + && state.evaluator().boolValue(item, StringConstants::conditionProperty()); + projectContext->project->name = state.evaluator().stringValue( + item, StringConstants::nameProperty()); + if (projectContext->project->name.isEmpty()) + projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item? + QVariantMap projectProperties; + if (!projectContext->project->enabled) { + projectProperties.insert(StringConstants::profileProperty(), + state.evaluator().stringValue( + item, StringConstants::profileProperty())); + projectContext->project->setProjectProperties(projectProperties); + return; + } + + projectContext->dummyModule = ResolvedModule::create(); + + for (Item::PropertyDeclarationMap::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); + const ScopedJsValue sv(engine->context(), state.evaluator().value(item, it.key())); + projectProperties.insert(it.key(), getJsVariant(engine->context(), sv)); + } + projectContext->project->setProjectProperties(projectProperties); + + for (Item * const child : item->children()) { + state.topLevelProject().checkCancelation(); + try { + switch (child->type()) { + case ItemType::SubProject: + resolveSubProject(child, projectContext); + break; + case ItemType::FileTagger: + resolveFileTagger(state, child, projectContext, nullptr); + break; + case ItemType::JobLimit: + resolveJobLimit(state, child, projectContext, nullptr, nullptr); + break; + case ItemType::Rule: + resolveRule(state, child, projectContext, nullptr, nullptr); + break; + default: + break; + } + } catch (const ErrorInfo &e) { + state.topLevelProject().addQueuedError(e); + } + } + + for (ProjectContext * const childContext : projectContext->children) + resolveProject(childContext); +} + +void ProjectResolver::Private::resolveSubProject(Item *item, ProjectContext *projectContext) +{ + // If we added a Project child item in ProductsCollector, then the sub-project will be + // resolved normally via resolveProject(). Otherwise, it was not loaded, for instance + // because its Properties condition was false, and special handling applies. + if (item->child(ItemType::Project)) + return; + + ResolvedProjectPtr project = ResolvedProject::create(); + project->enabled = false; + project->parentProject = projectContext->project; + projectContext->project->subProjects << project; + if (Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject)) { + project->name = state.evaluator().stringValue(propertiesItem, + StringConstants::nameProperty()); + } +} + +void ProjectResolver::Private::checkOverriddenValues() +{ + static const auto matchesPrefix = [](const QString &key) { + static const QStringList prefixes({StringConstants::projectPrefix(), + QStringLiteral("projects"), + QStringLiteral("products"), QStringLiteral("modules"), + StringConstants::moduleProviders(), + StringConstants::qbsModule()}); + return any_of(prefixes, [&key](const QString &prefix) { + return key.startsWith(prefix + QLatin1Char('.')); }); + }; + const QVariantMap &overriddenValues = state.parameters().overriddenValues(); + for (auto it = overriddenValues.begin(); it != overriddenValues.end(); ++it) { + if (matchesPrefix(it.key())) { + collectNameFromOverride(it.key()); + continue; + } + + ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(it.key())); + e.append(Tr::tr("Please use one of the following:")); + e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value")); + e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value")); + e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value")); + e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>." + "<property-name>:value")); + e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>." + "<property-name>:value")); + handlePropertyError(e, state.parameters(), state.logger()); + } +} + +void ProjectResolver::Private::collectNameFromOverride(const QString &overrideString) +{ + const auto extract = [&overrideString](const QString &prefix) { + if (!overrideString.startsWith(prefix)) + return QString(); + const int startPos = prefix.length(); + const int endPos = overrideString.lastIndexOf(StringConstants::dot()); + if (endPos == -1) + return QString(); + return overrideString.mid(startPos, endPos - startPos); + }; + const QString &projectName = extract(StringConstants::projectsOverridePrefix()); + if (!projectName.isEmpty()) { + state.topLevelProject().addProjectNameUsedInOverrides(projectName); + return; + } + const QString &productName = extract(StringConstants::productsOverridePrefix()); + if (!productName.isEmpty()) { + state.topLevelProject().addProductNameUsedInOverrides(productName.left( + productName.indexOf(StringConstants::dot()))); + return; + } +} + +void ProjectResolver::Private::loadTopLevelProjectItem() +{ + const QStringList topLevelSearchPaths + = state.parameters().finalBuildConfigurationTree() + .value(StringConstants::projectPrefix()).toMap() + .value(StringConstants::qbsSearchPathsProperty()).toStringList(); + SearchPathsManager searchPathsManager(state.itemReader(), topLevelSearchPaths); + Item * const root = state.itemReader().setupItemFromFile( + state.parameters().projectFilePath(), {}); + if (!root) + return; + + switch (root->type()) { + case ItemType::Product: + rootProjectItem = state.itemReader().wrapInProjectIfNecessary(root); + break; + case ItemType::Project: + rootProjectItem = root; + break; + default: + throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it" + " is of type '%1'.").arg(root->typeName()), root->location()); + } +} + +void ProjectResolver::Private::buildProjectTree() +{ + state.topLevelProject().setBuildDirectory(TopLevelProject::deriveBuildDirectory( + state.parameters().buildRoot(), + TopLevelProject::deriveId(state.parameters().finalBuildConfigurationTree()))); + rootProjectItem->setProperty(StringConstants::sourceDirectoryProperty(), + VariantValue::create(QFileInfo(rootProjectItem->file()->filePath()) + .absolutePath())); + rootProjectItem->setProperty(StringConstants::buildDirectoryProperty(), + VariantValue::create(state.topLevelProject().buildDirectory())); + rootProjectItem->setProperty(StringConstants::profileProperty(), + VariantValue::create(state.parameters().topLevelProfile())); + ProductsCollector(state).run(rootProjectItem); + + AccumulatingTimer timer(state.parameters().logElapsedTime() + ? &state.topLevelProject().timingData().propertyChecking : nullptr); + checkPropertyDeclarations(rootProjectItem, state); +} + +void ProjectResolver::Private::printProfilingInfo() +{ + if (!setupParams.logElapsedTime()) + return; + const auto print = [this](int indent, const QString &pattern, qint64 time) { + logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ') + << pattern.arg(elapsedTimeString(time)); + }; + print(2, Tr::tr("Project file loading and parsing took %1."), state.itemReader().elapsedTime()); + print(2, Tr::tr("Preparing products took %1."), + state.topLevelProject().timingData().preparingProducts); + print(2, Tr::tr("Setting up Groups took %1."), + state.topLevelProject().timingData().groupsSetup); + print(2, Tr::tr("Scheduling products took %1."), + state.topLevelProject().timingData().schedulingProducts); + print(2, Tr::tr("Resolving products took %1."), + state.topLevelProject().timingData().resolvingProducts); + print(4, Tr::tr("Property evaluation took %1."), + state.topLevelProject().timingData().propertyEvaluation); + print(4, Tr::tr("Resolving groups (without module property evaluation) took %1."), + state.topLevelProject().timingData().groupsResolving); + print(4, Tr::tr("Setting up product dependencies took %1."), + state.topLevelProject().timingData().dependenciesResolving); + print(6, Tr::tr("Running module providers took %1."), + state.topLevelProject().timingData().moduleProviders); + print(6, Tr::tr("Instantiating modules took %1."), + state.topLevelProject().timingData().moduleInstantiation); + print(6, Tr::tr("Merging module property values took %1."), + state.topLevelProject().timingData().propertyMerging); + logger.qbsLog(LoggerInfo, true) << QByteArray(4, ' ') << "There were " + << state.topLevelProject().productDeferrals() + << " product deferrals with a total of " + << state.topLevelProject().productCount() << " products."; + print(2, Tr::tr("Running Probes took %1."), state.topLevelProject().timingData().probes); + state.logger().qbsLog(LoggerInfo, true) + << " " + << Tr::tr("%1 probes encountered, %2 configure scripts executed, " + "%3 re-used from current run, %4 re-used from earlier run.") + .arg(state.topLevelProject().probesEncounteredCount()) + .arg(state.topLevelProject().probesRunCount()) + .arg(state.topLevelProject().reusedCurrentProbesCount()) + .arg(state.topLevelProject().reusedOldProbesCount()); + print(2, Tr::tr("Property checking took %1."), + state.topLevelProject().timingData().propertyChecking); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/loader/projectresolver.h index d172a74ed..22ad82518 100644 --- a/src/lib/corelib/language/loader.h +++ b/src/lib/corelib/loader/projectresolver.h @@ -36,53 +36,49 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#ifndef QBS_LOADER_H -#define QBS_LOADER_H -#include "forward_decls.h" -#include "moduleproviderinfo.h" +#ifndef PROJECTRESOLVER_H +#define PROJECTRESOLVER_H + +#include <language/forward_decls.h> #include <logging/logger.h> -#include <tools/filetime.h> +#include <tools/pimpl.h> +#include <tools/qbs_export.h> + +#include <QHash> +#include <QVariant> -#include <QtCore/qstringlist.h> +#include <vector> namespace qbs { -class Settings; class SetupProjectParameters; namespace Internal { +class FileTime; class Logger; class ProgressObserver; class ScriptEngine; +class StoredModuleProviderInfo; -class QBS_AUTOTEST_EXPORT Loader +class QBS_AUTOTEST_EXPORT ProjectResolver { public: - Loader(ScriptEngine *engine, Logger logger); + ProjectResolver(const SetupProjectParameters ¶meters, ScriptEngine *engine, Logger logger); + ~ProjectResolver(); void setProgressObserver(ProgressObserver *observer); - void setSearchPaths(const QStringList &searchPaths); void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes); void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); - void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } + void setLastResolveTime(const FileTime &time); void setStoredProfiles(const QVariantMap &profiles); - void setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo); - TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); - - static void setupProjectFilePath(SetupProjectParameters ¶meters); + void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo); + TopLevelProjectPtr resolve(); private: - Logger m_logger; - ProgressObserver *m_progressObserver; - ScriptEngine * const m_engine; - QStringList m_searchPaths; - std::vector<ProbeConstPtr> m_oldProjectProbes; - QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes; - ModuleProviderInfoList m_storedModuleProviderInfo; - QVariantMap m_storedProfiles; - FileTime m_lastResolveTime; + class Private; + Pimpl<Private> d; }; } // namespace Internal } // namespace qbs -#endif // QBS_LOADER_H +#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/logging/categories.cpp b/src/lib/corelib/logging/categories.cpp index 0f844f5b4..5738dc21d 100644 --- a/src/lib/corelib/logging/categories.cpp +++ b/src/lib/corelib/logging/categories.cpp @@ -50,6 +50,7 @@ Q_LOGGING_CATEGORY(lcModuleLoader, "qbs.moduleloader", QtCriticalMsg) Q_LOGGING_CATEGORY(lcPluginManager, "qbs.pluginmanager", QtCriticalMsg) Q_LOGGING_CATEGORY(lcProjectResolver, "qbs.projectresolver", QtCriticalMsg) Q_LOGGING_CATEGORY(lcUpToDateCheck, "qbs.uptodate", QtCriticalMsg) +Q_LOGGING_CATEGORY(lcLoaderScheduling, "qbs.loader.scheduling", QtCriticalMsg) } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/logging/categories.h b/src/lib/corelib/logging/categories.h index 40c69845e..c8873c30c 100644 --- a/src/lib/corelib/logging/categories.h +++ b/src/lib/corelib/logging/categories.h @@ -53,6 +53,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcModuleLoader) Q_DECLARE_LOGGING_CATEGORY(lcPluginManager) Q_DECLARE_LOGGING_CATEGORY(lcProjectResolver) Q_DECLARE_LOGGING_CATEGORY(lcUpToDateCheck) +Q_DECLARE_LOGGING_CATEGORY(lcLoaderScheduling) } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/logging/ilogsink.cpp b/src/lib/corelib/logging/ilogsink.cpp index 4eb930cbf..20cf52bcd 100644 --- a/src/lib/corelib/logging/ilogsink.cpp +++ b/src/lib/corelib/logging/ilogsink.cpp @@ -82,14 +82,11 @@ public: std::mutex mutex; }; -ILogSink::ILogSink() : d(new ILogSinkPrivate) +ILogSink::ILogSink() : d(std::make_unique<ILogSinkPrivate>()) { } -ILogSink::~ILogSink() -{ - delete d; -} +ILogSink::~ILogSink() = default; void ILogSink::setLogLevel(LoggerLevel level) { diff --git a/src/lib/corelib/logging/ilogsink.h b/src/lib/corelib/logging/ilogsink.h index 4b25aa7b8..0a055abc1 100644 --- a/src/lib/corelib/logging/ilogsink.h +++ b/src/lib/corelib/logging/ilogsink.h @@ -43,6 +43,8 @@ #include <QtCore/qstring.h> +#include <memory> + namespace qbs { class ErrorInfo; @@ -83,7 +85,7 @@ private: const QString &tag) = 0; class ILogSinkPrivate; - ILogSinkPrivate * const d; + const std::unique_ptr<ILogSinkPrivate> d; }; } // namespace qbs diff --git a/src/lib/corelib/logging/logger.cpp b/src/lib/corelib/logging/logger.cpp index b962afa92..65d0cc27f 100644 --- a/src/lib/corelib/logging/logger.cpp +++ b/src/lib/corelib/logging/logger.cpp @@ -79,6 +79,9 @@ LogWriter::~LogWriter() const LogWriter &LogWriter::operator=(const LogWriter &other) { + if (this == &other) // Self assignment guard. + return *this; + m_logSink = other.m_logSink; m_level = other.m_level; m_message = other.m_message; @@ -163,7 +166,7 @@ LogWriter operator<<(LogWriter w, const Internal::Set<QString> &strSet) LogWriter operator<<(LogWriter w, const QVariant &variant) { QString str = QLatin1String(variant.typeName()) + QLatin1Char('('); - if (variant.type() == QVariant::List) { + if (variant.userType() == QMetaType::QVariantList) { bool firstLoop = true; const auto list = variant.toList(); for (const QVariant &item : list) { @@ -183,17 +186,20 @@ LogWriter operator<<(LogWriter w, const QVariant &variant) LogWriter operator<<(LogWriter w, int n) { - return w << QString::number(n); + w.write(QString::number(n)); + return w; } LogWriter operator<<(LogWriter w, qint64 n) { - return w << QString::number(n); + w.write(QString::number(n)); + return w; } LogWriter operator<<(LogWriter w, bool b) { - return w << QString::fromLatin1(b ? "true" : "false"); + w.write(QString::fromLatin1(b ? "true" : "false")); + return w; } LogWriter operator<<(LogWriter w, const MessageTag &tag) @@ -225,7 +231,7 @@ void Logger::printWarning(const ErrorInfo &warning) LogWriter Logger::qbsLog(LoggerLevel level, bool force) const { - return LogWriter(m_logSink, level, force); + return {m_logSink, level, force}; } } // namespace Internal diff --git a/src/lib/corelib/logging/logger.h b/src/lib/corelib/logging/logger.h index 419a371c2..389dc7e05 100644 --- a/src/lib/corelib/logging/logger.h +++ b/src/lib/corelib/logging/logger.h @@ -90,7 +90,7 @@ private: class QBS_EXPORT MessageTag { public: - explicit MessageTag(const QString &tag) : m_tag(tag) {} + explicit MessageTag(QString tag) : m_tag(std::move(tag)) {} const QString &tag() const { return m_tag; } diff --git a/src/lib/corelib/logging/logging.pri b/src/lib/corelib/logging/logging.pri deleted file mode 100644 index 3a4379a9a..000000000 --- a/src/lib/corelib/logging/logging.pri +++ /dev/null @@ -1,18 +0,0 @@ -include(../../../install_prefix.pri) - -HEADERS += \ - $$PWD/categories.h \ - $$PWD/logger.h \ - $$PWD/translator.h \ - $$PWD/ilogsink.h - -SOURCES += \ - $$PWD/categories.cpp \ - $$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/parser/parser.pri b/src/lib/corelib/parser/parser.pri deleted file mode 100644 index e6a8a5345..000000000 --- a/src/lib/corelib/parser/parser.pri +++ /dev/null @@ -1,21 +0,0 @@ -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 index e72e79f87..42ef8ea29 100644 --- a/src/lib/corelib/parser/qmlerror.cpp +++ b/src/lib/corelib/parser/qmlerror.cpp @@ -39,6 +39,8 @@ #include "qmlerror.h" +#include <tools/qttools.h> + #include <QtCore/qdebug.h> #include <QtCore/qfile.h> #include <QtCore/qstringlist.h> @@ -93,16 +95,12 @@ QmlErrorPrivate::QmlErrorPrivate() /*! Creates an empty error object. */ -QmlError::QmlError() -: d(nullptr) -{ -} +QmlError::QmlError() = default; /*! Creates a copy of \a other. */ QmlError::QmlError(const QmlError &other) -: d(nullptr) { *this = other; } @@ -112,11 +110,13 @@ QmlError::QmlError(const QmlError &other) */ QmlError &QmlError::operator=(const QmlError &other) { + if (this == &other) // Self assignment guard. + return *this; + if (!other.d) { - delete d; d = nullptr; } else { - if (!d) d = new QmlErrorPrivate; + if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>(); d->url = other.d->url; d->description = other.d->description; d->line = other.d->line; @@ -128,10 +128,7 @@ QmlError &QmlError::operator=(const QmlError &other) /*! \internal */ -QmlError::~QmlError() -{ - delete d; d = nullptr; -} +QmlError::~QmlError() = default; /*! Returns true if this error is valid, otherwise false. @@ -147,7 +144,7 @@ bool QmlError::isValid() const QUrl QmlError::url() const { if (d) return d->url; - else return {}; + return {}; } /*! @@ -155,7 +152,7 @@ QUrl QmlError::url() const */ void QmlError::setUrl(const QUrl &url) { - if (!d) d = new QmlErrorPrivate; + if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>(); d->url = url; } @@ -165,7 +162,7 @@ void QmlError::setUrl(const QUrl &url) QString QmlError::description() const { if (d) return d->description; - else return {}; + return {}; } /*! @@ -173,7 +170,7 @@ QString QmlError::description() const */ void QmlError::setDescription(const QString &description) { - if (!d) d = new QmlErrorPrivate; + if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>(); d->description = description; } @@ -183,7 +180,7 @@ void QmlError::setDescription(const QString &description) int QmlError::line() const { if (d) return d->line; - else return -1; + return -1; } /*! @@ -191,7 +188,7 @@ int QmlError::line() const */ void QmlError::setLine(int line) { - if (!d) d = new QmlErrorPrivate; + if (!d) qbs::Internal::makePimpl<QmlErrorPrivate>(); d->line = line; } @@ -201,7 +198,7 @@ void QmlError::setLine(int line) int QmlError::column() const { if (d) return d->column; - else return -1; + return -1; } /*! @@ -209,7 +206,7 @@ int QmlError::column() const */ void QmlError::setColumn(int column) { - if (!d) d = new QmlErrorPrivate; + if (!d) qbs::Internal::makePimpl<QmlErrorPrivate>(); d->column = column; } @@ -259,9 +256,7 @@ QDebug operator<<(QDebug debug, const QmlError &error) if (f.open(QIODevice::ReadOnly)) { QByteArray data = f.readAll(); QTextStream stream(data, QIODevice::ReadOnly); -#ifndef QT_NO_TEXTCODEC - stream.setCodec("UTF-8"); -#endif + qbs::setupDefaultCodec(stream); const QString code = stream.readAll(); const QStringList lines = code.split(QLatin1Char('\n')); @@ -271,7 +266,7 @@ QDebug operator<<(QDebug debug, const QmlError &error) if (error.column() > 0) { int column = std::max(0, error.column() - 1); - column = std::min(column, line.length()); + column = std::min<int>(column, line.length()); QByteArray ind; ind.reserve(column); diff --git a/src/lib/corelib/parser/qmlerror.h b/src/lib/corelib/parser/qmlerror.h index cfac506bb..4f7bf8a07 100644 --- a/src/lib/corelib/parser/qmlerror.h +++ b/src/lib/corelib/parser/qmlerror.h @@ -40,7 +40,7 @@ #ifndef QQMLERROR_H #define QQMLERROR_H - +#include <tools/pimpl.h> #include <QtCore/qurl.h> #include <QtCore/qstring.h> @@ -73,7 +73,7 @@ public: QString toString() const; private: - QmlErrorPrivate *d; + qbs::Internal::Pimpl<QmlErrorPrivate> d; }; } // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljs.g b/src/lib/corelib/parser/qmljs.g index 956c65809..b9d533ada 100644 --- a/src/lib/corelib/parser/qmljs.g +++ b/src/lib/corelib/parser/qmljs.g @@ -189,6 +189,23 @@ ** ****************************************************************************/ + +// +// 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 @@ -196,17 +213,16 @@ #include "qmljsgrammar_p.h" #include "qmljsast_p.h" #include "qmljsengine_p.h" +#include <tools/qbs_export.h> #include <QtCore/qlist.h> #include <QtCore/qstring.h> -QT_QML_BEGIN_NAMESPACE - -namespace QmlJS { +namespace QbsQmlJS { class Engine; -class QML_PARSER_EXPORT Parser: protected $table +class QBS_AUTOTEST_EXPORT Parser: protected $table { public: union Value { @@ -270,7 +286,7 @@ public: AST::Statement *statement() const { if (! program) - return 0; + return nullptr; return program->statementCast(); } @@ -278,7 +294,7 @@ public: AST::ExpressionNode *expression() const { if (! program) - return 0; + return nullptr; return program->expressionCast(); } @@ -286,7 +302,7 @@ public: AST::UiObjectMember *uiObjectMember() const { if (! program) - return 0; + return nullptr; return program->uiObjectMemberCast(); } @@ -348,13 +364,13 @@ protected: enum { TOKEN_BUFFER_SIZE = 3 }; struct SavedToken { - int token; - double dval; + int token = 0; + double dval = 0.0; AST::SourceLocation loc; QStringRef spell; }; - double yylval; + double yylval = 0.0; QStringRef yytokenspell; AST::SourceLocation yylloc; AST::SourceLocation yyprevlloc; @@ -366,7 +382,7 @@ protected: QList<DiagnosticMessage> diagnostic_messages; }; -} // end of namespace QmlJS +} // end of namespace QbsQmlJS :/ @@ -458,7 +474,7 @@ AST::UiQualifiedId *Parser::reparseAsQualifiedId(AST::ExpressionNode *expr) return currentId->finish(); } - return 0; + return nullptr; } bool Parser::parse(int startToken) @@ -3003,9 +3019,5 @@ QT_QML_END_NAMESPACE ./ /: -QT_QML_END_NAMESPACE - - - #endif // QMLJSPARSER_P_H :/ diff --git a/src/lib/corelib/parser/qmljsast_p.h b/src/lib/corelib/parser/qmljsast_p.h index dcee233da..e2ba68508 100644 --- a/src/lib/corelib/parser/qmljsast_p.h +++ b/src/lib/corelib/parser/qmljsast_p.h @@ -57,6 +57,10 @@ #include <QtCore/qstring.h> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QtCore5Compat/qstringref.h> +#endif + namespace QbsQmlJS { #define QMLJS_DECLARE_AST_NODE(name) \ diff --git a/src/lib/corelib/parser/qmljsastvisitor_p.h b/src/lib/corelib/parser/qmljsastvisitor_p.h index bec174c65..4b7911aa4 100644 --- a/src/lib/corelib/parser/qmljsastvisitor_p.h +++ b/src/lib/corelib/parser/qmljsastvisitor_p.h @@ -58,7 +58,7 @@ namespace QbsQmlJS { namespace AST { -class QBS_AUTOTEST_EXPORT Visitor +class QML_PARSER_EXPORT Visitor { public: Visitor(); diff --git a/src/lib/corelib/parser/qmljsengine_p.cpp b/src/lib/corelib/parser/qmljsengine_p.cpp index 92ac6452a..67da09825 100644 --- a/src/lib/corelib/parser/qmljsengine_p.cpp +++ b/src/lib/corelib/parser/qmljsengine_p.cpp @@ -50,9 +50,9 @@ static int toDigit(char c) { if ((c >= '0') && (c <= '9')) return c - '0'; - else if ((c >= 'a') && (c <= 'z')) + if ((c >= 'a') && (c <= 'z')) return 10 + c - 'a'; - else if ((c >= 'A') && (c <= 'Z')) + if ((c >= 'A') && (c <= 'Z')) return 10 + c - 'A'; return -1; } @@ -149,7 +149,7 @@ QStringRef Engine::newStringRef(const QString &text) { const int pos = _extraCode.length(); _extraCode += text; - return _extraCode.midRef(pos, text.length()); + return QStringRef(&_extraCode).mid(pos, text.length()); } QStringRef Engine::newStringRef(const QChar *chars, int size) diff --git a/src/lib/corelib/parser/qmljsengine_p.h b/src/lib/corelib/parser/qmljsengine_p.h index 19e76a699..9c603ee5c 100644 --- a/src/lib/corelib/parser/qmljsengine_p.h +++ b/src/lib/corelib/parser/qmljsengine_p.h @@ -58,7 +58,12 @@ #include <QtCore/qstring.h> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QtCore5Compat/qstringref.h> +#endif + #include <set> +#include <utility> namespace QbsQmlJS { @@ -74,8 +79,8 @@ public: DiagnosticMessage() : kind(Error) {} - DiagnosticMessage(Kind kind, const AST::SourceLocation &loc, const QString &message) - : kind(kind), loc(loc), message(message) {} + DiagnosticMessage(Kind kind, const AST::SourceLocation &loc, QString message) + : kind(kind), loc(loc), message(std::move(message)) {} bool isWarning() const { return kind == Warning; } @@ -88,7 +93,7 @@ public: QString message; }; -class QBS_AUTOTEST_EXPORT Engine +class QML_PARSER_EXPORT Engine { Lexer *_lexer{nullptr}; Directives *_directives{nullptr}; @@ -114,7 +119,10 @@ public: MemoryPool *pool(); - inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); } + inline QStringRef midRef(int position, int size) + { + return QStringRef(&_code).mid(position, size); + } QStringRef newStringRef(const QString &s); QStringRef newStringRef(const QChar *chars, int size); diff --git a/src/lib/corelib/parser/qmljsglobal_p.h b/src/lib/corelib/parser/qmljsglobal_p.h index c3d198ea5..02e0b38e4 100644 --- a/src/lib/corelib/parser/qmljsglobal_p.h +++ b/src/lib/corelib/parser/qmljsglobal_p.h @@ -41,31 +41,14 @@ #include <QtCore/qglobal.h> -// Force QML_PARSER_EXPORT to be always empty. -#ifndef QT_CREATOR -# define QT_CREATOR -#endif -#ifdef QML_BUILD_STATIC_LIB -# undef QML_BUILD_STATIC_LIB -#endif -#define QML_BUILD_STATIC_LIB 1 - -#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 +#ifdef QBS_STATIC_LIB +#define QML_PARSER_EXPORT +#else +#ifdef QBS_LIBRARY +#define QML_PARSER_EXPORT Q_DECL_EXPORT +#else +#define QML_PARSER_EXPORT Q_DECL_IMPORT +#endif // QBS_LIBRARY +#endif // QBS_STATIC_LIB #endif // QMLJSGLOBAL_P_H diff --git a/src/lib/corelib/parser/qmljsgrammar.cpp b/src/lib/corelib/parser/qmljsgrammar.cpp index 07a193f6f..75b4a488a 100644 --- a/src/lib/corelib/parser/qmljsgrammar.cpp +++ b/src/lib/corelib/parser/qmljsgrammar.cpp @@ -43,969 +43,975 @@ 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}; + "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}; + 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}; + 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}; + 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}; + 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, + 360, 1257, 2491, 2491, 2393, 982, 78, 84, 145, -102, + 81, 64, 68, 210, -102, 306, 54, -102, -102, 620, + 59, 128, 268, 244, -102, -102, -102, 521, 205, 1257, + -102, -102, -102, 442, -102, 2197, 1819, 1257, 1257, 1257, + -102, 682, 1257, -102, -102, -102, 1257, 1257, -102, -102, + -102, -102, -102, 1257, -102, 1257, 1257, -102, 1257, 1257, + 95, 188, -102, -102, 1257, 1257, 1257, -102, -102, -102, + 191, 1257, 313, 1257, 1257, 1257, 1257, 521, 1257, 1257, + 1257, 1257, 1257, 1257, 196, 1257, 1257, 1257, 151, 146, + 116, 167, 174, 182, 186, 189, 521, 429, 521, 1257, + 45, 1257, 41, 2099, 1257, 1257, -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, + 125, 1257, -102, -102, 58, 46, -102, 1257, -102, -102, + 1257, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, 1257, 17, 1257, 1257, 75, 71, + 1257, -102, 2099, 1257, 1257, -102, 154, -102, 51, -102, + -102, 55, -102, 288, 72, 57, -102, 297, -102, 62, + 2491, -102, -102, -102, -102, -102, 232, -102, -102, 60, + -102, -102, -102, -102, -102, -102, 2491, -102, -102, 458, + -102, 470, 89, 2393, 49, 442, 69, 47, 2687, 65, + 1257, -102, 61, 43, 1257, 40, -102, 39, 82, -102, + -102, 442, -102, -102, -102, -102, -102, -102, 102, -102, + -102, -102, -102, 98, -102, -102, -102, -102, -102, -102, + 76, 63, 1257, 92, 93, -102, -102, 1348, -102, 88, + 66, 48, -102, 304, 80, 77, 560, 187, 86, 400, + 271, 442, 1257, 302, 1257, 1257, 1257, 1257, 407, 1257, + 1257, 1257, 1257, 1257, 253, 257, 272, 281, 278, 362, + 358, 337, 1257, 48, 1257, 73, 1257, -102, 620, 1257, + -102, 1257, 56, 44, 1257, 42, 2393, -102, 1257, 117, + 2393, -102, 1257, 38, 1257, 1257, 187, 74, 1257, -102, + 70, 118, 22, -102, -102, 1257, -102, 442, 1257, -102, + 67, 1257, 79, 2393, -102, 1257, 112, 2393, -102, -25, + 442, -48, -20, 2491, -41, -102, 2393, -102, 1257, 113, + 2393, 2, 2393, -102, 3, 11, -34, -102, -102, 2393, + -30, 504, 19, 450, 114, 1257, 2393, 20, -16, 414, + -1, -31, 772, -3, -4, -102, 1443, -102, 87, -35, + -5, 1257, 0, 30, 1257, 33, 1257, 14, 15, 1257, + -102, 2295, 1, -102, -102, -102, -102, -102, -102, 1257, + -102, -102, -102, -102, 226, -102, 1257, -6, -102, 2393, + -102, 96, -102, -102, 2393, -102, 1257, 101, 36, -102, + 53, -102, 29, 91, 1257, -102, 24, 23, -102, -24, + -102, 2393, -102, 100, 2393, -102, 250, -102, -102, 90, + 2393, 16, -102, 10, 12, -102, 442, -12, 4, -102, + -102, -102, -102, 1257, 111, 2393, -102, 1257, 105, 2393, + -102, 6, -102, 214, -102, -102, 1257, -102, -102, 354, + -102, -102, -102, 85, 1537, -102, -102, 1631, -102, -102, + 1725, -102, -102, -102, -102, -102, -102, 135, -102, -102, + -102, -102, -102, -102, -102, -102, -102, 2491, -102, -102, + -102, 159, -10, 890, 202, -11, 18, -102, -102, 222, + -102, 199, -102, -102, -102, 359, 211, -102, 1910, -102, + -102, -102, -102, -102, -102, -102, -102, -102, 183, -13, + 349, 175, -2, 344, 282, -102, -17, -102, 800, 124, + -102, -18, 890, -102, -102, 1074, -102, -102, -102, 1166, + -102, -102, 208, -102, 1910, -102, 369, -18, -102, -102, + 204, 341, 9, 1910, -102, 203, -102, 177, -102, -8, + -51, 309, 206, 442, -102, 83, -102, -102, -102, 2001, + 800, 369, 2589, 1819, 34, -102, 436, 25, 493, 108, + 1257, 2393, 26, 7, 409, 28, 8, 598, 32, 31, + -102, 1443, -102, 87, 5, 21, 1257, 52, 37, 1257, + 50, 1257, 35, 13, 134, -102, -102, 27, -102, -102, + 710, -102, 266, -38, 890, -102, -102, 115, 300, -102, + 176, -102, 122, -102, -102, 442, -102, -102, 126, -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}; + -107, -3, -98, 4, 7, 280, 3, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -35, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 78, + -107, -107, -107, 20, -107, -107, -22, 15, 86, 72, + -107, 193, 175, -107, -107, -107, 174, 170, -107, -107, + -107, -107, -107, 118, -107, 144, 145, -107, 127, 156, + -107, -107, -107, -107, 162, 165, 166, -107, -107, -107, + -107, 149, -107, 137, 136, 135, 134, -107, 181, 180, + 179, 173, 53, 117, -107, 81, 95, 90, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 103, + -107, 101, -107, 194, 92, 58, -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, 32, -107, -107, -107, -107, -107, 16, -107, -107, + 49, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, 70, -107, 109, 34, -107, -107, + 45, -107, 216, 60, 100, -107, -107, -107, -107, -107, + -107, -107, -107, 44, -107, -107, -107, 47, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, 48, -107, -107, 35, + -107, 39, -107, 56, -107, 79, -107, -107, 83, -107, + 76, -107, -107, -107, 89, 69, -107, -107, -107, -107, + -107, -2, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, 19, -107, -107, -107, -107, 105, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -13, 195, -107, 218, 212, 215, 228, -107, 114, + 87, 121, 124, 107, -107, -107, -107, -107, -107, -107, + -107, -107, 198, -107, 221, -107, 236, -107, -107, 237, + -107, 128, -107, -107, 184, -107, 36, -107, 24, -107, + 102, -107, 192, -107, 234, 238, -107, -107, 224, -107, + -107, -107, -107, -107, -107, 191, -107, 93, 99, -107, + -107, 108, -107, 88, -107, 84, -107, 91, -107, -107, + 141, -107, -107, -23, -107, -107, 59, -107, 57, -107, + 55, -107, 54, -107, -107, -107, -107, -107, -107, 46, + -107, 31, -107, 30, -107, 169, 37, -107, -107, 68, + -107, -107, 85, -107, -107, -107, 73, -107, -107, -107, + -107, 66, -107, 52, 98, -107, 104, -107, -107, 43, + -107, 40, -107, -107, -107, -107, -107, -107, -107, 42, + -107, -107, -107, -107, -107, -107, 106, -107, -107, 62, + -107, -107, -107, -107, 63, -107, 65, -107, -107, -107, + -107, -107, -30, -107, 67, -107, -19, -107, -107, -107, + -107, 94, -107, -107, 97, -107, -107, -107, -107, -107, + 75, -50, -107, -107, 25, -107, 28, -107, 29, -107, + -107, -107, -107, 41, -107, 64, -107, 38, -107, 51, + -107, -107, -107, -107, -107, -107, 33, -107, -107, 122, + -107, -107, -107, -107, 61, -107, -107, 163, -107, -107, + 50, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, 82, -107, -107, + -107, -107, -107, -12, -107, -107, -107, -107, -107, -107, + -107, -15, -107, -107, -107, -5, -107, -107, 297, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -9, -26, -107, -11, -107, -107, -107, -107, 183, -107, + -107, -107, 288, -107, -107, 283, -107, -107, -107, 390, + -107, -107, -107, -107, 321, -107, -107, -8, -107, -107, + 11, 18, -107, 305, -107, -107, -107, 1, -107, -107, + -107, 14, -4, 0, -107, -107, -107, -107, -107, 337, + 217, -107, 22, 279, 9, -107, 8, -107, 6, -107, + 153, 17, -107, -107, 5, -107, -107, 96, -107, -107, + -107, 13, -107, -107, -107, -107, 23, -107, 10, 77, + -107, 71, -107, -107, -107, -107, -107, 12, -107, -107, + 21, -107, -107, 26, 148, -107, -107, -107, 27, -107, + -107, -107, -107, -107, -107, -10, -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, + 572, 416, -124, -103, -121, 348, 346, -126, 399, 575, + 389, 340, 342, 343, 345, 545, 563, 538, 352, 391, + 257, 401, 533, 466, 531, 403, -132, -113, -124, 424, + 431, 430, -132, -113, 424, 346, 444, 440, -103, -121, + -129, 416, 71, 257, 348, 440, 446, 565, 448, 457, + 453, 457, 620, 335, 408, 560, 511, -129, 424, -126, + 545, 164, 141, 440, 141, 99, 147, 545, 414, 420, + 440, 453, 409, 440, 272, 453, 346, 164, 170, 416, + 187, 318, 71, 348, 644, 257, 292, 325, 296, 272, + 0, 183, 0, 477, -102, 190, 443, 0, 0, 252, + 141, 427, 306, 0, 0, 304, 149, 434, 141, 141, + 444, 179, 99, 141, 292, 236, 0, 189, 457, 141, + 141, 141, 0, 0, 101, 141, 327, 294, 331, 0, + 172, 0, 542, 141, 0, 0, 314, 62, 301, 333, + 315, 478, 542, 141, 577, 576, 428, 101, 63, 173, + 249, 248, 254, 251, 256, 255, 58, 413, 412, 247, + 246, 418, 141, 242, 241, 459, 0, 59, 620, 249, + 248, 455, 337, 350, 172, 249, 248, 310, 166, 328, + 543, 142, 167, 634, 633, 58, 58, 640, 639, 312, + 615, 58, 85, 173, 86, 488, 59, 59, 172, 85, + 64, 86, 59, 64, 512, 87, 512, 85, 0, 86, + 177, 85, 87, 86, 85, 554, 86, 173, 518, 406, + 87, 85, 469, 86, 87, 103, 0, 87, 512, 579, + 85, 529, 86, 512, 87, 512, 0, 637, 636, 567, + 141, 172, 0, 87, 104, 65, 105, 0, 65, 0, + 514, 66, 514, 0, 66, 545, 64, 0, 141, 0, + 173, 513, 406, 513, 568, 566, 0, 635, 0, 555, + 553, 0, 519, 517, 514, 470, 468, 514, 85, 514, + 86, 514, 85, 172, 86, 513, 0, 0, 513, 536, + 513, 87, 513, 235, 234, 87, 85, 85, 86, 86, + 0, 65, 173, 85, 174, 86, 85, 66, 86, 87, + 87, 438, 437, 630, 580, 0, 87, 34, 0, 87, + 274, 275, 274, 275, 73, 74, 34, 631, 629, 34, + 0, 73, 74, 0, 0, 0, 0, 0, 34, 0, + 0, 0, 0, 537, 535, 0, 0, 276, 277, 276, + 277, 75, 76, 0, 48, 50, 49, 628, 75, 76, + 279, 280, 0, 48, 50, 49, 48, 50, 49, 281, + 34, 0, 282, 34, 283, 48, 50, 49, 34, 0, + 45, 279, 280, 34, 172, 279, 280, 0, 34, 45, + 281, 0, 45, 282, 281, 283, 0, 282, 0, 283, + 0, 45, -90, 173, 0, 174, 0, 48, 50, 49, + 48, 50, 49, 0, 0, 48, 50, 49, 0, 0, + 48, 50, 49, 279, 280, 48, 50, 49, 0, 0, + 279, 280, 281, 45, 0, 282, 45, 283, 34, 281, + 0, 45, 282, 34, 283, -345, 45, 0, 0, 0, + -345, 45, 78, 79, 6, 5, 4, 1, 3, 2, + 80, 81, 0, 0, 82, 34, 83, 0, 0, 0, + 0, 34, 0, 0, 0, 48, 50, 49, 0, 34, + 48, 50, 49, 0, 0, 0, 0, 34, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 240, 239, 34, + 0, 45, 48, 50, 49, 0, 45, 0, 48, 50, + 49, 245, 244, 0, 0, 0, 48, 50, 49, 240, + 239, 0, 34, 0, 48, 50, 49, 0, 45, 0, + 0, 245, 244, 34, 45, 0, 48, 50, 49, 0, + 0, 0, 45, 0, 78, 79, 0, 0, 0, 0, + 45, 0, 80, 81, 245, 244, 82, 0, 83, 48, + 50, 49, 45, 151, 0, 240, 239, 0, 0, 0, + 48, 50, 49, 152, 0, 0, 0, 153, 0, 0, + 0, 0, 0, 0, 0, 45, 154, 0, 155, 0, + 0, 308, 0, 0, 0, 0, 45, 0, 0, 156, + 0, 157, 62, 0, 0, 0, 0, 0, 0, 158, + 30, 31, 159, 63, 0, 0, 0, 0, 160, 0, + 33, 0, 0, 151, 161, 0, 0, 34, 0, 0, + 0, 35, 36, 152, 37, 0, 0, 153, 0, 0, + 162, 41, 0, 0, 0, 44, 154, 0, 155, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 156, + 0, 157, 62, 51, 48, 50, 49, 0, 52, 158, + 0, 0, 159, 63, 0, 0, 0, 0, 160, 43, + 54, 32, 0, 0, 161, 40, 0, 0, 0, 0, + 45, 0, 0, 0, 30, 31, 0, 0, 0, 0, + 162, 0, 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, 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, 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, + 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, 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, 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, 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, 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, + -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, 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, 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, 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, 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, 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, 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, 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}; + 16, 528, 320, 532, 534, 638, 552, 497, 498, 499, + 516, 559, 619, 461, 515, 574, 302, 472, 487, 250, + 182, 243, 253, 238, 358, 573, 436, 309, 616, 578, + 571, 618, 439, 562, 627, 182, 150, 641, 307, 250, + 561, 458, 632, 447, 454, 243, 238, 450, 402, 445, + 238, 449, 237, 460, 243, 148, 353, 351, 250, 186, + 349, 347, 188, 176, 410, 415, 456, 425, 417, 436, + 433, 143, 467, 169, 439, 429, 302, 358, 344, 302, + 392, 400, 390, 182, 171, 358, 237, 336, 163, 482, + 334, 439, 436, 338, 339, 140, 358, 146, 404, 358, + 485, 60, 302, 0, 311, 0, 94, 0, 320, 404, + 0, 302, 0, 0, 0, 0, 0, 0, 60, 60, + 60, 452, 490, 0, 0, 60, 60, 451, 180, 60, + 405, 302, 165, 88, 60, 60, 489, 461, 60, 146, + 285, 405, 90, 60, 178, 146, 60, 89, 451, 60, + 407, 60, 60, 266, 452, 60, 186, 60, 270, 102, + 288, 501, 60, 100, 642, 60, 60, 284, 494, 60, + 95, 330, 60, 168, 286, 60, 432, 287, 61, 435, + 332, 329, 60, 60, 60, 60, 0, 302, 98, 97, + 96, 77, 60, 60, 451, 452, 501, 60, 480, 540, + 303, 60, 479, 180, 60, 72, 404, 70, 471, 106, + 60, 0, 67, 60, 60, 68, 69, 60, 60, 180, + 493, 60, 60, 60, 492, 491, 93, 60, 60, 60, + 501, 106, 92, 91, 84, 108, 0, 617, 405, 298, + 60, 341, 0, 60, 270, 270, 60, 0, 270, 0, + 0, 270, 273, 0, 0, 313, 305, 108, 175, 293, + 60, 326, 0, 60, 0, 270, 60, 289, 270, 60, + 290, 270, 298, 278, 270, 0, 60, 270, 0, 0, + 295, 270, 60, 291, 298, 298, 298, 270, 548, 270, + 270, 270, 556, 501, 319, 540, 501, 614, 0, 316, + 509, 501, 0, 509, 544, 0, 297, 300, 317, 0, + 0, 0, 500, 510, 0, 500, 510, 0, 472, 520, + 521, 522, 523, 527, 524, 525, 564, 520, 521, 522, + 523, 527, 524, 525, 556, 0, 0, 0, 0, 0, + 0, 557, 558, 520, 521, 522, 523, 527, 524, 525, + 581, 0, 0, 0, 0, 0, 0, 584, 585, 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, 551, 0, 0, 0, 0, + 0, 0, 0, 501, 0, 0, 0, 0, 0, 0, + 509, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 500, 510, 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, 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, + 8, 36, 7, 7, 7, 36, 7, 7, 7, 60, + 7, 36, 60, 33, 55, 33, 7, 34, 16, 8, + 36, 55, 24, 17, 37, 55, 7, 7, 7, 5, + 7, 55, 7, 7, 5, 7, 20, 33, 7, 7, + 7, 36, 1, 36, 36, 33, 36, 29, 60, 36, + 36, 36, 90, 31, 60, 66, 66, 7, 5, 7, + 33, 2, 8, 33, 8, 48, 8, 33, 7, 33, + 33, 36, 7, 33, 1, 36, 7, 2, 7, 36, + 8, 7, 1, 36, 0, 36, 48, 17, 8, 1, + -1, 36, -1, 8, 7, 33, 6, -1, -1, 36, + 8, 10, 60, -1, -1, 61, 60, 7, 8, 8, + 20, 60, 48, 8, 48, 55, -1, 60, 36, 8, + 8, 8, -1, -1, 79, 8, 8, 79, 61, -1, + 15, -1, 8, 8, -1, -1, 50, 42, 61, 60, + 54, 56, 8, 8, 61, 62, 55, 79, 53, 34, + 61, 62, 60, 77, 61, 62, 40, 61, 62, 61, + 62, 60, 8, 61, 62, 60, -1, 51, 90, 61, + 62, 60, 60, 60, 15, 61, 62, 60, 50, 61, + 56, 56, 54, 61, 62, 40, 40, 61, 62, 2, + 56, 40, 25, 34, 27, 60, 51, 51, 15, 25, + 12, 27, 51, 12, 29, 38, 29, 25, -1, 27, + 56, 25, 38, 27, 25, 7, 27, 34, 7, 36, + 38, 25, 8, 27, 38, 15, -1, 38, 29, 7, + 25, 29, 27, 29, 38, 29, -1, 61, 62, 36, + 8, 15, -1, 38, 34, 57, 36, -1, 57, -1, + 75, 63, 75, -1, 63, 33, 12, -1, 8, -1, + 34, 86, 36, 86, 61, 62, -1, 91, -1, 61, + 62, -1, 61, 62, 75, 61, 62, 75, 25, 75, + 27, 75, 25, 15, 27, 86, -1, -1, 86, 7, + 86, 38, 86, 61, 62, 38, 25, 25, 27, 27, + -1, 57, 34, 25, 36, 27, 25, 63, 27, 38, + 38, 61, 62, 47, 92, -1, 38, 29, -1, 38, + 18, 19, 18, 19, 18, 19, 29, 61, 62, 29, + -1, 18, 19, -1, -1, -1, -1, -1, 29, -1, + -1, -1, -1, 61, 62, -1, -1, 45, 46, 45, + 46, 45, 46, -1, 66, 67, 68, 91, 45, 46, + 23, 24, -1, 66, 67, 68, 66, 67, 68, 32, + 29, -1, 35, 29, 37, 66, 67, 68, 29, -1, + 92, 23, 24, 29, 15, 23, 24, -1, 29, 92, + 32, -1, 92, 35, 32, 37, -1, 35, -1, 37, + -1, 92, 33, 34, -1, 36, -1, 66, 67, 68, + 66, 67, 68, -1, -1, 66, 67, 68, -1, -1, + 66, 67, 68, 23, 24, 66, 67, 68, -1, -1, + 23, 24, 32, 92, -1, 35, 92, 37, 29, 32, + -1, 92, 35, 29, 37, 36, 92, -1, -1, -1, + 36, 92, 23, 24, 94, 95, 96, 97, 98, 99, + 31, 32, -1, -1, 35, 29, 37, -1, -1, -1, + -1, 29, -1, -1, -1, 66, 67, 68, -1, 29, + 66, 67, 68, -1, -1, -1, -1, 29, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 61, 62, 29, + -1, 92, 66, 67, 68, -1, 92, -1, 66, 67, + 68, 61, 62, -1, -1, -1, 66, 67, 68, 61, + 62, -1, 29, -1, 66, 67, 68, -1, 92, -1, + -1, 61, 62, 29, 92, -1, 66, 67, 68, -1, + -1, -1, 92, -1, 23, 24, -1, -1, -1, -1, + 92, -1, 31, 32, 61, 62, 35, -1, 37, 66, + 67, 68, 92, 3, -1, 61, 62, -1, -1, -1, + 66, 67, 68, 13, -1, -1, -1, 17, -1, -1, + -1, -1, -1, -1, -1, 92, 26, -1, 28, -1, + -1, 31, -1, -1, -1, -1, 92, -1, -1, 39, + -1, 41, 42, -1, -1, -1, -1, -1, -1, 49, + 12, 13, 52, 53, -1, -1, -1, -1, 58, -1, + 22, -1, -1, 3, 64, -1, -1, 29, -1, -1, + -1, 33, 34, 13, 36, -1, -1, 17, -1, -1, + 80, 43, -1, -1, -1, 47, 26, -1, 28, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 39, + -1, 41, 42, 65, 66, 67, 68, -1, 70, 49, + -1, -1, 52, 53, -1, -1, -1, -1, 58, 81, + 82, 83, -1, -1, 64, 87, -1, -1, -1, -1, + 92, -1, -1, -1, 12, 13, -1, -1, -1, -1, + 80, -1, -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, -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, 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, -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, 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, + 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, 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}; + 3, 13, 15, 29, 15, 15, 15, 105, 4, 2, + 15, 19, 9, 15, 29, 15, 3, 39, 3, 2, + 15, 15, 3, 15, 2, 29, 3, 3, 19, 15, + 29, 19, 22, 15, 13, 15, 71, 11, 2, 2, + 29, 3, 15, 15, 3, 15, 15, 22, 2, 99, + 15, 22, 4, 2, 15, 39, 2, 2, 2, 15, + 3, 2, 15, 3, 2, 2, 2, 97, 3, 3, + 3, 39, 39, 39, 22, 94, 3, 2, 101, 3, + 40, 39, 39, 15, 39, 2, 4, 3, 39, 39, + 2, 22, 3, 2, 15, 3, 2, 39, 13, 2, + 39, 48, 3, -1, 2, -1, 53, -1, 15, 13, + -1, 3, -1, -1, -1, -1, -1, -1, 48, 48, + 48, 50, 50, -1, -1, 48, 48, 50, 50, 48, + 45, 3, 62, 52, 48, 48, 50, 15, 48, 39, + 53, 45, 52, 48, 44, 39, 48, 52, 50, 48, + 44, 48, 48, 48, 50, 48, 15, 48, 53, 58, + 53, 13, 48, 60, 16, 48, 48, 53, 50, 48, + 53, 72, 48, 64, 53, 48, 82, 53, 51, 82, + 72, 88, 48, 48, 48, 48, -1, 3, 54, 54, + 54, 54, 48, 48, 50, 50, 13, 48, 35, 16, + 72, 48, 39, 50, 48, 56, 13, 51, 86, 15, + 48, -1, 50, 48, 48, 50, 50, 48, 48, 50, + 50, 48, 48, 48, 50, 50, 53, 48, 48, 48, + 13, 15, 53, 53, 53, 41, -1, 20, 45, 48, + 48, 100, -1, 48, 53, 53, 48, -1, 53, -1, + -1, 53, 57, -1, -1, 63, 72, 41, 42, 61, + 48, 70, -1, 48, -1, 53, 48, 55, 53, 48, + 55, 53, 48, 55, 53, -1, 48, 53, -1, -1, + 59, 53, 48, 55, 48, 48, 48, 53, 5, 53, + 53, 53, 13, 13, 70, 16, 13, 18, -1, 65, + 20, 13, -1, 20, 16, -1, 70, 70, 70, -1, + -1, -1, 32, 33, -1, 32, 33, -1, 39, 22, + 23, 24, 25, 26, 27, 28, 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, 5, -1, -1, -1, -1, + -1, -1, -1, 13, -1, -1, -1, -1, -1, -1, + 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 32, 33, -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, -1, -1, -1, -1, -1, -1 +}; -} // namespace QbsQmlJS +} // end of namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsgrammar_p.h b/src/lib/corelib/parser/qmljsgrammar_p.h index a3525408b..aa41562de 100644 --- a/src/lib/corelib/parser/qmljsgrammar_p.h +++ b/src/lib/corelib/parser/qmljsgrammar_p.h @@ -52,156 +52,155 @@ #ifndef QMLJSGRAMMAR_P_H #define QMLJSGRAMMAR_P_H -#include "qmljsglobal_p.h" #include <QtCore/qglobal.h> namespace QbsQmlJS { -class QML_PARSER_EXPORT QmlJSGrammar +class 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]; - } + 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 = 2789, + GOTO_CHECK_OFFSET = 2789 + }; + + 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]; + } }; diff --git a/src/lib/corelib/parser/qmljslexer.cpp b/src/lib/corelib/parser/qmljslexer.cpp index 9a09ec348..e148652ad 100644 --- a/src/lib/corelib/parser/qmljslexer.cpp +++ b/src/lib/corelib/parser/qmljslexer.cpp @@ -46,7 +46,11 @@ #include <QtCore/qdebug.h> QT_BEGIN_NAMESPACE -Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +Q_CORE_EXPORT double qstrntod(const char *s00, int len, char const **se, bool *ok); +#else +Q_CORE_EXPORT double qstrntod(const char *s00, qsizetype len, char const **se, bool *ok); +#endif QT_END_NAMESPACE namespace QbsQmlJS { @@ -65,10 +69,9 @@ static unsigned char convertHex(ushort c) { if (c >= '0' && c <= '9') return (c - '0'); - else if (c >= 'a' && c <= 'f') + if (c >= 'a' && c <= 'f') return (c - 'a' + 10); - else - return (c - 'A' + 10); + return (c - 'A' + 10); } static QChar convertHex(QChar c1, QChar c2) @@ -78,7 +81,7 @@ static QChar convertHex(QChar c1, QChar c2) static QChar convertUnicode(QChar c1, QChar c2, QChar c3, QChar c4) { - return {uchar((convertHex(c3.unicode()) << 4) + convertHex(c4.unicode())), + return QChar{uchar((convertHex(c3.unicode()) << 4) + convertHex(c4.unicode())), uchar((convertHex(c1.unicode()) << 4) + convertHex(c2.unicode()))}; } @@ -178,7 +181,7 @@ int Lexer::lex() _tokenSpell = QStringRef(); _tokenKind = scanToken(); - _tokenLength = _codePtr - _tokenStartPtr - 1; + _tokenLength = int(_codePtr - _tokenStartPtr - 1); _delimited = false; _restrictedKeyword = false; @@ -237,10 +240,10 @@ int Lexer::lex() bool Lexer::isUnicodeEscapeSequence(const QChar *chars) { - if (isHexDigit(chars[0]) && isHexDigit(chars[1]) && isHexDigit(chars[2]) && isHexDigit(chars[3])) - return true; - - return false; + return isHexDigit(chars[0]) + && isHexDigit(chars[1]) + && isHexDigit(chars[2]) + && isHexDigit(chars[3]); } QChar Lexer::decodeUnicodeEscapeCharacter(bool *ok) @@ -293,10 +296,9 @@ again: _tokenLine = _currentLineNumber; _tokenStartPtr = _codePtr - 1; // ### TODO: insert it before the optional \r sequence. return T_SEMICOLON; - } else { - _terminator = true; - syncProhibitAutomaticSemicolon(); } + _terminator = true; + syncProhibitAutomaticSemicolon(); } scanChar(); @@ -348,7 +350,8 @@ again: return T_GT_GT_GT_EQ; } return T_GT_GT_GT; - } else if (_char == QLatin1Char('=')) { + } + if (_char == QLatin1Char('=')) { scanChar(); return T_GT_GT_EQ; } @@ -397,7 +400,8 @@ again: scanChar(); if (_engine) { - _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 4, + _engine->addComment(tokenOffset() + 2, + int(_codePtr - _tokenStartPtr - 1 - 4), tokenStartLine(), tokenStartColumn() + 2); } @@ -412,7 +416,7 @@ again: scanChar(); } if (_engine) { - _engine->addComment(tokenOffset() + 2, _codePtr - _tokenStartPtr - 1 - 2, + _engine->addComment(tokenOffset() + 2, int(_codePtr - _tokenStartPtr - 1 - 2), tokenStartLine(), tokenStartColumn() + 2); } goto again; @@ -452,15 +456,13 @@ again: } } - chars.append('\0'); - const char *begin = chars.constData(); const char *end = nullptr; bool ok = false; - _tokenValue = qstrtod(begin, &end, &ok); + _tokenValue = qstrntod(begin, chars.size(), &end, &ok); - if (end - begin != chars.size() - 1) { + if (end - begin != chars.size()) { _errorCode = IllegalExponentIndicator; _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); return T_ERROR; @@ -553,8 +555,10 @@ again: while (!_char.isNull()) { if (_char == QLatin1Char('\n') || _char == QLatin1Char('\\')) { break; - } else if (_char == quote) { - _tokenSpell = _engine->midRef(startCode - _code.unicode() - 1, _codePtr - startCode); + } + if (_char == quote) { + _tokenSpell = _engine->midRef( + int(startCode - _code.unicode() - 1), int(_codePtr - startCode)); scanChar(); return T_STRING_LITERAL; @@ -706,7 +710,7 @@ again: if (! identifierWithEscapeChars) { identifierWithEscapeChars = true; _tokenText.resize(0); - _tokenText.insert(0, _tokenStartPtr, _codePtr - _tokenStartPtr - 1); + _tokenText.insert(0, _tokenStartPtr, int(_codePtr - _tokenStartPtr - 1)); _validTokenText = true; } @@ -719,7 +723,7 @@ again: return T_ERROR; } } else { - _tokenLength = _codePtr - _tokenStartPtr - 1; + _tokenLength = int(_codePtr - _tokenStartPtr - 1); int kind = T_IDENTIFIER; @@ -727,10 +731,12 @@ again: kind = classify(_tokenStartPtr, _tokenLength, _qmlMode); if (_engine) { - if (kind == T_IDENTIFIER && identifierWithEscapeChars) + if (kind == T_IDENTIFIER && identifierWithEscapeChars) { _tokenSpell = _engine->newStringRef(_tokenText); - else - _tokenSpell = _engine->midRef(_tokenStartPtr - _code.unicode(), _tokenLength); + } else { + _tokenSpell = _engine->midRef( + int(_tokenStartPtr - _code.unicode()), _tokenLength); + } } return kind; @@ -842,15 +848,13 @@ int Lexer::scanNumber(QChar ch) return T_NUMERIC_LITERAL; } - chars.append('\0'); - const char *begin = chars.constData(); const char *end = nullptr; bool ok = false; - _tokenValue = qstrtod(begin, &end, &ok); + _tokenValue = qstrntod(begin, chars.size(), &end, &ok); - if (end - begin != chars.size() - 1) { + if (end - begin != chars.size()) { _errorCode = IllegalExponentIndicator; _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); return T_ERROR; @@ -891,7 +895,7 @@ bool Lexer::scanRegExp(RegExpBodyPrefix prefix) scanChar(); } - _tokenLength = _codePtr - _tokenStartPtr - 1; + _tokenLength = int(_codePtr - _tokenStartPtr - 1); return true; case '\\': @@ -916,7 +920,7 @@ bool Lexer::scanRegExp(RegExpBodyPrefix prefix) while (! _char.isNull() && ! isLineTerminator()) { if (_char == QLatin1Char(']')) break; - else if (_char == QLatin1Char('\\')) { + if (_char == QLatin1Char('\\')) { // regular expression backslash sequence _tokenText += _char; scanChar(); @@ -995,7 +999,7 @@ int Lexer::tokenEndLine() const int Lexer::tokenEndColumn() const { - return _codePtr - _lastLinePtr; + return int(_codePtr - _lastLinePtr); } QString Lexer::tokenText() const @@ -1067,7 +1071,7 @@ bool Lexer::scanDirectives(Directives *directives) const int lineNumber = tokenStartLine(); - if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD)) + if (_tokenKind != T_IDENTIFIER && _tokenKind != T_RESERVED_WORD) return false; // expected a valid QML/JS directive const QString directiveName = tokenText(); @@ -1079,7 +1083,7 @@ bool Lexer::scanDirectives(Directives *directives) // it must be a pragma or an import directive. if (directiveName == QLatin1String("pragma")) { // .pragma library - if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) + if (lex() != T_IDENTIFIER || tokenText() != QLatin1String("library")) return false; // expected `library // we found a .pragma library directive @@ -1122,7 +1126,7 @@ bool Lexer::scanDirectives(Directives *directives) // // recognize the mandatory `as' followed by the module name // - if (! (lex() == T_RESERVED_WORD && tokenText() == QLatin1String("as"))) + if (lex() != T_RESERVED_WORD || tokenText() != QLatin1String("as")) return false; // expected `as' if (lex() != T_IDENTIFIER) diff --git a/src/lib/corelib/parser/qmljslexer_p.h b/src/lib/corelib/parser/qmljslexer_p.h index cf41fb255..c9801c0f5 100644 --- a/src/lib/corelib/parser/qmljslexer_p.h +++ b/src/lib/corelib/parser/qmljslexer_p.h @@ -56,6 +56,10 @@ #include <tools/qbs_export.h> #include <QtCore/qstring.h> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QtCore5Compat/qstringref.h> +#endif + namespace QbsQmlJS { class Engine; @@ -82,7 +86,7 @@ public: } }; -class QBS_AUTOTEST_EXPORT Lexer: public QmlJSGrammar +class QML_PARSER_EXPORT Lexer : public QmlJSGrammar { public: enum { diff --git a/src/lib/corelib/parser/qmljsparser.cpp b/src/lib/corelib/parser/qmljsparser.cpp index 3c8ae51e0..d2c87c7cb 100644 --- a/src/lib/corelib/parser/qmljsparser.cpp +++ b/src/lib/corelib/parser/qmljsparser.cpp @@ -328,7 +328,7 @@ case 26: { } break; case 27: { - const auto node = new (pool) AST::UiObjectInitializer((AST::UiObjectMemberList*)0); + const auto node = new (pool) AST::UiObjectInitializer(nullptr); node->lbraceToken = loc(1); node->rbraceToken = loc(2); sym(1).Node = node; @@ -614,7 +614,7 @@ case 80: { } break; case 81: { - const auto node = new (pool) AST::ArrayLiteral((AST::Elision *) 0); + const auto node = new (pool) AST::ArrayLiteral(static_cast<AST::Elision *>(nullptr)); node->lbracketToken = loc(1); node->rbracketToken = loc(2); sym(1).Node = node; @@ -635,8 +635,7 @@ case 83: { } break; case 84: { - const auto node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), - (AST::Elision *) 0); + const auto node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), nullptr); node->lbracketToken = loc(1); node->commaToken = loc(3); node->rbracketToken = loc(4); @@ -700,7 +699,7 @@ case 89: { } break; case 90: { - sym(1).Node = new (pool) AST::ElementList((AST::Elision *) 0, sym(1).Expression); + sym(1).Node = new (pool) AST::ElementList(nullptr, sym(1).Expression); } break; case 91: { @@ -708,8 +707,7 @@ case 91: { } break; case 92: { - const auto node = new (pool) AST::ElementList(sym(1).ElementList, - (AST::Elision *) 0, sym(3).Expression); + const auto node = new (pool) AST::ElementList(sym(1).ElementList, nullptr, sym(3).Expression); node->commaToken = loc(2); sym(1).Node = node; } break; diff --git a/src/lib/corelib/parser/qmljsparser_p.h b/src/lib/corelib/parser/qmljsparser_p.h index c761bb25b..9744a7eb6 100644 --- a/src/lib/corelib/parser/qmljsparser_p.h +++ b/src/lib/corelib/parser/qmljsparser_p.h @@ -70,7 +70,7 @@ namespace QbsQmlJS { class Engine; -class QBS_AUTOTEST_EXPORT Parser: protected QmlJSGrammar +class QML_PARSER_EXPORT Parser : protected QmlJSGrammar { public: union Value { diff --git a/src/lib/corelib/tools/applecodesignutils.cpp b/src/lib/corelib/tools/applecodesignutils.cpp index feae266bf..de74e9206 100644 --- a/src/lib/corelib/tools/applecodesignutils.cpp +++ b/src/lib/corelib/tools/applecodesignutils.cpp @@ -95,12 +95,12 @@ QVariantMap certificateInfo(const QByteArray &data) map.insert(QString::fromUtf8(attr), cert.subjectInfo(attr).front()); return map; }; - + const auto sha1 = QString::fromLatin1(cert.digest(QCryptographicHash::Sha1).toHex().toUpper()); return { - {QStringLiteral("SHA1"), cert.digest(QCryptographicHash::Sha1).toHex().toUpper()}, + {QStringLiteral("SHA1"), sha1}, {QStringLiteral("subjectInfo"), subjectInfo(cert)}, - {QStringLiteral("validBefore"), cert.effectiveDate()}, - {QStringLiteral("validAfter"), cert.expiryDate()} + {QStringLiteral("validAfter"), cert.effectiveDate()}, + {QStringLiteral("validBefore"), cert.expiryDate()} }; } diff --git a/src/lib/corelib/tools/architectures.cpp b/src/lib/corelib/tools/architectures.cpp index cf9fec27b..f139509e4 100644 --- a/src/lib/corelib/tools/architectures.cpp +++ b/src/lib/corelib/tools/architectures.cpp @@ -55,7 +55,7 @@ QString canonicalTargetArchitecture(const QString &architecture, const QString &system, const QString &abi) { - const QString arch = canonicalArchitecture(architecture); + QString arch = canonicalArchitecture(architecture); const bool isApple = (vendor == QStringLiteral("apple") || system == QStringLiteral("darwin") || system == QStringLiteral("macosx") diff --git a/src/lib/corelib/tools/buildoptions.cpp b/src/lib/corelib/tools/buildoptions.cpp index e4e9ba17f..cc3ef7557 100644 --- a/src/lib/corelib/tools/buildoptions.cpp +++ b/src/lib/corelib/tools/buildoptions.cpp @@ -413,8 +413,8 @@ template<> JobLimits fromJson(const QJsonValue &limitsData) { JobLimits limits; const QJsonArray &limitsArray = limitsData.toArray(); - for (const QJsonValue &v : limitsArray) { - const QJsonObject limitData = v.toObject(); + for (const auto &value : limitsArray) { + const QJsonObject limitData = value.toObject(); QString pool; int limit = 0; setValueFromJson(pool, limitData, "pool"); diff --git a/src/lib/corelib/tools/buildoptions.h b/src/lib/corelib/tools/buildoptions.h index bd0fb22cb..59b87237d 100644 --- a/src/lib/corelib/tools/buildoptions.h +++ b/src/lib/corelib/tools/buildoptions.h @@ -45,10 +45,10 @@ #include "joblimits.h" #include <QtCore/qshareddata.h> +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QJsonObject; -class QStringList; QT_END_NAMESPACE namespace qbs { diff --git a/src/lib/corelib/tools/clangclinfo.cpp b/src/lib/corelib/tools/clangclinfo.cpp new file mode 100644 index 000000000..fd907ebf1 --- /dev/null +++ b/src/lib/corelib/tools/clangclinfo.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://www.qt.io/licensing +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "clangclinfo.h" + +#include "hostosinfo.h" +#include "msvcinfo.h" +#include "stlutils.h" + +namespace qbs { +namespace Internal { + +static std::vector<MSVCInstallInfo> compatibleMsvcs(Logger &logger) +{ + auto msvcs = MSVCInstallInfo::installedMSVCs(logger); + auto filter = [](const MSVCInstallInfo &info) + { + const auto versions = info.version.split(QLatin1Char('.')); + if (versions.empty()) + return true; + bool ok = false; + const int major = versions.at(0).toInt(&ok); + return !ok || major < 15; // support MSVC2017 and above + }; + Internal::removeIf(msvcs, filter); + for (const auto &msvc: msvcs) { + auto vcvarsallPath = msvc.findVcvarsallBat(); + if (vcvarsallPath.isEmpty()) + continue; + } + return msvcs; +} + +static QString findCompatibleVcsarsallBat(const std::vector<MSVCInstallInfo> &msvcs) +{ + for (const auto &msvc: msvcs) { + auto vcvarsallPath = msvc.findVcvarsallBat(); + if (!vcvarsallPath.isEmpty()) + return vcvarsallPath; + } + return {}; +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else + return {}; +#endif +} + +static QString getToolchainInstallPath(const QFileInfo &compiler) +{ + return compiler.path(); // 1 level up +} + +QVariantMap ClangClInfo::toVariantMap() const +{ + return { + {QStringLiteral("toolchainInstallPath"), toolchainInstallPath}, + {QStringLiteral("vcvarsallPath"), vcvarsallPath}, + }; +} + +ClangClInfo ClangClInfo::fromCompilerFilePath(const QString &path, Logger &logger) +{ + const auto compilerName = QStringLiteral("clang-cl"); + const auto vcvarsallPath = findCompatibleVcsarsallBat(compatibleMsvcs(logger)); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.") + .arg(compilerName); + return {}; + } + + return {getToolchainInstallPath(QFileInfo(path)), vcvarsallPath}; +} + +std::vector<ClangClInfo> ClangClInfo::installedCompilers( + const std::vector<QString> &extraPaths, Logger &logger) +{ + std::vector<QString> compilerPaths; + compilerPaths.reserve(extraPaths.size()); + std::copy_if(extraPaths.begin(), extraPaths.end(), + std::back_inserter(compilerPaths), + [](const QString &path){ return !path.isEmpty(); }); + const auto compilerName = HostOsInfo::appendExecutableSuffix(QStringLiteral("clang-cl")); + + const QSettings registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\LLVM\\LLVM").arg(wow6432Key()), + QSettings::NativeFormat); + const auto key = QStringLiteral("."); + if (registry.contains(key)) { + const auto compilerPath = QDir::fromNativeSeparators(registry.value(key).toString()) + + QStringLiteral("/bin/") + compilerName; + if (QFileInfo::exists(compilerPath) && !contains(compilerPaths, compilerPath)) + compilerPaths.push_back(compilerPath); + } + + // this branch can be useful in case user had two LLVM installations (e.g. 32bit & 64bit) + // but uninstalled one - in that case, registry will be empty + static const char * const envVarCandidates[] = {"ProgramFiles", "ProgramFiles(x86)"}; + for (const auto &envVar : envVarCandidates) { + const auto value + = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); + const auto compilerPath = value + QStringLiteral("/LLVM/bin/") + compilerName; + if (QFileInfo::exists(compilerPath) && !contains(compilerPaths, compilerPath)) + compilerPaths.push_back(compilerPath); + } + + const auto msvcs = compatibleMsvcs(logger); + const auto vcvarsallPath = findCompatibleVcsarsallBat(msvcs); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.") + .arg(compilerName); + return {}; + } + + std::vector<ClangClInfo> result; + result.reserve(compilerPaths.size() + msvcs.size()); + transform(compilerPaths, result, [&vcvarsallPath](const auto &path) { + return ClangClInfo{getToolchainInstallPath(QFileInfo(path)), vcvarsallPath}; }); + + // If we didn't find custom LLVM installation, try to find if it's installed with Visual Studio + for (const auto &msvc : msvcs) { + const auto compilerPath = QStringLiteral("%1/VC/Tools/Llvm/bin/%2") + .arg(msvc.installDir, compilerName); + if (QFileInfo::exists(compilerPath)) { + const auto vcvarsallPath = msvc.findVcvarsallBat(); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("Found LLVM in %1, but vcvarsall.bat is missing.") + .arg(msvc.installDir); + } + + result.push_back({getToolchainInstallPath(QFileInfo(compilerPath)), vcvarsallPath}); + } + } + + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/clangclinfo.h b/src/lib/corelib/tools/clangclinfo.h new file mode 100644 index 000000000..76ae169f2 --- /dev/null +++ b/src/lib/corelib/tools/clangclinfo.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://www.qt.io/licensing +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QBS_CLANGCLINFO_H +#define QBS_CLANGCLINFO_H + +#include "logging/logger.h" + +#include <QtCore/qstring.h> + +#include <vector> + +namespace qbs { +namespace Internal { + +class ClangClInfo +{ +public: + QString toolchainInstallPath; + QString vcvarsallPath; + + bool isEmpty() const { return toolchainInstallPath.isEmpty() && vcvarsallPath.isEmpty(); } + + QBS_EXPORT QVariantMap toVariantMap() const; + + QBS_EXPORT static ClangClInfo fromCompilerFilePath(const QString &path, Logger &logger); + QBS_EXPORT static std::vector<ClangClInfo> installedCompilers( + const std::vector<QString> &extraPaths, qbs::Internal::Logger &logger); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_CLANGCLINFO_H diff --git a/src/lib/corelib/tools/cleanoptions.cpp b/src/lib/corelib/tools/cleanoptions.cpp index cef77468e..affc9b3f7 100644 --- a/src/lib/corelib/tools/cleanoptions.cpp +++ b/src/lib/corelib/tools/cleanoptions.cpp @@ -58,7 +58,7 @@ public: bool logElapsedTime; }; -} +} // namespace Internal /*! * \class CleanOptions diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp index 542408795..f04041e14 100644 --- a/src/lib/corelib/tools/codelocation.cpp +++ b/src/lib/corelib/tools/codelocation.cpp @@ -47,7 +47,7 @@ #include <QtCore/qdir.h> #include <QtCore/qjsonobject.h> #include <QtCore/qjsonvalue.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qshareddata.h> #include <QtCore/qstring.h> @@ -87,8 +87,9 @@ CodeLocation::CodeLocation(const QString &aFilePath, int aLine, int aColumn, boo } CodeLocation::CodeLocation(const CodeLocation &other) = default; - +CodeLocation::CodeLocation(CodeLocation &&other) noexcept = default; CodeLocation &CodeLocation::operator=(const CodeLocation &other) = default; +CodeLocation &CodeLocation::operator=(CodeLocation &&other) noexcept = default; CodeLocation::~CodeLocation() = default; @@ -118,9 +119,9 @@ QString CodeLocation::toString() const if (isValid()) { str = QDir::toNativeSeparators(filePath()); QString lineAndColumn; - if (line() > 0 && !str.contains(QRegExp(QStringLiteral(":[0-9]+$")))) + if (line() > 0 && !str.contains(QRegularExpression(QStringLiteral(":[0-9]+$")))) lineAndColumn += QLatin1Char(':') + QString::number(line()); - if (column() > 0 && !str.contains(QRegExp(QStringLiteral(":[0-9]+:[0-9]+$")))) + if (column() > 0 && !str.contains(QRegularExpression(QStringLiteral(":[0-9]+:[0-9]+$")))) lineAndColumn += QLatin1Char(':') + QString::number(column()); str += lineAndColumn; } @@ -181,4 +182,44 @@ bool operator<(const CodeLocation &cl1, const CodeLocation &cl2) return cl1.toString() < cl2.toString(); } +void CodePosition::load(Internal::PersistentPool &pool) { pool.load(m_line, m_column); } +void CodePosition::store(Internal::PersistentPool &pool) const { pool.store(m_line, m_column); } + +bool operator==(const CodePosition &pos1, const CodePosition &pos2) +{ + return pos1.line() == pos2.line() && pos1.column() == pos2.column(); +} +bool operator!=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 == pos2); } + +bool operator<(const CodePosition &pos1, const CodePosition &pos2) +{ + const int lineDiff = pos1.line() - pos2.line(); + if (lineDiff < 0) + return true; + if (lineDiff > 0) + return false; + return pos1.column() < pos2.column(); +} +bool operator>(const CodePosition &pos1, const CodePosition &pos2) { return pos2 < pos1; } +bool operator<=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 > pos2); } +bool operator>=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 < pos2); } + +CodeRange::CodeRange(const CodePosition &start, const CodePosition &end) + : m_start(start), m_end(end) {} + +void CodeRange::load(Internal::PersistentPool &pool) { pool.load(m_start, m_end); } +void CodeRange::store(Internal::PersistentPool &pool) const { pool.store(m_start, m_end); } + +bool CodeRange::contains(const CodePosition &pos) const +{ + return start() <= pos && end() > pos; +} + +bool operator==(const CodeRange &r1, const CodeRange &r2) +{ + return r1.start() == r2.start() && r1.end() == r2.end(); +} +bool operator!=(const CodeRange &r1, const CodeRange &r2) { return !(r1 == r2); } +bool operator<(const CodeRange &r1, const CodeRange &r2) { return r1.start() < r2.start(); } + } // namespace qbs diff --git a/src/lib/corelib/tools/codelocation.h b/src/lib/corelib/tools/codelocation.h index 3e84ce2d1..afcd2e075 100644 --- a/src/lib/corelib/tools/codelocation.h +++ b/src/lib/corelib/tools/codelocation.h @@ -62,7 +62,9 @@ public: explicit CodeLocation(const QString &aFilePath, int aLine = -1, int aColumn = -1, bool checkPath = true); CodeLocation(const CodeLocation &other); + CodeLocation(CodeLocation &&other) noexcept; CodeLocation &operator=(const CodeLocation &other); + CodeLocation &operator=(CodeLocation &&other) noexcept; ~CodeLocation(); QString filePath() const; @@ -84,10 +86,78 @@ private: QBS_EXPORT bool operator==(const CodeLocation &cl1, const CodeLocation &cl2); QBS_EXPORT bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2); QBS_EXPORT bool operator<(const CodeLocation &cl1, const CodeLocation &cl2); +inline auto qHash(const CodeLocation &cl) { return qHash(cl.toString()); } +QDebug operator<<(QDebug debug, const CodeLocation &location); -inline uint qHash(const CodeLocation &cl) { return qHash(cl.toString()); } +class QBS_EXPORT CodePosition +{ +public: + CodePosition(int line, int column) : m_line(line), m_column(column) {} -QDebug operator<<(QDebug debug, const CodeLocation &location); + CodePosition() = default; + CodePosition(const CodePosition &other) = default; + CodePosition(CodePosition &&other) = default; + CodePosition &operator=(const CodePosition &other) = default; + CodePosition &operator=(CodePosition &&other) = default; + + int line() const { return m_line; } + void setLine(int newLine) { m_line = newLine; } + + int column() const { return m_column; } + void setColumn(int newColumn) { m_column = newColumn; } + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool) const; + +private: + int m_line = 0; + int m_column = 0; +}; + +QBS_EXPORT bool operator==(const CodePosition &pos1, const CodePosition &pos2); +QBS_EXPORT bool operator!=(const CodePosition &pos1, const CodePosition &pos2); +QBS_EXPORT bool operator<(const CodePosition &pos1, const CodePosition &pos2); +QBS_EXPORT bool operator>(const CodePosition &pos1, const CodePosition &pos2); +QBS_EXPORT bool operator<=(const CodePosition &pos1, const CodePosition &pos2); +QBS_EXPORT bool operator>=(const CodePosition &pos1, const CodePosition &pos2); +inline auto qHash(const CodePosition &pos) +{ + return QT_PREPEND_NAMESPACE(qHash)(pos.line()) ^ QT_PREPEND_NAMESPACE(qHash)(pos.column()); +} + +class QBS_EXPORT CodeRange +{ +public: + CodeRange(const CodePosition &start, const CodePosition &end); + + CodeRange() = default; + CodeRange(const CodeRange &other) = default; + CodeRange(CodeRange &&other) = default; + CodeRange &operator=(const CodeRange &other) = default; + CodeRange &operator=(CodeRange &&other) = default; + + const CodePosition &start() const & { return m_start; } + const CodePosition &end() const & { return m_end; } + CodePosition start() && { return std::move(m_start); } + CodePosition end() && { return std::move(m_end); } + + bool contains(const CodePosition &pos) const; + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool) const; + +private: + CodePosition m_start; + CodePosition m_end; +}; + +QBS_EXPORT bool operator==(const CodeRange &r1, const CodeRange &r2); +QBS_EXPORT bool operator!=(const CodeRange &r1, const CodeRange &r2); +QBS_EXPORT bool operator<(const CodeRange &r1, const CodeRange &r2); +inline auto qHash(const CodeRange &range) { return qHash(range.start()) ^ qHash(range.end()); } + +using CodeLinksInFile = QHash<CodeRange, QList<CodeLocation>>; +using CodeLinks = QHash<QString, CodeLinksInFile>; } // namespace qbs diff --git a/src/lib/corelib/tools/commandechomode.h b/src/lib/corelib/tools/commandechomode.h index 88d8377ad..e7b315563 100644 --- a/src/lib/corelib/tools/commandechomode.h +++ b/src/lib/corelib/tools/commandechomode.h @@ -43,11 +43,7 @@ #include "qbs_export.h" #include <QtCore/qglobal.h> - -QT_BEGIN_NAMESPACE -class QString; -class QStringList; -QT_END_NAMESPACE +#include <QtCore/qstringlist.h> namespace qbs { diff --git a/src/lib/corelib/tools/deprecationwarningmode.cpp b/src/lib/corelib/tools/deprecationwarningmode.cpp new file mode 100644 index 000000000..e140e3e49 --- /dev/null +++ b/src/lib/corelib/tools/deprecationwarningmode.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "deprecationwarningmode.h" + +/*! + * \enum DeprecationWarningMode + * This enum type specifies how \QBS should behave on encountering deprecated items or properties. + * \value DeprecationWarningMode::Error Project resolving will stop with an error message. + * \value DeprecationWarningMode::On A warning will be printed. + * \value DeprecationWarningMode::BeforeRemoval A warning will be printed if and only if this is + * the last \QBS version before the removal version. This is the default behavior. + * \note If the removal version's minor version number is zero, the behavior is + * the same as for ErrorHandlingMode::On. + * \value DeprecationWarningMode::Off No warnings will be emitted for deprecated constructs. + */ + +namespace qbs { + +DeprecationWarningMode defaultDeprecationWarningMode() +{ + return DeprecationWarningMode::BeforeRemoval; +} + +QString deprecationWarningModeName(DeprecationWarningMode mode) +{ + switch (mode) { + case DeprecationWarningMode::Error: + return QStringLiteral("error"); + case DeprecationWarningMode::On: + return QStringLiteral("on"); + case DeprecationWarningMode::BeforeRemoval: + return QStringLiteral("before-removal"); + case DeprecationWarningMode::Off: + return QStringLiteral("off"); + default: + break; + } + return {}; +} + +DeprecationWarningMode deprecationWarningModeFromName(const QString &name) +{ + DeprecationWarningMode mode = defaultDeprecationWarningMode(); + for (int i = 0; i <= int(DeprecationWarningMode::Sentinel); ++i) { + if (deprecationWarningModeName(static_cast<DeprecationWarningMode>(i)) == name) { + mode = static_cast<DeprecationWarningMode>(i); + break; + } + } + return mode; +} + +QStringList allDeprecationWarningModeStrings() +{ + QStringList result; + for (int i = 0; i <= int(DeprecationWarningMode::Sentinel); ++i) + result << deprecationWarningModeName(static_cast<DeprecationWarningMode>(i)); + return result; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/deprecationwarningmode.h b/src/lib/corelib/tools/deprecationwarningmode.h new file mode 100644 index 000000000..bb2a14155 --- /dev/null +++ b/src/lib/corelib/tools/deprecationwarningmode.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBS_DEPRECATIONWARNINGMODE_H +#define QBS_DEPRECATIONWARNINGMODE_H + +#include "qbs_export.h" + +#include <QtCore/qstringlist.h> + +namespace qbs { + +enum class DeprecationWarningMode { Error, On, BeforeRemoval, Off, Sentinel = Off }; + +QBS_EXPORT DeprecationWarningMode defaultDeprecationWarningMode(); +QBS_EXPORT QString deprecationWarningModeName(DeprecationWarningMode mode); +QBS_EXPORT DeprecationWarningMode deprecationWarningModeFromName(const QString &name); +QBS_EXPORT QStringList allDeprecationWarningModeStrings(); + +} // namespace qbs + +#endif // QBS_DEPRECATIONWARNINGMODE_H + diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp index ff7ce46cf..f1b90b71e 100644 --- a/src/lib/corelib/tools/error.cpp +++ b/src/lib/corelib/tools/error.cpp @@ -40,8 +40,11 @@ #include "error.h" #include "persistence.h" -#include "qttools.h" #include "stringconstants.h" +#include "setupprojectparameters.h" +#include "logging/logger.h" + +#include <tools/stlutils.h> #include <QtCore/qjsonarray.h> #include <QtCore/qjsonobject.h> @@ -205,15 +208,21 @@ ErrorInfo::ErrorInfo(const QString &description, const QStringList &backtrace) { append(description); for (const QString &traceLine : backtrace) { - static const std::regex regexp("^(.+) at (.+):(\\-?[0-9]+)$"); + if (traceLine.contains(QStringLiteral("<eval>"))) + continue; + static const std::regex regexpWithFunc("^(.+) at [^(]*\\((.+):(\\-?[0-9]+)\\)$"); + static const std::regex regexpWithoutFunc("^(.+) at (.+):(\\-?[0-9]+)$"); std::smatch match; const std::string tl = traceLine.toStdString(); - if (std::regex_match(tl, match, regexp)) { - const QString message = QString::fromStdString(match[1]), - file = QString::fromStdString(match[2]), - line = QString::fromStdString(match[3]); + bool hasMatch = std::regex_match(tl, match, regexpWithFunc); + if (!hasMatch) + hasMatch = std::regex_match(tl, match, regexpWithoutFunc); + if (hasMatch) { + const QString message = QString::fromStdString(match[1]).trimmed(); + const QString file = QString::fromStdString(match[2]); + const QString line = QString::fromStdString(match[3]); const CodeLocation location(file, line.toInt()); - appendBacktrace(message, location); + appendBacktrace(message, CodeLocation(file, line.toInt())); } } } @@ -267,7 +276,7 @@ void ErrorInfo::clear() QString ErrorInfo::toString() const { QStringList lines; - for (const ErrorItem &e : qAsConst(d->items)) { + for (const ErrorItem &e : std::as_const(d->items)) { if (e.isBacktraceItem()) { QString line; if (!e.description().isEmpty()) @@ -304,8 +313,15 @@ bool ErrorInfo::isInternalError() const bool ErrorInfo::hasLocation() const { - return std::any_of(d->items.cbegin(), d->items.cend(), - [](const ErrorItem &ei) { return ei.codeLocation().isValid(); }); + return Internal::any_of(d->items, [](const ErrorItem &ei) { + return ei.codeLocation().isValid(); }); +} + +bool ErrorInfo::isCancelException() const +{ + return Internal::any_of(d->items, [](const ErrorItem &ei) { + return ei.description() == QLatin1String("interrupted"); + }); } void ErrorInfo::load(Internal::PersistentPool &pool) @@ -325,4 +341,12 @@ void appendError(ErrorInfo &dst, const ErrorInfo &src) dst.append(item); } +void handlePropertyError( + const ErrorInfo &error, const SetupProjectParameters ¶ms, Internal::Logger &logger) +{ + if (params.propertyCheckingMode() == ErrorHandlingMode::Strict) + throw error; + logger.printWarning(error); +} + } // namespace qbs diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h index 36cf5e0ea..ba600a558 100644 --- a/src/lib/corelib/tools/error.h +++ b/src/lib/corelib/tools/error.h @@ -45,18 +45,21 @@ #include <QtCore/qhash.h> #include <QtCore/qmetatype.h> #include <QtCore/qshareddata.h> +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QJsonObject; -template <class T> class QList; -class QString; -class QStringList; QT_END_NAMESPACE namespace qbs { -namespace Internal { class PersistentPool; } +namespace Internal { +class PersistentPool; +class Logger; +} // namespace Internal class CodeLocation; +class SetupProjectParameters; + class QBS_EXPORT ErrorItem { friend class ErrorInfo; @@ -110,6 +113,7 @@ public: QJsonObject toJson() const; bool isInternalError() const; bool hasLocation() const; + bool isCancelException() const; void load(Internal::PersistentPool &pool); void store(Internal::PersistentPool &pool) const; @@ -120,7 +124,10 @@ private: }; void appendError(ErrorInfo &dst, const ErrorInfo &src); -inline uint qHash(const ErrorInfo &e) { return qHash(e.toString()); } +void handlePropertyError( + const ErrorInfo &error, const SetupProjectParameters ¶ms, Internal::Logger &logger); + +inline auto qHash(const ErrorInfo &e) { return qHash(e.toString()); } inline bool operator==(const ErrorInfo &e1, const ErrorInfo &e2) { return e1.toString() == e2.toString(); } diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp index 0dc06dd86..b5d9c0151 100644 --- a/src/lib/corelib/tools/executablefinder.cpp +++ b/src/lib/corelib/tools/executablefinder.cpp @@ -76,7 +76,7 @@ QString ExecutableFinder::findExecutable(const QString &path, const QString &wor //if (FileInfo::fileName(filePath) == filePath) if (!FileInfo::isAbsolute(filePath)) return findInPath(filePath, workingDirPath); - else if (HostOsInfo::isWindowsHost()) + if (HostOsInfo::isWindowsHost()) return findBySuffix(filePath); return filePath; } @@ -99,7 +99,7 @@ QString ExecutableFinder::findBySuffix(const QString &filePath) const bool ExecutableFinder::candidateCheck(const QString &directory, const QString &program, QString &fullProgramPath) const { - for (const QString &suffix : qAsConst(m_executableSuffixes)) { + for (const QString &suffix : std::as_const(m_executableSuffixes)) { QString candidate = directory + program + suffix; qCDebug(lcExec) << "candidate:" << candidate; QFileInfo fi(candidate); @@ -120,10 +120,10 @@ QString ExecutableFinder::findInPath(const QString &filePath, const QString &wor fullProgramPath = filePath; qCDebug(lcExec) << "looking for executable in PATH" << fullProgramPath; QStringList pathEnv = m_environment.value(StringConstants::pathEnvVar()) - .split(HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts); if (HostOsInfo::isWindowsHost()) pathEnv.prepend(StringConstants::dot()); - for (QString directory : qAsConst(pathEnv)) { + for (QString directory : std::as_const(pathEnv)) { if (directory == StringConstants::dot()) directory = workingDirPath; if (!directory.isEmpty()) { diff --git a/src/lib/corelib/tools/fileinfo.cpp b/src/lib/corelib/tools/fileinfo.cpp index 1918117d6..f56762c04 100644 --- a/src/lib/corelib/tools/fileinfo.cpp +++ b/src/lib/corelib/tools/fileinfo.cpp @@ -41,13 +41,14 @@ #include <logging/translator.h> #include <tools/qbsassert.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qdatetime.h> #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #if defined(Q_OS_UNIX) #include <cerrno> @@ -133,16 +134,16 @@ void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QString *d *fileName = filePath.mid(idx + 1); } -void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName) +void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QStringView *dirPath, QStringView *fileName) { int idx = filePath.lastIndexOf(QLatin1Char('/')); if (idx < 0) { - dirPath->clear(); - *fileName = QStringRef(&filePath); + *dirPath = QStringView(); + *fileName = QStringView(filePath); return; } - *dirPath = filePath.leftRef(idx); - *fileName = filePath.midRef(idx + 1); + *dirPath = QStringView(filePath).left(idx); + *fileName = QStringView(filePath).mid(idx + 1); } bool FileInfo::exists(const QString &fp) @@ -180,20 +181,12 @@ bool FileInfo::isAbsolute(const QString &path, HostOsInfo::HostOs hostOs) return false; } -bool FileInfo::isPattern(const QString &str) -{ - return isPattern(QStringRef(&str)); -} - -bool FileInfo::isPattern(const QStringRef &str) +bool FileInfo::isPattern(QStringView str) { - for (const QChar &ch : str) { - if (ch == QLatin1Char('*') || ch == QLatin1Char('?') - || ch == QLatin1Char(']') || ch == QLatin1Char('[')) { - return true; - } - } - return false; + return Internal::any_of(str, [](const auto &ch) { + return (ch == QLatin1Char('*') || ch == QLatin1Char('?') || ch == QLatin1Char(']') + || ch == QLatin1Char('[')); + }); } /** @@ -242,17 +235,6 @@ QString FileInfo::resolvePath(const QString &base, const QString &rel, HostOsInf 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); -} - #ifdef Q_OS_WIN static QString prependLongPathPrefix(const QString &absolutePath) { @@ -353,9 +335,70 @@ QString applicationDirPath() #elif defined(Q_OS_UNIX) +namespace GetFileTimes { + +enum TimeType { LastModified, LastStatusChanged }; + +// SFINAE magic inspired by +// https://github.com/qt/qtbase/blob/5.13/src/corelib/io/qfilesystemengine_unix.cpp +template< + TimeType type, + typename T, + std::enable_if_t<(&T::st_mtim, &T::st_ctim, true), int> = 0> +static inline FileTime get(const T &stat) +{ + if constexpr (type == LastModified) + return stat.st_mtim; + else if constexpr (type == LastStatusChanged) + return stat.st_ctim; +} + +// we are interested in real members, not compatibility macros +# if defined(st_mtimespec) +# undef st_mtimespec +# endif + +# if defined(st_ctimespec) +# undef st_ctimespec +# endif + +template< + TimeType type, + typename T, + std::enable_if_t<(&T::st_mtimespec, &T::st_ctimespec, true), int> = 0> +static inline FileTime get(const T &stat) +{ + if constexpr (type == LastModified) + return stat.st_mtimespec; + else if constexpr (type == LastStatusChanged) + return stat.st_ctimespec; +} + +# if defined(st_mtimensec) +# undef st_mtimensec +# endif + +# if defined(st_ctimensec) +# undef st_ctimensec +# endif + +template< + TimeType type, + typename T, + std::enable_if_t<(&T::st_mtimensec, &T::st_ctimensec, true), int> = 0> +static inline FileTime get(const T &stat) +{ + if constexpr (type == LastModified) + return FileTime::InternalType{stat.st_mtime, stat.st_mtimensec}; + else if constexpr (type == LastStatusChanged) + return FileTime::InternalType{stat.st_ctime, stat.st_ctimensec}; +} + +} // namespace GetFileTimes + FileInfo::FileInfo(const QString &fileName) { - if (stat(fileName.toLocal8Bit(), &m_stat) == -1) { + if (stat(fileName.toLocal8Bit().constData(), &m_stat) == -1) { m_stat.st_mtime = 0; m_stat.st_mode = 0; } @@ -368,24 +411,12 @@ bool FileInfo::exists() const FileTime FileInfo::lastModified() const { -#if APPLE_STAT_TIMESPEC - return m_stat.st_mtimespec; -#elif HAS_CLOCK_GETTIME - return m_stat.st_mtim; -#else - return m_stat.st_mtime; -#endif + return GetFileTimes::get<GetFileTimes::LastModified>(m_stat); } FileTime FileInfo::lastStatusChange() const { -#if APPLE_STAT_TIMESPEC - return m_stat.st_ctimespec; -#elif HAS_CLOCK_GETTIME - return m_stat.st_ctim; -#else - return m_stat.st_ctime; -#endif + return GetFileTimes::get<GetFileTimes::LastStatusChanged>(m_stat); } bool FileInfo::isDir() const @@ -450,7 +481,7 @@ static QByteArray storedLinkTarget(const QString &filePath) const QByteArray nativeFilePath = QFile::encodeName(filePath); ssize_t len; while (true) { - struct stat sb; + struct stat sb{}; if (lstat(nativeFilePath.constData(), &sb)) { qWarning("storedLinkTarget: lstat for %s failed with error code %d", nativeFilePath.constData(), errno); diff --git a/src/lib/corelib/tools/fileinfo.h b/src/lib/corelib/tools/fileinfo.h index 9813b69a7..3ad585f74 100644 --- a/src/lib/corelib/tools/fileinfo.h +++ b/src/lib/corelib/tools/fileinfo.h @@ -72,14 +72,12 @@ public: static QString completeSuffix(const QString &fp); static QString path(const QString &fp, HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); static void splitIntoDirectoryAndFileName(const QString &filePath, QString *dirPath, QString *fileName); - static void splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName); + static void splitIntoDirectoryAndFileName(const QString &filePath, QStringView *dirPath, QStringView *fileName); static bool exists(const QString &fp); static bool isAbsolute(const QString &fp, HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); - static bool isPattern(const QStringRef &str); - static bool isPattern(const QString &str); + static bool isPattern(QStringView str); static QString resolvePath(const QString &base, const QString &rel, HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); - static bool globMatches(const QRegExp &pattern, const QString &subject); static bool isFileCaseCorrect(const QString &filePath); // Symlink-correct check. @@ -101,10 +99,10 @@ private: 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, bool copyDirectoryContents, QString *errorMessage); +bool QBS_AUTOTEST_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage); +bool QBS_AUTOTEST_EXPORT copyFileRecursion( + const QString &sourcePath, const QString &targetPath, bool preserveSymLinks, + bool copyDirectoryContents, QString *errorMessage); } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/filesaver.cpp b/src/lib/corelib/tools/filesaver.cpp index 5a0a68c1f..3459ac558 100644 --- a/src/lib/corelib/tools/filesaver.cpp +++ b/src/lib/corelib/tools/filesaver.cpp @@ -107,14 +107,9 @@ bool FileSaver::commit() return true; } -bool FileSaver::write(const std::vector<char> &data) +bool FileSaver::write(std::string_view data) { - return fwrite(data, device()); -} - -bool FileSaver::write(const std::string &data) -{ - return fwrite(data, device()); + return Internal::fwrite(data.data(), data.size(), device()); } } // namespace Internal diff --git a/src/lib/corelib/tools/filesaver.h b/src/lib/corelib/tools/filesaver.h index 8b4c01669..06f11eba0 100644 --- a/src/lib/corelib/tools/filesaver.h +++ b/src/lib/corelib/tools/filesaver.h @@ -60,8 +60,7 @@ public: std::ostream *device(); bool open(); bool commit(); - bool write(const std::vector<char> &data); - bool write(const std::string &data); + bool write(std::string_view data); private: std::string m_oldFileContents; diff --git a/src/lib/corelib/tools/filetime.cpp b/src/lib/corelib/tools/filetime.cpp index d115075a7..4ccb5731e 100644 --- a/src/lib/corelib/tools/filetime.cpp +++ b/src/lib/corelib/tools/filetime.cpp @@ -50,78 +50,29 @@ namespace qbs { namespace Internal { -#ifdef APPLE_CUSTOM_CLOCK_GETTIME -#include <sys/time.h> - -#ifndef CLOCK_REALTIME -#define CLOCK_REALTIME 0 -#endif - -// clk_id isn't used, only the CLOCK_REALTIME case is implemented. -int clock_gettime(int /*clk_id*/, struct timespec *t) -{ - struct timeval tv; - // Resolution of gettimeofday is 1000nsecs = 1 microsecond. - int ret = gettimeofday(&tv, NULL); - t->tv_sec = tv.tv_sec; - t->tv_nsec = tv.tv_usec * 1000; - return ret; -} -#endif +#if defined(Q_OS_WIN) FileTime::FileTime() { -#ifdef Q_OS_WIN static_assert(sizeof(FileTime::InternalType) == sizeof(FILETIME), "FileTime::InternalType has wrong size."); m_fileTime = 0; -#elif HAS_CLOCK_GETTIME - m_fileTime = {0, 0}; -#else - m_fileTime = 0; -#endif } FileTime::FileTime(const FileTime::InternalType &ft) : m_fileTime(ft) { -#if HAS_CLOCK_GETTIME - if (m_fileTime.tv_sec == 0) - m_fileTime.tv_nsec = 0; // stat() sets only the first member to 0 for non-existing files. -#endif } int FileTime::compare(const FileTime &other) const { -#ifdef Q_OS_WIN auto const t1 = reinterpret_cast<const FILETIME *>(&m_fileTime); auto const t2 = reinterpret_cast<const FILETIME *>(&other.m_fileTime); return CompareFileTime(t1, t2); -#elif HAS_CLOCK_GETTIME - if (m_fileTime.tv_sec < other.m_fileTime.tv_sec) - return -1; - if (m_fileTime.tv_sec > other.m_fileTime.tv_sec) - return 1; - if (m_fileTime.tv_nsec < other.m_fileTime.tv_nsec) - return -1; - if (m_fileTime.tv_nsec > other.m_fileTime.tv_nsec) - return 1; - return 0; -#else - if (m_fileTime < other.m_fileTime) - return -1; - if (m_fileTime > other.m_fileTime) - return 1; - return 0; -#endif } void FileTime::clear() { -#if HAS_CLOCK_GETTIME - m_fileTime = { 0, 0 }; -#else m_fileTime = 0; -#endif } bool FileTime::isValid() const @@ -131,32 +82,16 @@ bool FileTime::isValid() const FileTime FileTime::currentTime() { -#ifdef Q_OS_WIN FileTime result; SYSTEMTIME st; GetSystemTime(&st); auto const ft = reinterpret_cast<FILETIME *>(&result.m_fileTime); SystemTimeToFileTime(&st, ft); return result; -#elif defined APPLE_CUSTOM_CLOCK_GETTIME - InternalType t; - // Explicitly use our custom version, so that we don't get an additional unresolved symbol on a - // system that actually provides one, but isn't used due to the minimium deployment target - // being lower. - qbs::Internal::clock_gettime(CLOCK_REALTIME, &t); - return t; -#elif HAS_CLOCK_GETTIME - InternalType t; - clock_gettime(CLOCK_REALTIME, &t); - return t; -#else - return time(nullptr); -#endif } FileTime FileTime::oldestTime() { -#ifdef Q_OS_WIN SYSTEMTIME st = { 1601, 1, @@ -171,25 +106,15 @@ FileTime FileTime::oldestTime() auto const ft = reinterpret_cast<FILETIME *>(&result.m_fileTime); SystemTimeToFileTime(&st, ft); return result; -#elif HAS_CLOCK_GETTIME - return FileTime({1, 0}); -#else - return 1; -#endif } double FileTime::asDouble() const { -#if HAS_CLOCK_GETTIME - return static_cast<double>(m_fileTime.tv_sec); -#else return static_cast<double>(m_fileTime); -#endif } QString FileTime::toString() const { -#ifdef Q_OS_WIN auto const ft = reinterpret_cast<const FILETIME *>(&m_fileTime); SYSTEMTIME stUTC, stLocal; FileTimeToSystemTime(ft, &stUTC); @@ -198,16 +123,93 @@ QString FileTime::toString() const .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; -#else +} + +#else // defined(Q_OS_WIN) + +// based on https://github.com/qt/qtbase/blob/5.13/src/corelib/kernel/qelapsedtimer_unix.cpp +// for details why it is implemented this way, see Qt source code +# if !defined(CLOCK_REALTIME) +# define CLOCK_REALTIME 0 +static inline void qbs_clock_gettime(int, struct timespec *ts) +{ + // support clock_gettime with gettimeofday + struct timeval tv; + gettimeofday(&tv, 0); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; +} + +# ifdef _POSIX_MONOTONIC_CLOCK +# undef _POSIX_MONOTONIC_CLOCK +# define _POSIX_MONOTONIC_CLOCK -1 +# endif +# else +static inline void qbs_clock_gettime(clockid_t clock, struct timespec *ts) +{ + clock_gettime(clock, ts); +} +# endif + +FileTime::FileTime() +{ + m_fileTime = {0, 0}; +} + +FileTime::FileTime(const FileTime::InternalType &ft) : m_fileTime(ft) +{ + if (m_fileTime.tv_sec == 0) + m_fileTime.tv_nsec = 0; // stat() sets only the first member to 0 for non-existing files. +} + +int FileTime::compare(const FileTime &other) const +{ + if (m_fileTime.tv_sec < other.m_fileTime.tv_sec) + return -1; + if (m_fileTime.tv_sec > other.m_fileTime.tv_sec) + return 1; + if (m_fileTime.tv_nsec < other.m_fileTime.tv_nsec) + return -1; + if (m_fileTime.tv_nsec > other.m_fileTime.tv_nsec) + return 1; + return 0; +} + +void FileTime::clear() +{ + m_fileTime = { 0, 0 }; +} + +bool FileTime::isValid() const +{ + return *this != FileTime(); +} + +FileTime FileTime::currentTime() +{ + InternalType t; + qbs_clock_gettime(CLOCK_REALTIME, &t); + return t; +} + +FileTime FileTime::oldestTime() +{ + return FileTime({1, 0}); +} + +double FileTime::asDouble() const +{ + return static_cast<double>(m_fileTime.tv_sec); +} + +QString FileTime::toString() const +{ QDateTime dt; -#if HAS_CLOCK_GETTIME - dt.setMSecsSinceEpoch(m_fileTime.tv_sec * 1000 + m_fileTime.tv_nsec / 1000000); -#else - dt.setTime_t(m_fileTime); -#endif + dt.setMSecsSinceEpoch(m_fileTime.tv_sec * 1000 + m_fileTime.tv_nsec / 1000000); return dt.toString(Qt::ISODateWithMs); -#endif } +#endif // defined(Q_OS_WIN) + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/filetime.h b/src/lib/corelib/tools/filetime.h index f9a15f794..a0d4d2d7d 100644 --- a/src/lib/corelib/tools/filetime.h +++ b/src/lib/corelib/tools/filetime.h @@ -45,28 +45,10 @@ #include <QtCore/qdatastream.h> #include <QtCore/qdebug.h> -#if defined(Q_OS_UNIX) && !defined(__APPLE__) +#if defined(Q_OS_UNIX) #include <time.h> -#define HAS_CLOCK_GETTIME (_POSIX_C_SOURCE >= 199309L) #endif // Q_OS_UNIX -#ifdef __APPLE__ - -#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 -// macOS 10.12+ ships clock_gettime. -#else -// We implement our own clock_gettime. -#define APPLE_CUSTOM_CLOCK_GETTIME 1 -#endif // __MAC_OS_X_VERSION_MIN_REQUIRED - -// Either way we have a clock_gettime in the end. -#define HAS_CLOCK_GETTIME 1 - -// Apple stat struct has slightly different names for time fields. -#define APPLE_STAT_TIMESPEC 1 - -#endif // __APPLE__ - namespace qbs { namespace Internal { @@ -74,11 +56,7 @@ class QBS_AUTOTEST_EXPORT FileTime { public: #if defined(Q_OS_UNIX) -#if HAS_CLOCK_GETTIME using InternalType = timespec; -#else - using InternalType = time_t; -#endif // HAS_CLOCK_GETTIME #elif defined(Q_OS_WIN) using InternalType = quint64; #else @@ -107,7 +85,7 @@ public: template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) { -#if HAS_CLOCK_GETTIME +#if !defined(Q_OS_WIN) pool.serializationOp<opType>(m_fileTime.tv_sec, m_fileTime.tv_nsec); #else pool.serializationOp<opType>(m_fileTime); diff --git a/src/lib/corelib/tools/hostosinfo.h b/src/lib/corelib/tools/hostosinfo.h index d7f718c19..32817f731 100644 --- a/src/lib/corelib/tools/hostosinfo.h +++ b/src/lib/corelib/tools/hostosinfo.h @@ -67,16 +67,16 @@ namespace qbs { namespace Internal { -class QBS_EXPORT HostOsInfo // Exported for use by command-line tools. +class HostOsInfo { public: // Add more as needed. enum HostOs { HostOsWindows, HostOsLinux, HostOsMacos, HostOsOtherUnix, HostOsOther }; - static inline std::string hostOSIdentifier(); - static inline std::string hostOSArchitecture(); - static inline std::vector<std::string> hostOSIdentifiers(); - static inline std::vector<std::string> canonicalOSIdentifiers(const std::string &os); + static inline QString hostOSIdentifier(); + static inline QString hostOSArchitecture(); + static inline std::vector<QString> hostOSIdentifiers(); + static inline std::vector<QString> canonicalOSIdentifiers(const QString &os); static inline HostOs hostOs(); static inline Version hostOsVersion() { @@ -98,6 +98,28 @@ public: return v; } + static QString hostOsBuildVersion() { + QString v; + if (HostOsInfo::isWindowsHost()) { + QSettings settings(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\" + "Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + v = settings.value(QStringLiteral("CurrentBuildNumber")).toString(); + } else if (HostOsInfo::isMacosHost()) { + QSettings serverSettings(QStringLiteral( + "/System/Library/CoreServices/ServerVersion.plist"), + QSettings::NativeFormat); + v = serverSettings.value(QStringLiteral("ProductBuildVersion")).toString(); + if (v.isNull()) { + QSettings systemSettings(QStringLiteral( + "/System/Library/CoreServices/SystemVersion.plist"), + QSettings::NativeFormat); + v = systemSettings.value(QStringLiteral("ProductBuildVersion")).toString(); + } + } + return v; + } + static bool isWindowsHost() { return hostOs() == HostOsWindows; } static bool isLinuxHost() { return hostOs() == HostOsLinux; } static bool isMacosHost() { return hostOs() == HostOsMacos; } @@ -112,6 +134,13 @@ public: return finalName; } + static QString stripExecutableSuffix(const QString &executable) + { + constexpr QLatin1String suffix(QBS_HOST_EXE_SUFFIX, sizeof(QBS_HOST_EXE_SUFFIX) - 1); + return !suffix.isEmpty() && executable.endsWith(suffix) + ? executable.chopped(suffix.size()) : executable; + } + static QString dynamicLibraryName(const QString &libraryBaseName) { return QLatin1String(QBS_HOST_DYNAMICLIB_PREFIX) + libraryBaseName @@ -148,69 +177,68 @@ public: } }; -std::string HostOsInfo::hostOSIdentifier() +QString HostOsInfo::hostOSIdentifier() { #if defined(__APPLE__) - return "macos"; + return QStringLiteral("macos"); #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - return "windows"; + return QStringLiteral("windows"); #elif defined(_AIX) - return "aix"; + return QStringLiteral("aix"); #elif defined(hpux) || defined(__hpux) - return "hpux"; + return QStringLiteral("hpux"); #elif defined(__sun) || defined(sun) - return "solaris"; + return QStringLiteral("solaris"); #elif defined(__linux__) || defined(__linux) - return "linux"; + return QStringLiteral("linux"); #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) - return "freebsd"; + return QStringLiteral("freebsd"); #elif defined(__NetBSD__) - return "netbsd"; + return QStringLiteral("netbsd"); #elif defined(__OpenBSD__) - return "openbsd"; + return QStringLiteral("openbsd"); #elif defined(__GNU__) - return "hurd"; + return QStringLiteral("hurd"); #elif defined(__HAIKU__) - return "haiku"; + return QStringLiteral("haiku"); #else #warning "Qbs has not been ported to this OS - see http://qbs.io/" - return ""; + return {}; #endif } -std::string HostOsInfo::hostOSArchitecture() +QString HostOsInfo::hostOSArchitecture() { const auto cpuArch = QSysInfo::currentCpuArchitecture(); if (cpuArch == QLatin1String("i386")) - return "x86"; - return cpuArch.toStdString(); + return QStringLiteral("x86"); + return cpuArch; } -std::vector<std::string> HostOsInfo::hostOSIdentifiers() +std::vector<QString> HostOsInfo::hostOSIdentifiers() { return canonicalOSIdentifiers(hostOSIdentifier()); } -std::vector<std::string> HostOsInfo::canonicalOSIdentifiers(const std::string &name) +std::vector<QString> HostOsInfo::canonicalOSIdentifiers(const QString &name) { - std::vector<std::string> list { name }; - if (contains(std::vector<std::string> {"ios-simulator"}, name)) - list << canonicalOSIdentifiers("ios"); - if (contains(std::vector<std::string> {"tvos-simulator"}, name)) - list << canonicalOSIdentifiers("tvos"); - if (contains(std::vector<std::string> {"watchos-simulator"}, name)) - list << canonicalOSIdentifiers("watchos"); - if (contains(std::vector<std::string> {"macos", "ios", "tvos", "watchos"}, name)) - list << canonicalOSIdentifiers("darwin"); - if (contains(std::vector<std::string> {"darwin", "freebsd", "netbsd", "openbsd"}, name)) - list << canonicalOSIdentifiers("bsd"); - if (contains(std::vector<std::string> {"android"}, name)) - list << canonicalOSIdentifiers("linux"); + std::vector<QString> list { name }; + if (contains({u"ios-simulator"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("ios")); + if (contains({u"tvos-simulator"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("tvos")); + if (contains({u"watchos-simulator"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("watchos")); + if (contains({u"macos", u"ios", u"tvos", u"watchos"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("darwin")); + if (contains({u"darwin", u"freebsd", u"netbsd", u"openbsd"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("bsd")); + if (contains({u"android"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("linux")); // Note: recognized non-Unix platforms include: windows, haiku, vxworks - if (contains(std::vector<std::string> { - "bsd", "aix", "hpux", "solaris", "linux", "hurd", "qnx", "integrity"}, name)) - list << canonicalOSIdentifiers("unix"); + if (contains({u"bsd", u"aix", u"hpux", u"solaris", u"linux", u"hurd", u"qnx", u"integrity"}, name)) + list << canonicalOSIdentifiers(QStringLiteral("unix")); return list; } diff --git a/src/lib/corelib/tools/id.cpp b/src/lib/corelib/tools/id.cpp index 6dd1147f2..cca755512 100644 --- a/src/lib/corelib/tools/id.cpp +++ b/src/lib/corelib/tools/id.cpp @@ -75,7 +75,7 @@ public: : n(length), str(s) { if (!n) - length = n = qstrlen(s); + length = n = int(qstrlen(s)); h = 0; while (length--) { h = (h << 4) + *s++; @@ -95,7 +95,7 @@ static bool operator==(const StringHolder &sh1, const StringHolder &sh2) } -static uint qHash(const StringHolder &sh) +QHashValueType qHash(const StringHolder &sh) { return sh.h; } @@ -269,11 +269,10 @@ Id Id::withPrefix(const char *prefix) const bool Id::operator==(const char *name) const { - const char *string = getStringFromId(m_id); - if (string && name) - return strcmp(string, name) == 0; - else - return false; + const auto string = getStringFromId(m_id); + if (!string.isNull() && name) + return strcmp(string.data(), name) == 0; + return false; } bool Id::alphabeticallyBefore(Id other) const diff --git a/src/lib/corelib/tools/id.h b/src/lib/corelib/tools/id.h index aa327833f..845ed60df 100644 --- a/src/lib/corelib/tools/id.h +++ b/src/lib/corelib/tools/id.h @@ -41,6 +41,7 @@ #define QBS_TOOLS_ID_H #include "qbs_export.h" +#include <tools/porting.h> #include <QtCore/qmetatype.h> #include <QtCore/qstring.h> @@ -84,7 +85,7 @@ private: int m_id; }; -inline uint qHash(const Id &id) { return id.uniqueIdentifier(); } +inline QHashValueType qHash(const Id &id) { return id.uniqueIdentifier(); } } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/installoptions.cpp b/src/lib/corelib/tools/installoptions.cpp index 5e112e6de..563f04a7b 100644 --- a/src/lib/corelib/tools/installoptions.cpp +++ b/src/lib/corelib/tools/installoptions.cpp @@ -68,7 +68,7 @@ public: QString effectiveInstallRoot(const InstallOptions &options, const TopLevelProject *project) { - const QString installRoot = options.installRoot(); + QString installRoot = options.installRoot(); if (!installRoot.isEmpty()) return installRoot; diff --git a/src/lib/corelib/tools/iosutils.h b/src/lib/corelib/tools/iosutils.h index 1a5faf3c3..69957a82e 100644 --- a/src/lib/corelib/tools/iosutils.h +++ b/src/lib/corelib/tools/iosutils.h @@ -45,7 +45,7 @@ #include <ostream> #if defined(_WIN32) && defined(_MSC_VER) -#include <codecvt> +#include <windows.h> #include <locale> #define QBS_RENAME_IMPL ::_wrename #define QBS_UNLINK_IMPL ::_wunlink @@ -82,8 +82,10 @@ static inline bool fwrite(const char *s, std::ostream *stream) static inline qbs_filesystem_path_string_type utf8_to_native_path(const std::string &str) { #if defined(_WIN32) && defined(_MSC_VER) - std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; - return converter.from_bytes(str); + const int size = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + std::wstring result(size, 0); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), result.data(), size); + return result; #else return str; #endif diff --git a/src/lib/corelib/tools/jsliterals.cpp b/src/lib/corelib/tools/jsliterals.cpp index 74328006c..647bdc640 100644 --- a/src/lib/corelib/tools/jsliterals.cpp +++ b/src/lib/corelib/tools/jsliterals.cpp @@ -40,8 +40,9 @@ #include "jsliterals.h" #include <tools/stringconstants.h> +#include <tools/qttools.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> namespace qbs { @@ -53,7 +54,7 @@ QString toJSLiteral(const bool b) QString toJSLiteral(const QString &str) { QString js = str; - js.replace(QRegExp(QLatin1String("([\\\\\"])")), QLatin1String("\\\\1")); + js.replace(QRegularExpression(QLatin1String("([\\\\\"])")), QLatin1String("\\\\1")); js.prepend(QLatin1Char('"')); js.append(QLatin1Char('"')); return js; @@ -75,7 +76,7 @@ QString toJSLiteral(const QVariant &val) { if (!val.isValid()) return Internal::StringConstants::undefinedValue(); - if (val.type() == QVariant::List || val.type() == QVariant::StringList) { + if (val.userType() == QMetaType::QVariantList || val.userType() == QMetaType::QStringList) { QString res; const auto list = val.toList(); for (const QVariant &child : list) { @@ -86,7 +87,7 @@ QString toJSLiteral(const QVariant &val) res.append(QLatin1Char(']')); return res; } - if (val.type() == QVariant::Map) { + if (val.userType() == QMetaType::QVariantMap) { const QVariantMap &vm = val.toMap(); QString str = QStringLiteral("{"); for (QVariantMap::const_iterator it = vm.begin(); it != vm.end(); ++it) { @@ -97,9 +98,9 @@ QString toJSLiteral(const QVariant &val) str += QLatin1Char('}'); return str; } - if (val.type() == QVariant::Bool) + if (val.userType() == QMetaType::Bool) return toJSLiteral(val.toBool()); - if (val.canConvert(QVariant::String)) + if (qVariantCanConvert(val, QMetaType::QString)) return toJSLiteral(val.toString()); return QStringLiteral("Unconvertible type %1").arg(QLatin1String(val.typeName())); } diff --git a/src/lib/corelib/tools/jsonhelper.h b/src/lib/corelib/tools/jsonhelper.h index d87802c0a..cb911c45a 100644 --- a/src/lib/corelib/tools/jsonhelper.h +++ b/src/lib/corelib/tools/jsonhelper.h @@ -40,6 +40,8 @@ #ifndef QBS_JSON_HELPER_H #define QBS_JSON_HELPER_H +#include <tools/stlutils.h> + #include <QtCore/qjsonarray.h> #include <QtCore/qjsonobject.h> #include <QtCore/qjsonvalue.h> @@ -60,10 +62,7 @@ template<> inline QString fromJson(const QJsonValue &v) { return v.toString(); } template<> inline QStringList fromJson(const QJsonValue &v) { const QJsonArray &jsonList = v.toArray(); - QStringList stringList; - std::transform(jsonList.begin(), jsonList.end(), std::back_inserter(stringList), - [](const QVariant &v) { return v.toString(); }); - return stringList; + return transformed<QStringList>(jsonList, [](const auto &v) { return v.toString(); }); } template<> inline QVariantMap fromJson(const QJsonValue &v) { return v.toObject().toVariantMap(); } template<> inline QProcessEnvironment fromJson(const QJsonValue &v) @@ -78,9 +77,9 @@ template<> inline QProcessEnvironment fromJson(const QJsonValue &v) template<typename T> inline void setValueFromJson(T &targetValue, const QJsonObject &data, const char *jsonProperty) { - const QJsonValue v = data.value(QLatin1String(jsonProperty)); - if (!v.isNull()) - targetValue = fromJson<T>(v); + const auto it = data.find(QLatin1String(jsonProperty)); + if (it != data.end()) + targetValue = fromJson<T>(*it); } } // namespace Internal diff --git a/src/lib/corelib/tools/launcherinterface.cpp b/src/lib/corelib/tools/launcherinterface.cpp index f4e80c5e2..cd877c154 100644 --- a/src/lib/corelib/tools/launcherinterface.cpp +++ b/src/lib/corelib/tools/launcherinterface.cpp @@ -61,11 +61,23 @@ namespace Internal { class LauncherProcess : public QProcess { public: - LauncherProcess(QObject *parent) : QProcess(parent) { } + LauncherProcess(QObject *parent) : QProcess(parent) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && defined(Q_OS_UNIX) + setChildProcessModifier([this] { setupChildProcess_impl(); }); +#endif + } private: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void setupChildProcess() override { + setupChildProcess_impl(); + } +#endif + + void setupChildProcess_impl() + { #ifdef Q_OS_UNIX const auto pid = static_cast<pid_t>(processId()); setpgid(pid, pid); @@ -108,9 +120,7 @@ void LauncherInterface::doStart() return; } m_process = new LauncherProcess(this); - connect(m_process, - static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), - this, &LauncherInterface::handleProcessError); + connect(m_process, &QProcess::errorOccurred, this, &LauncherInterface::handleProcessError); connect(m_process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &LauncherInterface::handleProcessFinished); @@ -130,8 +140,7 @@ void LauncherInterface::doStop() if (!m_process) return; m_process->disconnect(); - if (m_socket->isReady()) - m_socket->shutdown(); + m_socket->shutdown(); m_process->waitForFinished(3000); m_process->deleteLater(); m_process = nullptr; diff --git a/src/lib/corelib/tools/launcherpackets.h b/src/lib/corelib/tools/launcherpackets.h index 4306718fd..b3eac4320 100644 --- a/src/lib/corelib/tools/launcherpackets.h +++ b/src/lib/corelib/tools/launcherpackets.h @@ -145,7 +145,7 @@ class ProcessErrorPacket : public LauncherPacket public: ProcessErrorPacket(quintptr token); - QProcess::ProcessError error; + QProcess::ProcessError error = QProcess::UnknownError; QString errorString; private: diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp index 4373b10b8..7d4788ee3 100644 --- a/src/lib/corelib/tools/launchersocket.cpp +++ b/src/lib/corelib/tools/launchersocket.cpp @@ -68,21 +68,21 @@ void LauncherSocket::sendData(const QByteArray &data) void LauncherSocket::shutdown() { - QBS_ASSERT(m_socket, return); - m_socket->disconnect(); - m_socket->write(ShutdownPacket().serialize()); - m_socket->waitForBytesWritten(1000); - m_socket->deleteLater(); - m_socket = nullptr; + const auto socket = m_socket.exchange(nullptr); + if (!socket) + return; + socket->disconnect(); + socket->write(ShutdownPacket().serialize()); + socket->waitForBytesWritten(1000); + socket->deleteLater(); } void LauncherSocket::setSocket(QLocalSocket *socket) { QBS_ASSERT(!m_socket, return); - m_socket = socket; + m_socket.store(socket); m_packetParser.setDevice(m_socket); - connect(m_socket, - static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error), + connect(m_socket, &QLocalSocket::errorOccurred, this, &LauncherSocket::handleSocketError); connect(m_socket, &QLocalSocket::readyRead, this, &LauncherSocket::handleSocketDataAvailable); @@ -93,8 +93,9 @@ void LauncherSocket::setSocket(QLocalSocket *socket) void LauncherSocket::handleSocketError() { - if (m_socket->error() != QLocalSocket::PeerClosedError) - handleError(Tr::tr("Socket error: %1").arg(m_socket->errorString())); + auto socket = m_socket.load(); + if (socket->error() != QLocalSocket::PeerClosedError) + handleError(Tr::tr("Socket error: %1").arg(socket->errorString())); } void LauncherSocket::handleSocketDataAvailable() @@ -127,18 +128,19 @@ void LauncherSocket::handleSocketDisconnected() void LauncherSocket::handleError(const QString &error) { - m_socket->disconnect(); - m_socket->deleteLater(); - m_socket = nullptr; + const auto socket = m_socket.exchange(nullptr); + socket->disconnect(); + socket->deleteLater(); emit errorOccurred(error); } void LauncherSocket::handleRequests() { - QBS_ASSERT(isReady(), return); + const auto socket = m_socket.load(); + QBS_ASSERT(socket, return); std::lock_guard<std::mutex> locker(m_requestsMutex); - for (const QByteArray &request : qAsConst(m_requests)) - m_socket->write(request); + for (const QByteArray &request : std::as_const(m_requests)) + socket->write(request); m_requests.clear(); } diff --git a/src/lib/corelib/tools/launchersocket.h b/src/lib/corelib/tools/launchersocket.h index a9a1af800..2eb2c3f63 100644 --- a/src/lib/corelib/tools/launchersocket.h +++ b/src/lib/corelib/tools/launchersocket.h @@ -60,7 +60,7 @@ class LauncherSocket : public QObject Q_OBJECT friend class LauncherInterface; public: - bool isReady() const { return m_socket; } + bool isReady() const { return m_socket.load(); } void sendData(const QByteArray &data); signals: @@ -81,7 +81,7 @@ private: void handleError(const QString &error); void handleRequests(); - QLocalSocket *m_socket = nullptr; + std::atomic<QLocalSocket *> m_socket{nullptr}; PacketParser m_packetParser; std::vector<QByteArray> m_requests; std::mutex m_requestsMutex; diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp index cffec85b2..77b83023a 100644 --- a/src/lib/corelib/tools/msvcinfo.cpp +++ b/src/lib/corelib/tools/msvcinfo.cpp @@ -38,14 +38,21 @@ ****************************************************************************/ #include "msvcinfo.h" +#include "visualstudioversioninfo.h" +#include <logging/logger.h> #include <tools/error.h> #include <tools/profile.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qbytearray.h> #include <QtCore/qdir.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> #include <QtCore/qprocess.h> +#include <QtCore/qsettings.h> #include <QtCore/qstringlist.h> #include <QtCore/qtemporaryfile.h> @@ -54,6 +61,7 @@ #endif #include <algorithm> +#include <memory> #include <mutex> using namespace qbs; @@ -129,16 +137,18 @@ public: #ifdef Q_OS_WIN static QStringList parseCommandLine(const QString &commandLine) { - QStringList list; - const auto buf = new wchar_t[commandLine.size() + 1]; - buf[commandLine.toWCharArray(buf)] = 0; + const auto buf = std::make_unique<wchar_t[]>(size_t(commandLine.size()) + 1); + buf[size_t(commandLine.toWCharArray(buf.get()))] = 0; int argCount = 0; - LPWSTR *args = CommandLineToArgvW(buf, &argCount); + const auto argsDeleter = [](LPWSTR *p){ LocalFree(p); }; + const auto args = std::unique_ptr<LPWSTR[], decltype(argsDeleter)>( + CommandLineToArgvW(buf.get(), &argCount), argsDeleter); if (!args) throw ErrorInfo(mkStr("Could not parse command line arguments: ") + commandLine); + QStringList list; + list.reserve(argCount); for (int i = 0; i < argCount; ++i) - list.push_back(QString::fromWCharArray(args[i])); - delete[] buf; + list.push_back(QString::fromWCharArray(args[size_t(i)])); return list; } #endif @@ -163,9 +173,10 @@ static QVariantMap getMsvcDefines(const QString &compilerFilePath, QStringList out = QString::fromLocal8Bit(runProcess(compilerFilePath, QStringList() << QStringLiteral("/nologo") << backendSwitch - << QString::fromWCharArray(_wgetenv(L"COMSPEC")) + << qEnvironmentVariable("COMSPEC") << QStringLiteral("/c") << languageSwitch + << QStringLiteral("/Zs") << QStringLiteral("NUL"), compilerEnv, true, commands)).split(QLatin1Char('\n')); @@ -206,30 +217,32 @@ static QVariantMap getMsvcDefines(const QString &compilerFilePath, static QVariantMap getClangClDefines( const QString &compilerFilePath, const QProcessEnvironment &compilerEnv, - MSVC::CompilerLanguage language) + MSVC::CompilerLanguage language, + const QString &arch) { #ifdef Q_OS_WIN QFileInfo clInfo(compilerFilePath); - QFileInfo clangInfo(clInfo.absolutePath() + QLatin1String("/clang.exe")); + QFileInfo clangInfo(clInfo.absolutePath() + QLatin1String("/clang-cl.exe")); if (!clangInfo.exists()) throw ErrorInfo(QStringLiteral("%1 does not exist").arg(clangInfo.absoluteFilePath())); - QString languageSwitch; - switch (language) { - case MSVC::CLanguage: - languageSwitch = QStringLiteral("c"); - break; - case MSVC::CPlusPlusLanguage: - languageSwitch = QStringLiteral("c++"); - break; - } QStringList args = { - QStringLiteral("-dM"), - QStringLiteral("-E"), - QStringLiteral("-x"), - languageSwitch, - QStringLiteral("NUL"), + QStringLiteral("/d1PP"), // dump macros + QStringLiteral("/E") // preprocess to stdout }; + + if (language == MSVC::CLanguage) + args.append(QStringLiteral("/TC")); + else if (language == MSVC::CPlusPlusLanguage) + args.append(QStringLiteral("/TP")); + + if (arch == QLatin1String("x86")) + args.append(QStringLiteral("-m32")); + else if (arch == QLatin1String("x86_64")) + args.append(QStringLiteral("-m64")); + + args.append(QStringLiteral("NUL")); // filename + const auto lines = QString::fromLocal8Bit( runProcess( clangInfo.absoluteFilePath(), @@ -239,10 +252,8 @@ static QVariantMap getClangClDefines( QVariantMap result; for (const auto &line: lines) { static const auto defineString = QLatin1String("#define "); - if (!line.startsWith(defineString)) { - throw ErrorInfo(QStringLiteral("Unexpected compiler frontend output: ") - + lines.join(QLatin1Char('\n'))); - } + if (!line.startsWith(defineString)) + continue; QStringView view(line.data() + defineString.size()); const auto it = std::find(view.begin(), view.end(), QLatin1Char(' ')); if (it == view.end()) { @@ -253,15 +264,240 @@ static QVariantMap getClangClDefines( QStringView value(it + 1, view.end()); result.insert(key.toString(), value.isEmpty() ? QVariant() : QVariant(value.toString())); } + if (result.isEmpty()) { + throw ErrorInfo(QStringLiteral("Cannot determine macroses from compiler frontend output: ") + + lines.join(QLatin1Char('\n'))); + } return result; #else Q_UNUSED(compilerFilePath); Q_UNUSED(compilerEnv); Q_UNUSED(language); + Q_UNUSED(arch); + return {}; +#endif +} + +static QString formatVswhereOutput(const QString &out, const QString &err) +{ + QString ret; + if (!out.isEmpty()) { + ret.append(Tr::tr("stdout")).append(QLatin1String(":\n")); + const auto lines = out.split(QLatin1Char('\n')); + for (const QString &line : lines) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + if (!err.isEmpty()) { + ret.append(Tr::tr("stderr")).append(QLatin1String(":\n")); + const auto lines = err.split(QLatin1Char('\n')); + for (const QString &line : lines) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + return ret; +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else return {}; #endif } +static QString vswhereFilePath() +{ + static const std::vector<const char *> envVarCandidates{"ProgramFiles", "ProgramFiles(x86)"}; + for (const char * const envVar : envVarCandidates) { + const QString value = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); + QString cmd = value + + QStringLiteral("/Microsoft Visual Studio/Installer/vswhere.exe"); + if (QFileInfo(cmd).exists()) + return cmd; + } + return {}; +} + +enum class ProductType { VisualStudio, BuildTools }; +static std::vector<MSVCInstallInfo> retrieveInstancesFromVSWhere( + ProductType productType, Logger &logger) +{ + std::vector<MSVCInstallInfo> result; + const QString cmd = vswhereFilePath(); + if (cmd.isEmpty()) + return result; + QProcess vsWhere; + QStringList args = productType == ProductType::VisualStudio + ? QStringList({QStringLiteral("-all"), QStringLiteral("-legacy"), + QStringLiteral("-prerelease")}) + : QStringList({QStringLiteral("-products"), + QStringLiteral("Microsoft.VisualStudio.Product.BuildTools")}); + args << QStringLiteral("-format") << QStringLiteral("json") << QStringLiteral("-utf8"); + vsWhere.start(cmd, args); + if (!vsWhere.waitForStarted(-1)) + return result; + if (!vsWhere.waitForFinished(-1)) { + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(": ")) + .append(vsWhere.errorString()); + return result; + } + if (vsWhere.exitCode() != 0) { + const QString stdOut = QString::fromLocal8Bit(vsWhere.readAllStandardOutput()); + const QString stdErr = QString::fromLocal8Bit(vsWhere.readAllStandardError()); + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(".\n")) + .append(formatVswhereOutput(stdOut, stdErr)); + return result; + } + QJsonParseError parseError{}; + QJsonDocument jsonOutput = QJsonDocument::fromJson(vsWhere.readAllStandardOutput(), + &parseError); + if (parseError.error != QJsonParseError::NoError) { + logger.qbsWarning() << Tr::tr("The vswhere tool produced invalid JSON output: %1") + .arg(parseError.errorString()); + return result; + } + const auto jsonArray = jsonOutput.array(); + for (const auto &value : jsonArray) { + const QJsonObject o = value.toObject(); + MSVCInstallInfo info; + info.version = o.value(QStringLiteral("installationVersion")).toString(); + if (productType == ProductType::BuildTools) { + // For build tools, the version is e.g. "15.8.28010.2036", rather than "15.0". + const int dotIndex = info.version.indexOf(QLatin1Char('.')); + if (dotIndex != -1) + info.version = info.version.left(dotIndex); + } + info.installDir = o.value(QStringLiteral("installationPath")).toString(); + if (!info.version.isEmpty() && !info.installDir.isEmpty()) + result.push_back(info); + } + return result; +} + +static std::vector<MSVCInstallInfo> installedMSVCsFromVsWhere(Logger &logger) +{ + const std::vector<MSVCInstallInfo> vsInstallations + = retrieveInstancesFromVSWhere(ProductType::VisualStudio, logger); + const std::vector<MSVCInstallInfo> buildToolInstallations + = retrieveInstancesFromVSWhere(ProductType::BuildTools, logger); + std::vector<MSVCInstallInfo> all; + std::copy(vsInstallations.begin(), vsInstallations.end(), std::back_inserter(all)); + std::copy(buildToolInstallations.begin(), buildToolInstallations.end(), + std::back_inserter(all)); + return all; +} + +static std::vector<MSVCInstallInfo> installedMSVCsFromRegistry() +{ + std::vector<MSVCInstallInfo> result; + + // Detect Visual Studio + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\SxS\\VS7"), + QSettings::NativeFormat); + const auto vsNames = vsRegistry.childKeys(); + for (const QString &vsName : vsNames) { + MSVCInstallInfo entry; + entry.version = vsName; + entry.installDir = vsRegistry.value(vsName).toString(); + result.push_back(entry); + } + + // Detect Visual C++ Build Tools + QSettings vcbtRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualCppBuildTools"), + QSettings::NativeFormat); + const QStringList &vcbtRegistryChildGroups = vcbtRegistry.childGroups(); + for (const QString &childGroup : vcbtRegistryChildGroups) { + vcbtRegistry.beginGroup(childGroup); + bool ok; + int installed = vcbtRegistry.value(QStringLiteral("Installed")).toInt(&ok); + if (ok && installed) { + MSVCInstallInfo entry; + entry.version = childGroup; + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\") + childGroup + + QStringLiteral("\\Setup\\VC"), + QSettings::NativeFormat); + entry.installDir = vsRegistry.value(QStringLiteral("ProductDir")).toString(); + result.push_back(entry); + } + vcbtRegistry.endGroup(); + } + + return result; +} + +/* + Returns the list of compilers present in all MSVC installations + (Visual Studios or Build Tools) without the architecture, e.g. + [VC\Tools\MSVC\14.16.27023, VC\Tools\MSVC\14.14.26428, ...] +*/ +static std::vector<MSVC> installedCompilersHelper(Logger &logger) +{ + std::vector<MSVC> msvcs; + std::vector<MSVCInstallInfo> installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + installInfos = installedMSVCsFromRegistry(); + for (const MSVCInstallInfo &installInfo : installInfos) { + MSVC msvc; + msvc.internalVsVersion = Version::fromString(installInfo.version, true); + if (!msvc.internalVsVersion.isValid()) + continue; + + QDir vsInstallDir(installInfo.installDir); + msvc.vsInstallPath = vsInstallDir.absolutePath(); + if (vsInstallDir.dirName() != QStringLiteral("VC") + && !vsInstallDir.cd(QStringLiteral("VC"))) { + continue; + } + + msvc.version = QString::number(Internal::VisualStudioVersionInfo( + msvc.internalVsVersion).marketingVersion()); + if (msvc.version.isEmpty()) { + logger.qbsWarning() + << Tr::tr("Unknown MSVC version %1 found.").arg(installInfo.version); + continue; + } + + if (msvc.internalVsVersion.majorVersion() < 15) { + QDir vcInstallDir = vsInstallDir; + if (!vcInstallDir.cd(QStringLiteral("bin"))) + continue; + msvc.vcInstallPath = vcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } else { + QDir vcInstallDir = vsInstallDir; + vcInstallDir.cd(QStringLiteral("Tools/MSVC")); + const auto vcVersionStrs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + std::vector<Version> vcVersions; + vcVersions.reserve(vcVersionStrs.size()); + for (const QString &vcVersionStr : vcVersionStrs) { + const Version vcVersion = Version::fromString(vcVersionStr); + if (!vcVersion.isValid()) + continue; + vcVersions.push_back(vcVersion); + } + // sort the versions so the new one comes first + std::sort(vcVersions.begin(), vcVersions.end(), std::greater<>()); + + for (const Version &vcVersion : vcVersions) { + QDir specificVcInstallDir = vcInstallDir; + if (!specificVcInstallDir.cd(vcVersion.toString()) + || !specificVcInstallDir.cd(QStringLiteral("bin"))) { + continue; + } + msvc.vcInstallPath = specificVcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } + } + } + return msvcs; +} + void MSVC::init() { determineCompilerVersion(); @@ -274,12 +510,55 @@ void MSVC::init() QString MSVC::architectureFromClPath(const QString &clPath) { const auto parentDir = QFileInfo(clPath).absolutePath(); - const auto parentDirName = QFileInfo(parentDir).fileName().toLower(); + auto parentDirName = QFileInfo(parentDir).fileName().toLower(); + // can be the case when cl.exe is present within the Windows SDK installation... but can it? if (parentDirName == QLatin1String("bin")) return QStringLiteral("x86"); return parentDirName; } +QString MSVC::vcVariablesVersionFromBinPath(const QString &binPath) +{ + const auto binDirName = QFileInfo(binPath).fileName().toLower(); + // the case when cl.exe is present within the Windows SDK installation + if (binDirName == QLatin1String("bin")) + return {}; + // binPath is something like + // Microsoft Visual Studio 14.0/VC/bin/amd64_x86 + // or + // Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.28.29910/bin/Hostx64/x64 + QDir dir(binPath); + dir.cdUp(); + // older Visual Studios do not support multiple compiler versions + if (dir.dirName().toLower() == QLatin1String("bin")) + return {}; + dir.cdUp(); + dir.cdUp(); + return dir.dirName(); +} + +QString MSVC::canonicalArchitecture(const QString &arch) +{ + if (arch == QLatin1String("x64") || arch == QLatin1String("amd64")) + return QStringLiteral("x86_64"); + return arch; +} + +std::pair<QString, QString> MSVC::getHostTargetArchPair(const QString &arch) +{ + QString hostArch; + QString targetArch; + const int index = arch.indexOf(QLatin1Char('_')); + if (index != -1) { + hostArch = arch.mid(0, index); + targetArch = arch.mid(index); + } else { + hostArch = arch; + targetArch = arch; + } + return {canonicalArchitecture(hostArch), canonicalArchitecture(targetArch)}; +} + QString MSVC::binPathForArchitecture(const QString &arch) const { QString archSubDir; @@ -301,10 +580,106 @@ QVariantMap MSVC::compilerDefines(const QString &compilerFilePath, { const auto compilerName = QFileInfo(compilerFilePath).fileName().toLower(); if (compilerName == QLatin1String("clang-cl.exe")) - return getClangClDefines(compilerFilePath, environment, language); + return getClangClDefines(compilerFilePath, environment, language, architecture); return getMsvcDefines(compilerFilePath, environment, language); } +std::vector<MSVCArchInfo> MSVC::findSupportedArchitectures(const MSVC &msvc) +{ + std::vector<MSVCArchInfo> result; + auto addResult = [&result](const MSVCArchInfo &ai) { + if (QFile::exists(ai.binPath + QLatin1String("/cl.exe"))) + result.push_back(ai); + }; + if (msvc.internalVsVersion.majorVersion() < 15) { + static const QStringList knownArchitectures = QStringList() + << QStringLiteral("x86") + << QStringLiteral("amd64_x86") + << QStringLiteral("amd64") + << QStringLiteral("x86_amd64") + << QStringLiteral("ia64") + << QStringLiteral("x86_ia64") + << QStringLiteral("x86_arm") + << QStringLiteral("amd64_arm"); + for (const QString &knownArchitecture : knownArchitectures) { + MSVCArchInfo ai; + ai.arch = knownArchitecture; + ai.binPath = msvc.binPathForArchitecture(knownArchitecture); + addResult(ai); + } + } else { + QDir vcInstallDir(msvc.vcInstallPath); + const auto hostArchs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &hostArch : hostArchs) { + QDir subdir = vcInstallDir; + if (!subdir.cd(hostArch)) + continue; + const QString shortHostArch = hostArch.mid(4).toLower(); + const auto archs = subdir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &arch : archs) { + MSVCArchInfo ai; + ai.binPath = subdir.absoluteFilePath(arch); + if (shortHostArch == arch) + ai.arch = arch; + else + ai.arch = shortHostArch + QLatin1Char('_') + arch; + addResult(ai); + } + } + } + return result; +} + +QVariantMap MSVC::toVariantMap() const +{ + return { + {QStringLiteral("version"), version}, + {QStringLiteral("internalVsVersion"), internalVsVersion.toString()}, + {QStringLiteral("vsInstallPath"), vsInstallPath}, + {QStringLiteral("vcInstallPath"), vcInstallPath}, + {QStringLiteral("binPath"), binPath}, + {QStringLiteral("architecture"), architecture}, + }; +} + +/*! + \internal + Returns the list of all compilers present in all MSVC installations + separated by host/target arch, e.g. + [ + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x86, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm64, + VC\Tools\MSVC\14.16.27023\bin\Hostx86\x64, + ... + ] + \note that MSVC.architecture can be either "x64" or "amd64" (depending on the MSVC version) + in case of 64-bit platform (but we use the "x86_64" name...) +*/ +std::vector<MSVC> MSVC::installedCompilers(Logger &logger) +{ + std::vector<MSVC> msvcs; + const auto instMsvcs = installedCompilersHelper(logger); + for (const MSVC &msvc : instMsvcs) { + if (msvc.internalVsVersion.majorVersion() < 15) { + // Check existence of various install scripts + const QString vcvars32bat = msvc.vcInstallPath + QLatin1String("/vcvars32.bat"); + if (!QFileInfo(vcvars32bat).isFile()) + continue; + } + + const auto ais = findSupportedArchitectures(msvc); + transform(ais, msvcs, [&msvc](const auto &ai) { + MSVC specificMSVC = msvc; + specificMSVC.architecture = ai.arch; + specificMSVC.binPath = ai.binPath; + return specificMSVC; + }); + } + return msvcs; +} + void MSVC::determineCompilerVersion() { QString cppFilePath; @@ -332,3 +707,24 @@ void MSVC::determineCompilerVersion() compilerVersion = Version(versionStr.mid(0, 2).toInt(), versionStr.mid(2, 2).toInt(), versionStr.mid(4).toInt()); } + +QString MSVCInstallInfo::findVcvarsallBat() const +{ + static const auto vcvarsall2017 = QStringLiteral("VC/Auxiliary/Build/vcvarsall.bat"); + // 2015, 2013 and 2012 + static const auto vcvarsallOld = QStringLiteral("VC/vcvarsall.bat"); + QDir dir(installDir); + if (dir.exists(vcvarsall2017)) + return dir.absoluteFilePath(vcvarsall2017); + if (dir.exists(vcvarsallOld)) + return dir.absoluteFilePath(vcvarsallOld); + return {}; +} + +std::vector<MSVCInstallInfo> MSVCInstallInfo::installedMSVCs(Logger &logger) +{ + auto installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + return installedMSVCsFromRegistry(); + return installInfos; +} diff --git a/src/lib/corelib/tools/msvcinfo.h b/src/lib/corelib/tools/msvcinfo.h index 171afeb29..efaf0b6c2 100644 --- a/src/lib/corelib/tools/msvcinfo.h +++ b/src/lib/corelib/tools/msvcinfo.h @@ -53,6 +53,14 @@ namespace qbs { namespace Internal { +class Logger; + +struct MSVCArchInfo +{ + QString arch; + QString binPath; +}; + /** * Represents one MSVC installation for one specific target architecture. * There are potentially multiple MSVCs in one Visual Studio installation. @@ -73,12 +81,14 @@ public: QString binPath; QString pathPrefix; QString architecture; + QString sdkVersion; QProcessEnvironment environment; MSVC() = default; - MSVC(const QString &clPath, QString arch): - architecture(std::move(arch)) + MSVC(const QString &clPath, QString arch, QString sdkVersion = {}): + architecture(std::move(arch)), + sdkVersion(std::move(sdkVersion)) { QDir parentDir = QFileInfo(clPath).dir(); binPath = parentDir.absolutePath(); @@ -90,11 +100,20 @@ public: QBS_EXPORT void init(); QBS_EXPORT static QString architectureFromClPath(const QString &clPath); + QBS_EXPORT static QString vcVariablesVersionFromBinPath(const QString &binPath); + QBS_EXPORT static QString canonicalArchitecture(const QString &arch); + QBS_EXPORT static std::pair<QString, QString> getHostTargetArchPair(const QString &arch); QBS_EXPORT QString binPathForArchitecture(const QString &arch) const; QBS_EXPORT QString clPathForArchitecture(const QString &arch) const; QBS_EXPORT QVariantMap compilerDefines(const QString &compilerFilePath, CompilerLanguage language) const; + QBS_EXPORT static std::vector<MSVCArchInfo> findSupportedArchitectures(const MSVC &msvc); + + QBS_EXPORT QVariantMap toVariantMap() const; + + QBS_EXPORT static std::vector<MSVC> installedCompilers(Logger &logger); + private: void determineCompilerVersion(); }; @@ -110,6 +129,16 @@ public: } }; +struct QBS_EXPORT MSVCInstallInfo +{ + QString version; + QString installDir; + + QString findVcvarsallBat() const; + + static std::vector<MSVCInstallInfo> installedMSVCs(Logger &logger); +}; + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/scriptclasspropertyiterator.h b/src/lib/corelib/tools/mutexdata.h index f6154f993..160f6dd26 100644 --- a/src/lib/corelib/buildgraph/scriptclasspropertyiterator.h +++ b/src/lib/corelib/tools/mutexdata.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2023 Ivan Komissarov (abbapoh@gmail.com) ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -37,74 +37,68 @@ ** ****************************************************************************/ -#ifndef QBS_SCRIPTCLASSPROPERTYITERATOR_H -#define QBS_SCRIPTCLASSPROPERTYITERATOR_H +#pragma once -#include <tools/qbsassert.h> - -#include <QtCore/qmap.h> -#include <QtCore/qstring.h> -#include <QtScript/qscriptclasspropertyiterator.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptstring.h> - -#include <vector> +#include <shared_mutex> namespace qbs { namespace Internal { -class ScriptClassPropertyIterator : public QScriptClassPropertyIterator +// adapted version of https://github.com/dragazo/rustex/blob/master/rustex.h + +// a data with a mutually exclusive access +template<typename DataType, typename MutexType = std::shared_mutex> +class MutexData { public: - ScriptClassPropertyIterator(const QScriptValue &object, const QVariantMap &properties, - const std::vector<QString> &additionalProperties) - : QScriptClassPropertyIterator(object), - m_it(properties), - m_additionalProperties(additionalProperties) + template<typename T, template<typename> typename LockType> + class ReferenceGuard { - } + friend class MutexData; + template<typename U> + ReferenceGuard(U &&data) noexcept + : m_ptr(std::addressof(data.m_data)) + , m_lock(data.m_mutex) + {} + public: + ReferenceGuard(const ReferenceGuard &) = delete; + ReferenceGuard(ReferenceGuard &&) = default; + ReferenceGuard &operator=(const ReferenceGuard &) = delete; + ReferenceGuard &operator=(ReferenceGuard &&) = default; -private: - bool hasNext() const override - { - return m_it.hasNext() || m_index < int(m_additionalProperties.size()) - 1; - } - bool hasPrevious() const override { return m_index > -1 || m_it.hasPrevious(); } - void toFront() override { m_it.toFront(); m_index = -1; } - void toBack() override { m_it.toBack(); m_index = int(m_additionalProperties.size()) - 1; } + T &get() const noexcept { return *m_ptr; } + T &data() const noexcept { return *m_ptr; } + operator T &() const noexcept { return *m_ptr; } - void next() override - { - QBS_ASSERT(hasNext(), return); - if (m_it.hasNext()) - m_it.next(); - else - ++m_index; - } + private: + T *m_ptr; + LockType<MutexType> m_lock; + }; - void previous() override - { - QBS_ASSERT(hasPrevious(), return); - if (m_index >= 0) - --m_index; - if (m_index == -1) - m_it.previous(); - } + using UniqueMutableGuard = ReferenceGuard<DataType, std::unique_lock>; + using UniqueConstGuard = ReferenceGuard<const DataType, std::unique_lock>; + using SharedGuard = ReferenceGuard<const DataType, std::shared_lock>; - QScriptString name() const override - { - const QString theName = m_index >= 0 && m_index < int(m_additionalProperties.size()) - ? m_additionalProperties.at(m_index) - : m_it.key(); - return object().engine()->toStringHandle(theName); - } + template< + typename ...Args, + std::enable_if_t<std::is_constructible_v<DataType, Args...>, int> = 0 + > + explicit MutexData(Args &&...args) : m_data(std::forward<Args>(args)...) {} + + [[nodiscard]] UniqueMutableGuard lock() noexcept { return UniqueMutableGuard{*this}; } + [[nodiscard]] UniqueConstGuard lock() const noexcept { return UniqueConstGuard{*this}; } - QMapIterator<QString, QVariant> m_it; - const std::vector<QString> m_additionalProperties; - int m_index = -1; + template< + typename U = MutexType, + std::enable_if_t<std::is_same_v<U, std::shared_mutex>, int> = 0 + > + [[nodiscard]] SharedGuard lock_shared() const noexcept + { return SharedGuard{*this}; } + +private: + DataType m_data; + mutable MutexType m_mutex; }; -} // namespace Internal } // namespace qbs - -#endif // QBS_SCRIPTCLASSPROPERTYITERATOR_H +} // namespace Internal diff --git a/src/lib/corelib/tools/pathutils.h b/src/lib/corelib/tools/pathutils.h index a2fad9aa4..55dbc1c6d 100644 --- a/src/lib/corelib/tools/pathutils.h +++ b/src/lib/corelib/tools/pathutils.h @@ -46,7 +46,7 @@ namespace qbs { namespace Internal { -class QBS_EXPORT PathUtils +class PathUtils { public: static QString toNativeSeparators(const QString &s, diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp index c30d16518..0e545377a 100644 --- a/src/lib/corelib/tools/persistence.cpp +++ b/src/lib/corelib/tools/persistence.cpp @@ -48,7 +48,7 @@ namespace qbs { namespace Internal { -static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-127"; +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-133"; NoBuildGraphError::NoBuildGraphError(const QString &filePath) : ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.") @@ -140,7 +140,12 @@ void PersistentPool::finalizeWriteStream() void PersistentPool::storeVariant(const QVariant &variant) { - const auto type = static_cast<quint32>(variant.type()); + if (variant.isNull()) { + m_stream << quint32(QMetaType::User); + m_stream << variant; + return; + } + const auto type = static_cast<quint32>(variant.userType()); m_stream << type; switch (type) { case QMetaType::QString: @@ -206,7 +211,7 @@ void PersistentPool::doLoadValue(QStringList &l) void PersistentPool::doLoadValue(QProcessEnvironment &env) { - const QStringList keys = load<QStringList>(); + const auto keys = load<QStringList>(); for (const QString &key : keys) env.insert(key, load<QString>()); } @@ -218,7 +223,7 @@ void PersistentPool::doStoreValue(const QString &s) void PersistentPool::doStoreValue(const QStringList &l) { - m_stream << l.size(); + m_stream << int(l.size()); for (const QString &s : l) store(s); } @@ -231,8 +236,5 @@ void PersistentPool::doStoreValue(const QProcessEnvironment &env) store(env.value(key)); } -const PersistentPool::PersistentObjectId PersistentPool::ValueNotFoundId; -const PersistentPool::PersistentObjectId PersistentPool::EmptyValueId; - } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/persistence.h b/src/lib/corelib/tools/persistence.h index e00310321..86365c993 100644 --- a/src/lib/corelib/tools/persistence.h +++ b/src/lib/corelib/tools/persistence.h @@ -48,8 +48,9 @@ #include <QtCore/qdatastream.h> #include <QtCore/qflags.h> #include <QtCore/qprocess.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> #include <memory> @@ -81,16 +82,14 @@ public: QVariantMap projectConfig; }; - template<typename T, typename ...Types> void store(const T &value, const Types &...args) + template<typename ...Types> void store(const Types &...args) { - PPHelper<T>::store(value, this); - store(args...); + (... , PPHelper<Types>::store(args, this)); } - template<typename T, typename ...Types> void load(T &value, Types &...args) + template<typename ...Types> void load(Types &...args) { - PPHelper<T>::load(value, this); - load(args...); + (... , PPHelper<Types>::load(args, this)); } template<typename T> T load() { T tmp; @@ -99,30 +98,17 @@ public: } enum OpType { Store, Load }; - template<OpType type, typename T, typename ...Types> struct OpTypeHelper { }; - template<typename T, typename ...Types> struct OpTypeHelper<Store, T, Types...> - { - static void serializationOp(PersistentPool *pool, const T &value, const Types &...args) - { - pool->store(value, args...); - } - }; - template<typename T, typename ...Types> struct OpTypeHelper<Load, T, Types...> + template<OpType type, typename ...Types> void serializationOp(const Types &...args) { - static void serializationOp(PersistentPool *pool, T &value, Types &...args) - { - pool->load(value, args...); - } - }; - template<OpType type, typename T, typename ...Types> void serializationOp(const T &value, - const Types &...args) - { - OpTypeHelper<type, T, Types...>::serializationOp(this, value, args...); + static_assert(type == Store); + store(args...); } - template<OpType type, typename T, typename ...Types> void serializationOp(T &value, - Types &...args) + template<OpType type, typename ...Types> void serializationOp(Types &...args) { - OpTypeHelper<type, T, Types...>::serializationOp(this, value, args...); + if constexpr(type == Store) + store(args...); + else + load(args...); } void load(const QString &filePath); @@ -159,12 +145,9 @@ private: template<typename T> QHash<T, PersistentObjectId> &idMap(); template<typename T> PersistentObjectId &lastStoredId(); - // Recursion termination - void store() {} - void load() {} - - static const PersistentObjectId ValueNotFoundId = -1; - static const PersistentObjectId EmptyValueId = -2; + static const inline PersistentObjectId ValueNotFoundId = -1; + static const inline PersistentObjectId EmptyValueId = -2; + static const inline PersistentObjectId NullValueId = -3; std::unique_ptr<QIODevice> m_file; QDataStream m_stream; @@ -289,8 +272,13 @@ template<typename T> inline T PersistentPool::idLoadValue() { int id; m_stream >> id; - if (id == EmptyValueId) + if (id == NullValueId) + return T(); + if (id == EmptyValueId) { + if constexpr (std::is_same_v<T, QString>) + return QString(0, QChar()); return T(); + } QBS_CHECK(id >= 0); if (id >= static_cast<int>(idStorage<T>().size())) { T value; @@ -305,6 +293,12 @@ template<typename T> inline T PersistentPool::idLoadValue() template<typename T> void PersistentPool::idStoreValue(const T &value) { + if constexpr (std::is_same_v<T, QString>) { + if (value.isNull()) { + m_stream << NullValueId; + return; + } + } if (value.isEmpty()) { m_stream << EmptyValueId; return; @@ -340,8 +334,8 @@ struct PPHelper /***** Specializations of Helper class *****/ template<typename T> -struct PPHelper<T, std::enable_if_t<std::is_member_function_pointer< - decltype(&T::template completeSerializationOp<PersistentPool::Load>)>::value>> +struct PPHelper<T, std::enable_if_t<std::is_member_function_pointer_v< + decltype(&T::template completeSerializationOp<PersistentPool::Load>)>>> { static void store(const T &value, PersistentPool *pool) { @@ -353,7 +347,7 @@ struct PPHelper<T, std::enable_if_t<std::is_member_function_pointer< } }; -template<typename T> struct PPHelper<T, std::enable_if_t<std::is_integral<T>::value>> +template<typename T> struct PPHelper<T, std::enable_if_t<std::is_integral_v<T>>> { static void store(const T &value, PersistentPool *pool) { pool->m_stream << value; } static void load(T &value, PersistentPool *pool) { pool->m_stream >> value; } @@ -370,7 +364,7 @@ template<> struct PPHelper<long> } }; -template<typename T> struct PPHelper<T, std::enable_if_t<std::is_enum<T>::value>> +template<typename T> struct PPHelper<T, std::enable_if_t<std::is_enum_v<T>>> { using U = std::underlying_type_t<T>; static void store(const T &value, PersistentPool *pool) @@ -413,8 +407,8 @@ template<typename T> struct PPHelper<T *> static void load(T* &value, PersistentPool *pool) { value = pool->idLoad<T>(); } }; -template<typename T> struct PPHelper<T, std::enable_if_t<std::is_same<T, QString>::value - || std::is_same<T, QStringList>::value || std::is_same<T, QProcessEnvironment>::value>> +template<typename T> struct PPHelper<T, std::enable_if_t<std::is_same_v<T, QString> + || std::is_same_v<T, QStringList> || std::is_same_v<T, QProcessEnvironment>>> { static void store(const T &v, PersistentPool *pool) { pool->idStoreValue(v); } static void load(T &v, PersistentPool *pool) { v = pool->idLoadValue<T>(); } @@ -426,10 +420,16 @@ template<> struct PPHelper<QVariant> static void load(QVariant &v, PersistentPool *pool) { v = pool->loadVariant(); } }; -template<> struct PPHelper<QRegExp> +template<> struct PPHelper<QRegularExpression> { - static void store(const QRegExp &re, PersistentPool *pool) { pool->store(re.pattern()); } - static void load(QRegExp &re, PersistentPool *pool) { re.setPattern(pool->load<QString>()); } + static void store(const QRegularExpression &re, PersistentPool *pool) + { + pool->store(re.pattern()); + } + static void load(QRegularExpression &re, PersistentPool *pool) + { + re.setPattern(pool->load<QString>()); + } }; template<typename T, typename U> struct PPHelper<std::pair<T, U>> @@ -446,6 +446,33 @@ template<typename T, typename U> struct PPHelper<std::pair<T, U>> } }; +template<typename... Args> struct PPHelper<std::tuple<Args...>> +{ + template<std::size_t... Ns> + static void storeHelper( + std::index_sequence<Ns...>, const std::tuple<Args...> &tuple, PersistentPool *pool) + { + (pool->store(std::get<Ns>(tuple)), ...); + } + + static void store(const std::tuple<Args...> &tuple, PersistentPool *pool) + { + storeHelper(std::make_index_sequence<sizeof...(Args)>(), tuple, pool); + } + + template<std::size_t... Ns> + static void loadHelper( + std::index_sequence<Ns...>, std::tuple<Args...> &tuple, PersistentPool *pool) + { + (pool->load(std::get<Ns>(tuple)), ...); + } + + static void load(std::tuple<Args...> &tuple, PersistentPool * pool) + { + loadHelper(std::make_index_sequence<sizeof...(Args)>(), tuple, pool); + } +}; + template<typename T> struct PPHelper<QFlags<T>> { using Int = typename QFlags<T>::Int; @@ -461,6 +488,7 @@ template<typename T> struct PPHelper<QFlags<T>> template<typename T> struct IsSimpleContainer : std::false_type { }; template<typename T> struct IsSimpleContainer<QList<T>> : std::true_type { }; +template<> struct IsSimpleContainer<QStringList> : std::false_type { }; template<typename T> struct IsSimpleContainer<std::vector<T>> : std::true_type { }; template<typename T> struct PPHelper<T, std::enable_if_t<IsSimpleContainer<T>::value>> @@ -490,7 +518,7 @@ struct PPHelper<T, std::enable_if_t<IsKeyValueContainer<T>::value>> { static void store(const T &container, PersistentPool *pool) { - pool->store(container.size()); + pool->store(int(container.size())); for (auto it = container.cbegin(); it != container.cend(); ++it) { pool->store(it.key()); pool->store(it.value()); diff --git a/src/lib/corelib/language/modulemerger.h b/src/lib/corelib/tools/pimpl.h index 3cc3ba08a..ab672aabe 100644 --- a/src/lib/corelib/language/modulemerger.h +++ b/src/lib/corelib/tools/pimpl.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2023 Ivan Komissarov (abbapoh@gmail.com) ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -37,52 +37,25 @@ ** ****************************************************************************/ -#ifndef QBS_MODULEMERGER_H -#define QBS_MODULEMERGER_H +#ifndef PIMPL_H +#define PIMPL_H -#include "item.h" -#include "qualifiedid.h" - -#include <logging/logger.h> -#include <tools/set.h> -#include <tools/version.h> - -#include <QtCore/qhash.h> +#include <tools/propagate_const.h> +#include <memory> namespace qbs { namespace Internal { -class ModuleMerger { -public: - ModuleMerger(Logger &logger, Item *root, Item::Module &moduleToMerge); - void start(); - -private: - Item::PropertyMap dfs(const Item::Module &m, Item::PropertyMap props); - void mergeOutProps(Item::PropertyMap *dst, const Item::PropertyMap &src); - void appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, - const ValuePtr &sv); - static ValuePtr lastInNextChain(const ValuePtr &v); - - enum PropertiesType { ScalarProperties, ListProperties }; - void insertProperties(Item::PropertyMap *dst, Item *srcItem, PropertiesType type); - void replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace); - void replaceItemInScopes(Item *toReplace); +template<typename T> +using Pimpl = KDToolBox::propagate_const<std::unique_ptr<T>>; - Logger &m_logger; - Item * const m_rootItem; - Item::Module &m_mergedModule; - Item *m_clonedModulePrototype = nullptr; - QHash<ValuePtr, PropertyDeclaration> m_decls; - Set<const Item *> m_seenInstancesTopDown; - Set<const Item *> m_seenInstancesBottomUp; - Set<Item *> m_moduleInstanceContainers; - bool m_required; - const bool m_isBaseModule; - VersionRange m_versionRange; -}; +template<typename T, typename... Args> +Pimpl<T> makePimpl(Args&&... args) +{ + return Pimpl<T>(std::make_unique<T>(std::forward<Args>(args)...)); +} } // namespace Internal } // namespace qbs -#endif // QBS_MODULEMERGER_H +#endif // PIMPL_H diff --git a/src/lib/corelib/tools/porting.h b/src/lib/corelib/tools/porting.h new file mode 100644 index 000000000..37582e8d3 --- /dev/null +++ b/src/lib/corelib/tools/porting.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBS_TOOLS_PORTING_H +#define QBS_TOOLS_PORTING_H + +#include <QtCore/qglobal.h> + +namespace qbs { + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +using QHashValueType = uint; +#else +using QHashValueType = size_t; +#endif + +} + +#endif diff --git a/src/lib/corelib/tools/preferences.cpp b/src/lib/corelib/tools/preferences.cpp index 4db271758..fe669e163 100644 --- a/src/lib/corelib/tools/preferences.cpp +++ b/src/lib/corelib/tools/preferences.cpp @@ -63,7 +63,6 @@ Preferences::Preferences(Settings *settings, QVariantMap profileContents) { } - /*! * \brief Returns true <=> colored output should be used for printing messages. * This is only relevant for command-line frontends. diff --git a/src/lib/corelib/tools/processresult.h b/src/lib/corelib/tools/processresult.h index 92408aa31..3870a3ae5 100644 --- a/src/lib/corelib/tools/processresult.h +++ b/src/lib/corelib/tools/processresult.h @@ -44,11 +44,10 @@ #include <QtCore/qshareddata.h> #include <QtCore/qmetatype.h> #include <QtCore/qprocess.h> +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QJsonObject; -class QString; -class QStringList; QT_END_NAMESPACE namespace qbs { diff --git a/src/lib/corelib/tools/processutils.cpp b/src/lib/corelib/tools/processutils.cpp index b27592f88..02608cc9f 100644 --- a/src/lib/corelib/tools/processutils.cpp +++ b/src/lib/corelib/tools/processutils.cpp @@ -91,7 +91,7 @@ QString processNameByPid(qint64 pid) char exePath[64]; char buf[PATH_MAX]; memset(buf, 0, sizeof(buf)); - sprintf(exePath, "/proc/%lld/exe", pid); + std::sprintf(exePath, "/proc/%lld/exe", pid); if (readlink(exePath, buf, sizeof(buf)) < 0) return {}; return FileInfo::fileName(QString::fromUtf8(buf)); diff --git a/src/lib/corelib/tools/profile.cpp b/src/lib/corelib/tools/profile.cpp index 7e594fd2d..0fe0ba19d 100644 --- a/src/lib/corelib/tools/profile.cpp +++ b/src/lib/corelib/tools/profile.cpp @@ -63,13 +63,13 @@ namespace qbs { /*! * \brief Creates an object giving access to the settings for profile \c name. */ -Profile::Profile(const QString &name, Settings *settings, const QVariantMap &profiles) - : m_name(name), +Profile::Profile(QString name, Settings *settings, QVariantMap profiles) + : m_name(std::move(name)), m_settings(settings), - m_values(profiles.value(name).toMap()), - m_profiles(profiles) + m_values(profiles.value(m_name).toMap()), + m_profiles(std::move(profiles)) { - QBS_ASSERT(name == cleanName(name), return); + QBS_ASSERT(m_name == cleanName(m_name), return); } bool Profile::exists() const @@ -213,7 +213,7 @@ QVariant Profile::possiblyInheritedValue(const QString &key, const QVariant &def QStringList profileChain) const { extendAndCheckProfileChain(profileChain); - const QVariant v = localValue(key); + QVariant v = localValue(key); if (v.isValid()) return v; const QString baseProfileName = baseProfile(); diff --git a/src/lib/corelib/tools/profile.h b/src/lib/corelib/tools/profile.h index aa8b7ef10..b7fc7ff67 100644 --- a/src/lib/corelib/tools/profile.h +++ b/src/lib/corelib/tools/profile.h @@ -42,12 +42,9 @@ #include "qbs_export.h" #include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> -QT_BEGIN_NAMESPACE -class QStringList; -QT_END_NAMESPACE - namespace qbs { class ErrorInfo; class Settings; @@ -55,7 +52,7 @@ class Settings; class QBS_EXPORT Profile { public: - Profile(const QString &name, Settings *settings, const QVariantMap &profiles = QVariantMap()); + Profile(QString name, Settings *settings, QVariantMap profiles = QVariantMap()); bool exists() const; QVariant value(const QString &key, const QVariant &defaultValue = QVariant(), diff --git a/src/lib/corelib/tools/profiling.cpp b/src/lib/corelib/tools/profiling.cpp index 7e3559b54..b93d3fa17 100644 --- a/src/lib/corelib/tools/profiling.cpp +++ b/src/lib/corelib/tools/profiling.cpp @@ -61,7 +61,7 @@ TimedActivityLogger::TimedActivityLogger(const Logger &logger, const QString &ac { if (!enabled) return; - d = new TimedActivityLoggerPrivate; + d = std::make_unique<TimedActivityLoggerPrivate>(); d->logger = logger; d->activity = activity; d->logger.qbsLog(LoggerInfo, true) << Tr::tr("Starting activity '%2'.").arg(activity); @@ -72,11 +72,10 @@ void TimedActivityLogger::finishActivity() { if (!d) return; - const QString timeString = elapsedTimeString(d->timer.elapsed()); + const QString timeString = elapsedTimeString(d->timer.nsecsElapsed()); d->logger.qbsLog(LoggerInfo, true) << Tr::tr("Activity '%2' took %3.").arg(d->activity, timeString); - delete d; - d = nullptr; + d.reset(); } TimedActivityLogger::~TimedActivityLogger() @@ -99,13 +98,13 @@ void AccumulatingTimer::stop() { if (!m_timer.isValid()) return; - *m_elapsedTime += m_timer.elapsed(); + *m_elapsedTime += m_timer.nsecsElapsed(); m_timer.invalidate(); } -QString elapsedTimeString(qint64 elapsedTimeInMs) +QString elapsedTimeString(qint64 elapsedTimeInNs) { - qint64 ms = elapsedTimeInMs; + qint64 ms = elapsedTimeInNs / (1000ll * 1000ll); qint64 s = ms/1000; ms -= s*1000; qint64 m = s/60; diff --git a/src/lib/corelib/tools/profiling.h b/src/lib/corelib/tools/profiling.h index 89f862ff9..3220c2a82 100644 --- a/src/lib/corelib/tools/profiling.h +++ b/src/lib/corelib/tools/profiling.h @@ -42,6 +42,8 @@ #include <QtCore/qelapsedtimer.h> +#include <memory> + QT_BEGIN_NAMESPACE class QString; QT_END_NAMESPACE @@ -50,7 +52,7 @@ namespace qbs { namespace Internal { class Logger; -QString elapsedTimeString(qint64 elapsedTimeInMs); +QString elapsedTimeString(qint64 elapsedTimeInNs); class TimedActivityLogger { @@ -61,7 +63,7 @@ public: private: class TimedActivityLoggerPrivate; - TimedActivityLoggerPrivate *d; + std::unique_ptr<TimedActivityLoggerPrivate> d; }; class AccumulatingTimer diff --git a/src/lib/corelib/tools/progressobserver.h b/src/lib/corelib/tools/progressobserver.h index fc49d9eed..73a61de37 100644 --- a/src/lib/corelib/tools/progressobserver.h +++ b/src/lib/corelib/tools/progressobserver.h @@ -41,12 +41,15 @@ #include <QtCore/qglobal.h> +#include <vector> + QT_BEGIN_NAMESPACE class QString; QT_END_NAMESPACE namespace qbs { namespace Internal { +class ScriptEngine; class ProgressObserver { @@ -64,6 +67,14 @@ public: // Call this to ensure that the progress bar always goes to 100%. void setFinished(); + + void addScriptEngine(ScriptEngine *engine) { m_scriptEngines.push_back(engine); } + +protected: + const std::vector<ScriptEngine *> &scriptEngines() const { return m_scriptEngines; } + +private: + std::vector<ScriptEngine *> m_scriptEngines; }; } // namespace Internal diff --git a/src/lib/corelib/tools/projectgeneratormanager.h b/src/lib/corelib/tools/projectgeneratormanager.h index 469d650b9..1efe593fc 100644 --- a/src/lib/corelib/tools/projectgeneratormanager.h +++ b/src/lib/corelib/tools/projectgeneratormanager.h @@ -45,10 +45,7 @@ #include <QtCore/qmap.h> #include <QtCore/qstring.h> - -QT_BEGIN_NAMESPACE -class QStringList; -QT_END_NAMESPACE +#include <QtCore/qstringlist.h> namespace qbs { class ProjectGenerator; diff --git a/src/lib/corelib/tools/propagate_const.h b/src/lib/corelib/tools/propagate_const.h new file mode 100644 index 000000000..65e35fc8c --- /dev/null +++ b/src/lib/corelib/tools/propagate_const.h @@ -0,0 +1,418 @@ +/**************************************************************************** +** MIT License +** +** Copyright (C) 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +** Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> +** +** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, ** and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice (including the next paragraph) +** shall be included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, +** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +** DEALINGS IN THE SOFTWARE. +****************************************************************************/ + +#ifndef KDTOOLBOX_PROPAGATE_CONST +#define KDTOOLBOX_PROPAGATE_CONST + +#include <functional> +#include <type_traits> +#include <utility> + +// Our SFINAE is too noisy and clang-format gets confused. +// clang-format off + +namespace KDToolBox +{ + +template <typename T> +class propagate_const; + +namespace detail { +// Type traits to detect propagate_const specializations +template <typename T> +struct is_propagate_const : std::false_type +{ +}; + +template <typename T> +struct is_propagate_const<propagate_const<T>> : std::true_type +{ +}; + +// Using SFINAE in a base class to constrain the conversion operators. +// Otherwise, if we make them function templates and apply SFINAE directly on +// them, we would not be able to do certain conversions (cf. +// [over.ics.user/3]). +template <typename T> +using propagate_const_element_type = std::remove_reference_t<decltype(*std::declval<T &>())>; + +// Const conversion. +// NON-STANDARD: checks that `const T` is convertible, not `T`. +// See https://wg21.link/lwg3812 +template <typename T, + bool = std::disjunction_v< + std::is_pointer<T>, + std::is_convertible<const T, const propagate_const_element_type<T> *> + > + > +struct propagate_const_const_conversion_operator_base +{ +}; + +template <typename T> +struct propagate_const_const_conversion_operator_base<T, true> +{ + constexpr operator const propagate_const_element_type<T> *() const; +}; + +// Non-const conversion +template <typename T, + bool = std::disjunction_v< + std::is_pointer<T>, + std::is_convertible<T, propagate_const_element_type<T> *> + > + > +struct propagate_const_non_const_conversion_operator_base +{ +}; + +template <typename T> +struct propagate_const_non_const_conversion_operator_base<T, true> +{ + constexpr operator propagate_const_element_type<T> *(); +}; + +template <typename T> +struct propagate_const_base + : propagate_const_const_conversion_operator_base<T>, + propagate_const_non_const_conversion_operator_base<T> +{}; + +} // namespace detail + +/* + TODO: This code could benefit from a C++20 overhaul: + * concepts + * three-way comparisons + * explicit(bool) + + However we can't depend on C++20 yet... +*/ + +template <typename T> +class propagate_const : public detail::propagate_const_base<T> +{ +public: + using element_type = detail::propagate_const_element_type<T>; + + // Special member functions + propagate_const() = default; + propagate_const(propagate_const &&) = default; + propagate_const &operator=(propagate_const &&) = default; + propagate_const(const propagate_const &) = delete; + propagate_const &operator=(const propagate_const &) = delete; + ~propagate_const() = default; + + // Constructor from values + + template < + typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v< + std::is_constructible<T, U>, // 1) we can build a T from a U, + std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a + // converting constructor, + std::is_convertible<U, T> // 3) and the conversion from U to T is implicit; + >, + bool> = true> + /* implicit */ // then, this constructor is implicit too. + propagate_const(U &&other) + : m_t(std::forward<U>(other)) + { + } + + template < + typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v< + std::is_constructible<T, U>, // 1) we can build a T from a U, + std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a + // converting constructor, + std::negation<std::is_convertible<U, T>> // 3) and the conversion from U to T is + // explicit; + >, + bool> = true> + explicit // then, this constructor is explicit. + propagate_const(U &&other) + : m_t(std::forward<U>(other)) + { + } + + // Constructors from other propagate_const (converting constructors) + template <typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion, + std::is_convertible<U, T> // 2) and the conversion is implicit; + >, + bool> = true> + /* implicit */ // then, this constructor is implicit. + constexpr propagate_const(propagate_const<U> &&other) + : m_t(std::move(get_underlying(other))) + { + } + + template <typename U, + std::enable_if_t< // This constructor is enabled if: + std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion, + std::negation<std::is_convertible<U, T>> // 2) and the + // conversion is + // explicit; + >, + bool> = true> + explicit // then, this constructor is explicit. + constexpr propagate_const(propagate_const<U> &&other) + : m_t(std::move(get_underlying(other))) + { + } + + // Converting assignment + template <typename U, + std::enable_if_t< // This assignment operator is enabled if + std::is_convertible_v<U, T>, // the conversion from U to T is implicit + bool> = true> + constexpr propagate_const &operator=(propagate_const<U> &&other) + { + m_t = std::move(get_underlying(other)); + return *this; + } + + template <typename U, + std::enable_if_t< // This assignment operator is enabled if: + std::conjunction_v< + std::is_convertible<U, T>, // 1) the conversion from U to T is implicit, + std::negation<detail::is_propagate_const<std::decay_t<U>>> // 2) and U is not a + // propagate_const + >, + bool> = true> + constexpr propagate_const &operator=(U &&other) + { + m_t = std::forward<U>(other); + return *this; + } + + // Swap + constexpr void swap(propagate_const &other) noexcept(std::is_nothrow_swappable_v<T>) + { + using std::swap; + swap(m_t, other.m_t); + } + + // Const observers + constexpr explicit operator bool() const { return static_cast<bool>(m_t); } + + constexpr const element_type *get() const { return get_impl(m_t); } + + constexpr const element_type &operator*() const { return *get(); } + + constexpr const element_type *operator->() const { return get(); } + + // Non-const observers + constexpr element_type *get() { return get_impl(m_t); } + + constexpr element_type &operator*() { return *get(); } + + constexpr element_type *operator->() { return get(); } + + // Non-member utilities: extract the contained object + template <typename U> + friend constexpr auto &get_underlying(propagate_const<U> &p); + + template <typename U> + friend constexpr const auto &get_underlying(const propagate_const<U> &p); + +private: + // Implementation of get() that works with raw pointers and smart + // pointers. Similar to std::to_address, but to_address is C++20, + // and propagate_const spec does not match it. + template <typename U> + static constexpr element_type *get_impl(U *u) + { + return u; + } + + template <typename U> + static constexpr element_type *get_impl(U &u) + { + return u.get(); + } + + template <typename U> + static constexpr const element_type *get_impl(const U *u) + { + return u; + } + + template <typename U> + static constexpr const element_type *get_impl(const U &u) + { + return u.get(); + } + + T m_t; +}; + +// Swap +template <typename T, + std::enable_if_t<std::is_swappable_v<T>, bool> = true> +constexpr void swap(propagate_const<T> &lhs, propagate_const<T> &rhs) + noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +// Implementation of get_underlying +template <typename T> +constexpr auto &get_underlying(propagate_const<T> &p) +{ + return p.m_t; +} + +template <typename T> +constexpr const auto &get_underlying(const propagate_const<T> &p) +{ + return p.m_t; +} + +// Implementation of the conversion operators +template <typename T> +constexpr detail::propagate_const_const_conversion_operator_base<T, true> + ::operator const detail::propagate_const_element_type<T> *() const +{ + return static_cast<const propagate_const<T> *>(this)->get(); +} + +template <typename T> +constexpr detail::propagate_const_non_const_conversion_operator_base<T, true> + ::operator detail::propagate_const_element_type<T> *() +{ + return static_cast<propagate_const<T> *>(this)->get(); +} + +// Comparisons. As per spec, they're free function templates. + +// Comparisons against nullptr. +template <typename T> +constexpr bool operator==(const propagate_const<T> &p, std::nullptr_t) +{ + return get_underlying(p) == nullptr; +} + +template <typename T> +constexpr bool operator!=(const propagate_const<T> &p, std::nullptr_t) +{ + return get_underlying(p) != nullptr; +} + +template <typename T> +constexpr bool operator==(std::nullptr_t, const propagate_const<T> &p) +{ + return nullptr == get_underlying(p); +} + +template <typename T> +constexpr bool operator!=(std::nullptr_t, const propagate_const<T> &p) +{ + return nullptr == get_underlying(p); +} + +// Comparisons between propagate_const +#define DEFINE_PROPAGATE_CONST_COMPARISON_OP(op) \ +template <typename T, typename U> \ + constexpr bool operator op (const propagate_const<T> &lhs, const propagate_const<U> &rhs) \ +{ \ + return get_underlying(lhs) op get_underlying(rhs); \ +} \ + + DEFINE_PROPAGATE_CONST_COMPARISON_OP(==) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(!=) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(<) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(<=) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(>) + DEFINE_PROPAGATE_CONST_COMPARISON_OP(>=) + +#undef DEFINE_PROPAGATE_CONST_COMPARISON_OP + +// Comparisons against other (smart) pointers +#define DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(op) \ + template <typename T, typename U> \ + constexpr bool operator op (const propagate_const<T> &lhs, const U &rhs) \ +{ \ + return get_underlying(lhs) op rhs; \ +} \ + template <typename T, typename U> \ + constexpr bool operator op (const T &lhs, const propagate_const<U> &rhs) \ +{ \ + return lhs op get_underlying(rhs); \ +} \ + + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(==) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(!=) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<=) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>) + DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>=) + +#undef DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP + +} // namespace KDToolBox + +// std::hash specialization +namespace std +{ +template <typename T> +struct hash<KDToolBox::propagate_const<T>> +{ + constexpr size_t operator()(const KDToolBox::propagate_const<T> &t) const + noexcept(noexcept(hash<T>{}(get_underlying(t)))) + { + return hash<T>{}(get_underlying(t)); + } +}; + +#define DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(COMP) \ +template <typename T> \ + struct COMP<KDToolBox::propagate_const<T>> \ +{ \ + constexpr bool operator()(const KDToolBox::propagate_const<T> &lhs, \ + const KDToolBox::propagate_const<T> &rhs) \ + { \ + return COMP<T>{}(get_underlying(lhs), get_underlying(rhs)); \ + } \ +}; \ + + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(equal_to) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(not_equal_to) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less_equal) + DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater_equal) + +#undef DEFINE_COMP_OBJECT_SPECIALIZATION + +} // namespace std + +#endif // KDTOOLBOX_PROPAGATE_CONST diff --git a/src/lib/corelib/tools/qbs_export.h b/src/lib/corelib/tools/qbs_export.h index 164aa4184..2cfe1cd82 100644 --- a/src/lib/corelib/tools/qbs_export.h +++ b/src/lib/corelib/tools/qbs_export.h @@ -53,14 +53,14 @@ #else # ifdef QBS_LIBRARY # define QBS_EXPORT QBS_DECL_EXPORT -# ifdef QBS_ENABLE_UNIT_TESTS +# ifdef QBS_WITH_TESTS # define QBS_AUTOTEST_EXPORT QBS_DECL_EXPORT # else # define QBS_AUTOTEST_EXPORT # endif # else # define QBS_EXPORT QBS_DECL_IMPORT -# ifdef QBS_ENABLE_UNIT_TESTS +# ifdef QBS_WITH_TESTS # define QBS_AUTOTEST_EXPORT QBS_DECL_IMPORT # else # define QBS_AUTOTEST_EXPORT diff --git a/src/lib/corelib/tools/qbsassert.h b/src/lib/corelib/tools/qbsassert.h index e8dfcacf8..65352fdb3 100644 --- a/src/lib/corelib/tools/qbsassert.h +++ b/src/lib/corelib/tools/qbsassert.h @@ -59,6 +59,11 @@ QBS_EXPORT void writeAssertLocation(const char *condition, const char *file, int // 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_GUARD(cond) \ + (Q_LIKELY(cond) \ + ? true \ + : (::qbs::Internal::writeAssertLocation(#cond, __FILE__, __LINE__), false)) + #define QBS_CHECK(cond)\ do {\ if (Q_LIKELY(cond)) {} else {\ diff --git a/src/lib/corelib/tools/qbspluginmanager.cpp b/src/lib/corelib/tools/qbspluginmanager.cpp index d4e92e22a..0816d9d25 100644 --- a/src/lib/corelib/tools/qbspluginmanager.cpp +++ b/src/lib/corelib/tools/qbspluginmanager.cpp @@ -79,7 +79,7 @@ QbsPluginManager::~QbsPluginManager() { unloadStaticPlugins(); - for (QLibrary * const lib : qAsConst(d->libs)) { + for (QLibrary * const lib : std::as_const(d->libs)) { auto unload = reinterpret_cast<QbsPluginUnloadFunction>(lib->resolve("QbsPluginUnload")); if (unload) unload(); diff --git a/src/lib/corelib/tools/qbsprocess.cpp b/src/lib/corelib/tools/qbsprocess.cpp index 52ce3f25a..3fdc05afe 100644 --- a/src/lib/corelib/tools/qbsprocess.cpp +++ b/src/lib/corelib/tools/qbsprocess.cpp @@ -65,7 +65,7 @@ void QbsProcess::start(const QString &command, const QStringList &arguments) { if (m_socketError) { m_error = QProcess::FailedToStart; - emit error(m_error); + emit errorOccurred(m_error); return; } m_command = command; @@ -95,7 +95,7 @@ void QbsProcess::cancel() m_errorString = Tr::tr("Process canceled before it was started."); m_error = QProcess::FailedToStart; m_state = QProcess::NotRunning; - emit error(m_error); + emit errorOccurred(m_error); break; case QProcess::Running: sendPacket(StopProcessPacket(token())); @@ -120,7 +120,7 @@ void QbsProcess::sendPacket(const LauncherPacket &packet) QByteArray QbsProcess::readAndClear(QByteArray &data) { - const QByteArray tmp = data; + QByteArray tmp = data; data.clear(); return tmp; } @@ -155,7 +155,7 @@ void QbsProcess::handleSocketError(const QString &message) if (m_state != QProcess::NotRunning) { m_state = QProcess::NotRunning; m_error = QProcess::FailedToStart; - emit error(m_error); + emit errorOccurred(m_error); } } @@ -166,7 +166,7 @@ void QbsProcess::handleErrorPacket(const QByteArray &packetData) m_error = packet.error; m_errorString = packet.errorString; m_state = QProcess::NotRunning; - emit error(m_error); + emit errorOccurred(m_error); } void QbsProcess::handleFinishedPacket(const QByteArray &packetData) diff --git a/src/lib/corelib/tools/qbsprocess.h b/src/lib/corelib/tools/qbsprocess.h index 2181818f0..3314e3d3c 100644 --- a/src/lib/corelib/tools/qbsprocess.h +++ b/src/lib/corelib/tools/qbsprocess.h @@ -69,7 +69,7 @@ public: QString errorString() const { return m_errorString; } signals: - void error(QProcess::ProcessError error); + void errorOccurred(QProcess::ProcessError error); void finished(int exitCode); private: diff --git a/src/lib/corelib/tools/qttools.cpp b/src/lib/corelib/tools/qttools.cpp index ffd336d56..40e0d0778 100644 --- a/src/lib/corelib/tools/qttools.cpp +++ b/src/lib/corelib/tools/qttools.cpp @@ -38,20 +38,42 @@ ****************************************************************************/ #include "qttools.h" +#include "porting.h" #include <QtCore/qprocess.h> +namespace std { + +size_t hash<QVariant>::operator()(const QVariant &v) const noexcept +{ + switch (v.userType()) { + case QMetaType::UnknownType: return 0; + case QMetaType::Bool: return std::hash<bool>()(v.toBool()); + case QMetaType::Int: return std::hash<int>()(v.toInt()); + case QMetaType::UInt: return std::hash<uint>()(v.toUInt()); + case QMetaType::QString: return std::hash<QString>()(v.toString()); + case QMetaType::QStringList: return std::hash<QStringList>()(v.toStringList()); + case QMetaType::QVariantList: return std::hash<QVariantList>()(v.toList()); + case QMetaType::QVariantMap: return std::hash<QVariantMap>()(v.toMap()); + case QMetaType::QVariantHash: return std::hash<QVariantHash>()(v.toHash()); + default: + QBS_ASSERT("Unsupported variant type" && false, return 0); + } +} + +} // namespace std + QT_BEGIN_NAMESPACE -uint qHash(const QStringList &list) +qbs::QHashValueType qHash(const QStringList &list) { - uint s = 0; + qbs::QHashValueType s = 0; for (const QString &n : list) s ^= qHash(n) + 0x9e3779b9 + (s << 6) + (s >> 2); return s; } -uint qHash(const QProcessEnvironment &env) +qbs::QHashValueType qHash(const QProcessEnvironment &env) { return qHash(env.toStringList()); } diff --git a/src/lib/corelib/tools/qttools.h b/src/lib/corelib/tools/qttools.h index c3b4d3a9f..029948be4 100644 --- a/src/lib/corelib/tools/qttools.h +++ b/src/lib/corelib/tools/qttools.h @@ -40,8 +40,14 @@ #ifndef QBSQTTOOLS_H #define QBSQTTOOLS_H +#include <tools/qbsassert.h> +#include <tools/porting.h> +#include <tools/stlutils.h> + #include <QtCore/qhash.h> #include <QtCore/qstringlist.h> +#include <QtCore/qtextstream.h> +#include <QtCore/qvariant.h> #include <functional> @@ -50,12 +56,6 @@ class QProcessEnvironment; QT_END_NAMESPACE namespace std { -#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) -template<> struct hash<QString> { - std::size_t operator()(const QString &s) const { return qHash(s); } -}; -#endif - template<typename T1, typename T2> struct hash<std::pair<T1, T2>> { size_t operator()(const pair<T1, T2> &x) const @@ -63,11 +63,92 @@ template<typename T1, typename T2> struct hash<std::pair<T1, T2>> return std::hash<T1>()(x.first) ^ std::hash<T2>()(x.second); } }; + +template <typename... Ts> +struct hash<std::tuple<Ts...>> +{ +private: + template<std::size_t... Ns> + static size_t helper(std::index_sequence<Ns...>, const std::tuple<Ts...> &tuple) noexcept + { + size_t seed = 0; + (qbs::Internal::hashCombineHelper(seed, std::get<Ns>(tuple)), ...); + return seed; + } + +public: + size_t operator()(const std::tuple<Ts...> & tuple) const noexcept + { + return helper(std::make_index_sequence<sizeof...(Ts)>(), tuple); + } +}; + + +template<> struct hash<QStringList> +{ + std::size_t operator()(const QStringList &s) const noexcept + { + return qbs::Internal::hashRange(s); + } +}; + +template<> struct hash<QVariant> +{ + size_t operator()(const QVariant &v) const noexcept; +}; + +template<> struct hash<QVariantList> +{ + size_t operator()(const QVariantList &v) const noexcept + { + return qbs::Internal::hashRange(v); + } +}; + +template<> struct hash<QVariantMap> +{ + size_t operator()(const QVariantMap &v) const noexcept + { + return qbs::Internal::hashRange(v); + } +}; + +template<> struct hash<QVariantHash> +{ + size_t operator()(const QVariantHash &v) const noexcept + { + return qbs::Internal::hashRange(v); + } +}; + } // namespace std QT_BEGIN_NAMESPACE -uint qHash(const QStringList &list); -uint qHash(const QProcessEnvironment &env); + +qbs::QHashValueType qHash(const QStringList &list); +qbs::QHashValueType qHash(const QProcessEnvironment &env); + +template<typename... Args> +qbs::QHashValueType qHash(const std::tuple<Args...> &tuple) +{ + return std::hash<std::tuple<Args...>>()(tuple) % std::numeric_limits<uint>::max(); +} + +inline qbs::QHashValueType qHash(const QVariant &v) +{ + return std::hash<QVariant>()(v) % std::numeric_limits<uint>::max(); +} + +inline qbs::QHashValueType qHash(const QVariantMap &v) +{ + return std::hash<QVariantMap>()(v) % std::numeric_limits<uint>::max(); +} + +inline qbs::QHashValueType qHash(const QVariantHash &v) +{ + return std::hash<QVariantHash>()(v) % std::numeric_limits<uint>::max(); +} + QT_END_NAMESPACE namespace qbs { @@ -75,14 +156,94 @@ namespace qbs { template <class T> QSet<T> toSet(const QList<T> &list) { -#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) - return list.toSet(); -#else return QSet<T>(list.begin(), list.end()); +} + +template<class T> +QList<T> toList(const QSet<T> &set) +{ + return QList<T>(set.begin(), set.end()); +} + +template<typename K, typename V> +QHash<K, V> &unite(QHash<K, V> &h, const QHash<K, V> &other) +{ + h.insert(other); + return h; +} + +inline void setupDefaultCodec(QTextStream &stream) +{ +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + stream.setCodec("UTF-8"); +#else + Q_UNUSED(stream); #endif } -} // namespace qbs +inline bool qVariantCanConvert(const QVariant &variant, int typeId) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + return variant.canConvert(QMetaType(typeId)); +#else + return variant.canConvert(typeId); // deprecated in Qt6 +#endif +} +inline bool qVariantConvert(QVariant &variant, int typeId) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + return variant.convert(QMetaType(typeId)); +#else + return variant.convert(typeId); // deprecated in Qt6 +#endif +} + +inline QMetaType::Type qVariantType(const QVariant &v) +{ + return static_cast<QMetaType::Type>( +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + v.metaType().id() +#else + v.type() +#endif + ); +} + +template<typename T> +inline QVariant typedNullVariant() +{ + const auto metaType = QMetaType::fromType<T>(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + return QVariant(metaType, nullptr); +#else + return QVariant(static_cast<QVariant::Type>(metaType.id())); +#endif +} + +inline bool qVariantsEqual(const QVariant &v1, const QVariant &v2) +{ + return v1.isNull() == v2.isNull() && v1 == v2; +} + +inline bool qVariantMapsEqual(const QVariantMap &m1, const QVariantMap &m2) +{ + if (m1.size() != m2.size()) + return false; + if (m1.isSharedWith(m2)) + return true; + + auto it1 = m1.cbegin(); + auto it2 = m2.cbegin(); + while (it1 != m1.cend()) { + if (it1.key() != it2.key() || !qVariantsEqual(it1.value(), it2.value())) + return false; + ++it2; + ++it1; + } + return true; +} + +} // namespace qbs #endif // QBSQTTOOLS_H diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp index adf930cf0..e89e4a0ad 100644 --- a/src/lib/corelib/tools/scripttools.cpp +++ b/src/lib/corelib/tools/scripttools.cpp @@ -39,9 +39,14 @@ #include "scripttools.h" -#include <QtCore/qdatastream.h> +#include <language/scriptengine.h> +#include <tools/error.h> -#include <QtScript/qscriptengine.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> namespace qbs { namespace Internal { @@ -62,20 +67,262 @@ QVariant getConfigProperty(const QVariantMap &cfg, const QStringList &name) { if (name.length() == 1) return cfg.value(name.front()); - else - return getConfigProperty(cfg.value(name.front()).toMap(), name.mid(1)); + return getConfigProperty(cfg.value(name.front()).toMap(), name.mid(1)); } -TemporaryGlobalObjectSetter::TemporaryGlobalObjectSetter(const QScriptValue &object) +TemporaryGlobalObjectSetter::TemporaryGlobalObjectSetter( + ScriptEngine *engine, const JSValue &object) + : m_engine(engine), m_oldGlobalObject(engine->globalObject()) { - QScriptEngine *engine = object.engine(); - m_oldGlobalObject = engine->globalObject(); engine->setGlobalObject(object); } TemporaryGlobalObjectSetter::~TemporaryGlobalObjectSetter() { - m_oldGlobalObject.engine()->setGlobalObject(m_oldGlobalObject); + m_engine->setGlobalObject(m_oldGlobalObject); +} + +JsException::JsException(JsException &&other) noexcept + : m_ctx(other.m_ctx), m_exception(other.m_exception), + m_fallbackLocation(std::move(other.m_fallbackLocation)) +{ + other.m_exception = JS_NULL; +} + +JsException::~JsException() { JS_FreeValue(m_ctx, m_exception); } + +QString JsException::message() const +{ + if (JS_IsError(m_ctx, m_exception)) + return getJsStringProperty(m_ctx, m_exception, QStringLiteral("message")); + const QVariant v = getJsVariant(m_ctx, m_exception); + switch (static_cast<QMetaType::Type>(v.userType())) { + case QMetaType::QVariantMap: + return QString::fromUtf8(QJsonDocument(QJsonObject::fromVariantMap(v.toMap())) + .toJson(QJsonDocument::Indented)); + case QMetaType::QStringList: + return QString::fromUtf8(QJsonDocument(QJsonArray::fromStringList(v.toStringList())) + .toJson(QJsonDocument::Indented)); + case QMetaType::QVariantList: + return QString::fromUtf8(QJsonDocument(QJsonArray::fromVariantList(v.toList())) + .toJson(QJsonDocument::Indented)); + default: + return v.toString(); + } +} + +const QStringList JsException::stackTrace() const +{ + return getJsStringProperty(m_ctx, m_exception, QLatin1String("stack")) + .split(QLatin1Char('\n'), Qt::SkipEmptyParts); +} + +ErrorInfo JsException::toErrorInfo() const +{ + const QString msg = message(); + ErrorInfo e(msg, stackTrace()); + if (e.hasLocation() || !m_fallbackLocation.isValid()) + return e; + return {msg, m_fallbackLocation}; +} + +void defineJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, JSValue val) +{ + JS_DefinePropertyValueStr(ctx, obj, prop.toUtf8().constData(), val, 0); +} + +JSValue getJsProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + return JS_GetPropertyStr(ctx, obj, prop.toUtf8().constData()); +} + +void setJsProperty(JSContext *ctx, JSValue obj, const QString &prop, JSValue val) +{ + if (JS_SetPropertyStr(ctx, obj, prop.toUtf8().constData(), val) <= 0) + qDebug() << "Oje!"; +} + +void setJsProperty(JSContext *ctx, JSValue obj, const QString &prop, const QString &val) +{ + setJsProperty(ctx, obj, prop, makeJsString(ctx, val)); +} + +void handleJsProperties(JSContext *ctx, JSValue obj, + const std::function<void (const JSAtom &, + const JSPropertyDescriptor &)> &handler) +{ + struct PropsHolder { + PropsHolder(JSContext *ctx) : ctx(ctx) {} + ~PropsHolder() { + for (uint32_t i = 0; i < count; ++i) + JS_FreeAtom(ctx, props[i].atom); + js_free(ctx, props); + } + JSContext * const ctx; + JSPropertyEnum *props = nullptr; + uint32_t count = 0; + } propsHolder(ctx); + JS_GetOwnPropertyNames(ctx, &propsHolder.props, &propsHolder.count, obj, JS_GPN_STRING_MASK); + for (uint32_t i = 0; i < propsHolder.count; ++i) { + JSPropertyDescriptor desc{0, JS_UNDEFINED, JS_UNDEFINED, JS_UNDEFINED}; + JS_GetOwnProperty(ctx, &desc, obj, propsHolder.props[i].atom); + const ScopedJsValueList valueHolder(ctx, {desc.value, desc.getter, desc.setter}); + handler(propsHolder.props[i].atom, desc); + } +} + +QString getJsString(JSContext *ctx, JSValueConst val) +{ + size_t strLen; + const char * const str = JS_ToCStringLen(ctx, &strLen, val); + QString s = QString::fromUtf8(str, strLen); + JS_FreeCString(ctx, str); + return s; +} + +QString getJsStringProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + const ScopedJsValue sv(ctx, getJsProperty(ctx, obj, prop)); + return getJsString(ctx, sv); +} + +int getJsIntProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + return JS_VALUE_GET_INT(getJsProperty(ctx, obj, prop)); +} + +bool getJsBoolProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + return JS_VALUE_GET_BOOL(getJsProperty(ctx, obj, prop)); +} + +JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(s); +} + +JSValue makeJsString(JSContext *ctx, const QString &s) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(s); +} + +JSValue makeJsStringList(JSContext *ctx, const QStringList &l) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(l); +} + +JSValue throwError(JSContext *ctx, const QString &message) +{ + return JS_Throw(ctx, makeJsString(ctx, message)); +} + +QStringList getJsStringList(JSContext *ctx, JSValue val) +{ + if (JS_IsString(val)) + return {getJsString(ctx, val)}; + if (!JS_IsArray(ctx, val)) + return {}; + const int size = getJsIntProperty(ctx, val, QLatin1String("length")); + QStringList l; + for (int i = 0; i < size; ++i) { + const ScopedJsValue elem(ctx, JS_GetPropertyUint32(ctx, val, i)); + l << getJsString(ctx, elem); + } + return l; +} + +JSValue makeJsVariant(JSContext *ctx, const QVariant &v, quintptr id) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(v, id); +} + +JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l, quintptr id) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(l, id); +} + +JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m, quintptr id) +{ + return ScriptEngine::engineForContext(ctx)->asJsValue(m, id); +} + +static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> path) +{ + if (JS_IsString(val)) + return getJsString(ctx, val); + if (JS_IsBool(val)) + return bool(JS_VALUE_GET_BOOL(val)); + if (JS_IsArrayBuffer(val)) { + size_t size = 0; + const auto data = JS_GetArrayBuffer(ctx, &size, val); + if (!data || !size) + return QByteArray(); + return QByteArray(reinterpret_cast<const char *>(data), size); + } + if (JS_IsArray(ctx, val)) { + if (path.contains(val)) + return {}; + path << val; + QVariantList l; + const int size = getJsIntProperty(ctx, val, QLatin1String("length")); + for (int i = 0; i < size; ++i) { + const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, val, i)); + l << getJsVariantImpl(ctx, sv, path); + } + return l; + } + if (JS_IsDate(val)) { + ScopedJsValue toString(ctx, getJsProperty(ctx, val, QLatin1String("toISOString"))); + if (!JS_IsFunction(ctx, toString)) + return {}; + ScopedJsValue dateString(ctx, JS_Call(ctx, toString, val, 0, nullptr)); + if (!JS_IsString(dateString)) + return {}; + return QDateTime::fromString(getJsString(ctx, dateString), Qt::ISODateWithMs); + } + if (JS_IsObject(val)) { + if (path.contains(val)) + return {}; + path << val; + QVariantMap map; + handleJsProperties(ctx, val, [ctx, &map, path](const JSAtom &prop, const JSPropertyDescriptor &desc) { + map.insert(getJsString(ctx, prop), getJsVariantImpl(ctx, desc.value, path)); + }); + return map; + } + const auto tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT) + return JS_VALUE_GET_INT(val); + else if (JS_TAG_IS_FLOAT64(tag)) + return JS_VALUE_GET_FLOAT64(val); + return {}; +} + +QVariant getJsVariant(JSContext *ctx, JSValue val) +{ + return getJsVariantImpl(ctx, val, {}); +} + +QStringList getJsStringListProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + JSValue array = getJsProperty(ctx, obj, prop); + QStringList list = getJsStringList(ctx, array); + JS_FreeValue(ctx, array); + return list; +} + +QVariant getJsVariantProperty(JSContext *ctx, JSValue obj, const QString &prop) +{ + const JSValue vv = getJsProperty(ctx, obj, prop); + QVariant v = getJsVariant(ctx, vv); + JS_FreeValue(ctx, vv); + return v; +} + +QString getJsString(JSContext *ctx, JSAtom atom) +{ + const ScopedJsValue atomString(ctx, JS_AtomToString(ctx, atom)); + return getJsString(ctx, atomString); } } // namespace Internal diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h index 4a258b98f..ac7ed9928 100644 --- a/src/lib/corelib/tools/scripttools.h +++ b/src/lib/corelib/tools/scripttools.h @@ -40,57 +40,172 @@ #ifndef QBS_SCRIPTTOOLS_H #define QBS_SCRIPTTOOLS_H -#include <tools/qbs_export.h> +#include "codelocation.h" +#include "porting.h" +#include "qbs_export.h" +#include <quickjs.h> + +#include <QtCore/qhash.h> #include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptprogram.h> -#include <QtScript/qscriptvalue.h> +#include <utility> +#include <vector> namespace qbs { +class ErrorInfo; namespace Internal { +class ScriptEngine; -template <typename C> -QScriptValue toScriptValue(QScriptEngine *scriptEngine, const C &container) -{ - QScriptValue v = scriptEngine->newArray(container.size()); - int i = 0; - for (const typename C::value_type &item : container) - v.setProperty(i++, scriptEngine->toScriptValue(item)); - return v; -} +using JSValueList = std::vector<JSValue>; -void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value); -QVariant QBS_AUTOTEST_EXPORT getConfigProperty(const QVariantMap &cfg, const QStringList &name); +void defineJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, JSValue val); +JSValue getJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +void setJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, JSValue val); +void setJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, const QString &val); +QString getJsStringProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +QStringList getJsStringListProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +int getJsIntProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +bool getJsBoolProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +QVariant getJsVariantProperty(JSContext *ctx, JSValueConst obj, const QString &prop); +QString getJsString(JSContext *ctx, JSValueConst val); +QString getJsString(JSContext *ctx, JSAtom atom); +QBS_AUTOTEST_EXPORT QVariant getJsVariant(JSContext *ctx, JSValueConst val); +JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s); +JSValue makeJsString(JSContext *ctx, const QString &s); +JSValue makeJsStringList(JSContext *ctx, const QStringList &l); +JSValue makeJsVariant(JSContext *ctx, const QVariant &v, quintptr id = 0); +JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l, quintptr id = 0); +JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m, quintptr id = 0); +QStringList getJsStringList(JSContext *ctx, JSValueConst val); +JSValue throwError(JSContext *ctx, const QString &message); +using PropertyHandler = std::function<void(const JSAtom &, const JSPropertyDescriptor &)>; +void handleJsProperties(JSContext *ctx, JSValueConst obj, const PropertyHandler &handler); +inline quintptr jsObjectId(const JSValue &val) { return quintptr(JS_VALUE_GET_OBJ(val)); } template <class T> -void attachPointerTo(QScriptValue &scriptValue, T *ptr) +void attachPointerTo(JSValue &scriptValue, T *ptr) { - QVariant v; - v.setValue<quintptr>(reinterpret_cast<quintptr>(ptr)); - scriptValue.setData(scriptValue.engine()->newVariant(v)); + JS_SetOpaque(scriptValue, const_cast<std::remove_const_t<T> *>(ptr)); } template <class T> -T *attachedPointer(const QScriptValue &scriptValue) +T *attachedPointer(const JSValue &scriptValue, JSClassID id) { - const auto ptr = scriptValue.data().toVariant().value<quintptr>(); - return reinterpret_cast<T *>(ptr); + return reinterpret_cast<T *>(JS_GetOpaque(scriptValue, id)); } class TemporaryGlobalObjectSetter { public: - TemporaryGlobalObjectSetter(const QScriptValue &object); + TemporaryGlobalObjectSetter(ScriptEngine *engine, const JSValue &object); ~TemporaryGlobalObjectSetter(); private: - QScriptValue m_oldGlobalObject; + ScriptEngine * const m_engine; + const JSValue m_oldGlobalObject; +}; + +class ScopedJsValue +{ +public: + ScopedJsValue(JSContext *ctx, JSValue v) : m_context(ctx), m_value(v) {} + void setValue(JSValue v) { JS_FreeValue(m_context, m_value); m_value = v; } + ~ScopedJsValue() { JS_FreeValue(m_context, m_value); } + operator JSValue() const { return m_value; } + JSValue release() { const JSValue v = m_value; m_value = JS_UNDEFINED; return v; } + void reset(JSValue v) { JS_FreeValue(m_context, m_value); m_value = v; } + + ScopedJsValue(const ScopedJsValue &) = delete; + ScopedJsValue &operator=(const ScopedJsValue &) = delete; + + ScopedJsValue(ScopedJsValue && other) : m_context(other.m_context), m_value(other.m_value) + { + other.m_value = JS_UNDEFINED; + } + +private: + JSContext * const m_context; + JSValue m_value; }; +class ScopedJsValueList +{ +public: + ScopedJsValueList(JSContext *ctx, const JSValueList &l) : m_context(ctx), m_list(l) {} + ~ScopedJsValueList() { for (const JSValue v : m_list) JS_FreeValue(m_context, v); } + operator JSValueList() const { return m_list; } + + ScopedJsValueList(const ScopedJsValueList &) = delete; + ScopedJsValueList &operator=(const ScopedJsValueList &) = delete; + + ScopedJsValueList(ScopedJsValueList && other) noexcept + : m_context(other.m_context), m_list(std::move(other.m_list)) + { + other.m_list.clear(); + } + +private: + JSContext * const m_context; + JSValueList m_list; +}; + +class ScopedJsAtom +{ +public: + ScopedJsAtom(JSContext *ctx, JSAtom a) : m_context(ctx), m_atom(a) {} + ScopedJsAtom(JSContext *ctx, const QByteArray &s) + : ScopedJsAtom(ctx, JS_NewAtom(ctx, s.constData())) {} + ScopedJsAtom(JSContext *ctx, const QString &s) : ScopedJsAtom(ctx, s.toUtf8()) {} + ~ScopedJsAtom() { JS_FreeAtom(m_context, m_atom); } + operator JSAtom() const { return m_atom; } + + ScopedJsAtom(const ScopedJsAtom &) = delete; + ScopedJsAtom&operator=(const ScopedJsAtom &) = delete; + + ScopedJsAtom(ScopedJsAtom &&other) : m_context(other.m_context), m_atom(other.m_atom) + { + other.m_atom = 0; + } + +private: + JSContext * const m_context; + JSAtom m_atom; +}; + +class QBS_AUTOTEST_EXPORT JsException +{ +public: + JsException(JSContext *ctx, JSValue ex, const CodeLocation &fallbackLocation) + : m_ctx(ctx), m_exception(ex), m_fallbackLocation(fallbackLocation) {} + JsException(JsException && other) noexcept; + ~JsException(); + JsException(const JsException &) = delete; + JsException &operator=(const JsException &) = delete; + + operator bool() const { return !JS_IsNull(m_exception); } + QString message() const; + const QStringList stackTrace() const; + ErrorInfo toErrorInfo() const; +private: + JSContext *m_ctx; + JSValue m_exception; + CodeLocation m_fallbackLocation; +}; + +void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value); +QVariant QBS_AUTOTEST_EXPORT getConfigProperty(const QVariantMap &cfg, const QStringList &name); + } // namespace Internal } // namespace qbs +// Only to be used for objects! +#ifndef JS_NAN_BOXING +inline bool operator==(JSValue v1, JSValue v2) { return v1.u.ptr == v2.u.ptr; } +inline bool operator!=(JSValue v1, JSValue v2) { return !(v1 == v2); } +inline bool operator<(JSValue v1, JSValue v2) { return v1.u.ptr < v2.u.ptr; } +inline qbs::QHashValueType qHash(const JSValue &v) { return QT_PREPEND_NAMESPACE(qHash)(v.u.ptr); } +#endif + #endif // QBS_SCRIPTTOOLS_H diff --git a/src/lib/corelib/tools/set.h b/src/lib/corelib/tools/set.h index d2af77d73..461175615 100644 --- a/src/lib/corelib/tools/set.h +++ b/src/lib/corelib/tools/set.h @@ -42,6 +42,7 @@ #include <tools/dynamictypecheck.h> #include <tools/persistence.h> +#include <tools/stlutils.h> #ifdef QT_CORE_LIB #include <QtCore/qstringlist.h> @@ -100,6 +101,8 @@ public: Set() = default; Set(const std::initializer_list<T> &list); + template<typename InputIterator> + Set(InputIterator first, InputIterator last); Set &unite(const Set &other); Set &operator+=(const Set &other) { return unite(other); } @@ -112,8 +115,8 @@ public: Set &operator&=(const Set &other) { return intersect(other); } Set &operator&=(const T &v) { return intersect(Set{ v }); } - iterator find(const T &v) { return std::find(m_data.begin(), m_data.end(), v); } - const_iterator find(const T &v) const { return std::find(m_data.cbegin(), m_data.cend(), v); } + iterator find(const T &v) { return binaryFind(m_data.begin(), m_data.end(), v); } + const_iterator find(const T &v) const { return binaryFind(m_data.cbegin(), m_data.cend(), v); } std::pair<iterator, bool> insert(const T &v); Set &operator+=(const T &v) { insert(v); return *this; } Set &operator|=(const T &v) { return operator+=(v); } @@ -143,15 +146,8 @@ public: QStringList toStringList() const; QString toString(const T& value) const { return value.toString(); } QString toString() const; - - static Set<T> fromList(const QList<T> &list); - QList<T> toList() const; #endif - static Set<T> fromStdVector(const std::vector<T> &vector); - static Set<T> fromStdSet(const std::set<T> &set); - std::set<T> toStdSet() const; - template<typename U> static Set<T> filtered(const Set<U> &s); bool operator==(const Set &other) const { return m_data == other.m_data; } @@ -177,6 +173,15 @@ template<typename T> Set<T>::Set(const std::initializer_list<T> &list) : m_data( m_data.erase(last, m_data.end()); } +template<typename T> +template<typename InputIterator> +Set<T>::Set(InputIterator first, InputIterator last) +{ + reserveIfForwardIterator(&m_data, first, last); + std::copy(first, last, std::back_inserter(m_data)); + sort(); +} + template<typename T> Set<T> &Set<T>::intersect(const Set<T> &other) { auto it = begin(); @@ -287,11 +292,7 @@ template<typename T> void Set<T>::store(PersistentPool &pool) const #ifdef QT_CORE_LIB template<typename T> QStringList Set<T>::toStringList() const { - QStringList sl; - sl.reserve(int(size())); - std::transform(cbegin(), cend(), std::back_inserter(sl), - [this](const T &e) { return toString(e); }); - return sl; + return transformed<QStringList>(*this, [this](const T &e) { return toString(e); }); } template<typename T> QString Set<T>::toString() const @@ -301,45 +302,8 @@ template<typename T> QString Set<T>::toString() const template<> inline QString Set<QString>::toString(const QString &value) const { return value; } -template<typename T> Set<T> Set<T>::fromList(const QList<T> &list) -{ - Set<T> s; - std::copy(list.cbegin(), list.cend(), std::back_inserter(s.m_data)); - s.sort(); - return s; -} - -template<typename T> QList<T> Set<T>::toList() const -{ - QList<T> list; - std::copy(m_data.cbegin(), m_data.cend(), std::back_inserter(list)); - return list; -} #endif -template<typename T> Set<T> Set<T>::fromStdVector(const std::vector<T> &vector) -{ - Set<T> s; - std::copy(vector.cbegin(), vector.cend(), std::back_inserter(s.m_data)); - s.sort(); - return s; -} - -template<typename T> Set<T> Set<T>::fromStdSet(const std::set<T> &set) -{ - Set<T> s; - std::copy(set.cbegin(), set.cend(), std::back_inserter(s.m_data)); - return s; -} - -template<typename T> std::set<T> Set<T>::toStdSet() const -{ - std::set<T> set; - for (auto it = cbegin(); it != cend(); ++it) - set.insert(*it); - return set; -} - template<typename T> typename Set<T>::iterator Set<T>::asMutableIterator(typename Set<T>::const_iterator cit) { @@ -349,8 +313,8 @@ typename Set<T>::iterator Set<T>::asMutableIterator(typename Set<T>::const_itera template<typename T> template<typename U> Set<T> Set<T>::filtered(const Set<U> &s) { - static_assert(std::is_pointer<T>::value, "Set::filtered() assumes pointer types"); - static_assert(std::is_pointer<U>::value, "Set::filtered() assumes pointer types"); + static_assert(std::is_pointer_v<T>, "Set::filtered() assumes pointer types"); + static_assert(std::is_pointer_v<U>, "Set::filtered() assumes pointer types"); Set<T> filteredSet; for (auto &u : s) { if (hasDynamicType<std::remove_pointer_t<T>>(u)) diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp index 7312cd622..4a3d9d727 100644 --- a/src/lib/corelib/tools/settings.cpp +++ b/src/lib/corelib/tools/settings.cpp @@ -91,8 +91,8 @@ Settings::Settings(const QString &baseDir) : Settings(baseDir, systemSettingsBas Settings::Settings(const QString &baseDir, const QString &systemBaseDir) : m_settings(SettingsCreator(baseDir).getQSettings()), - m_systemSettings(new QSettings(systemBaseDir + QStringLiteral("/qbs.conf"), - QSettings::IniFormat)), + m_systemSettings(std::make_unique<QSettings>(systemBaseDir + QStringLiteral("/qbs.conf"), + QSettings::IniFormat)), m_baseDir(baseDir) { // Actual qbs settings are stored transparently within a group, because QSettings @@ -100,11 +100,7 @@ Settings::Settings(const QString &baseDir, const QString &systemBaseDir) m_settings->beginGroup(QStringLiteral("org/qt-project/qbs")); } -Settings::~Settings() -{ - delete m_settings; - delete m_systemSettings; -} +Settings::~Settings() = default; QVariant Settings::value(const QString &key, Scopes scopes, const QVariant &defaultValue) const { @@ -121,9 +117,9 @@ QVariant Settings::value(const QString &key, Scopes scopes, const QVariant &defa } if (!systemValue.isValid()) return userValue; - if (static_cast<QMetaType::Type>(userValue.type()) == QMetaType::QStringList) + if (static_cast<QMetaType::Type>(userValue.userType()) == QMetaType::QStringList) return userValue.toStringList() + systemValue.toStringList(); - if (static_cast<QMetaType::Type>(userValue.type()) == QMetaType::QVariantList) + if (static_cast<QMetaType::Type>(userValue.userType()) == QMetaType::QVariantList) return userValue.toList() + systemValue.toList(); return userValue; } @@ -139,13 +135,22 @@ QStringList Settings::allKeys(Scopes scopes) const return keys; } -QStringList Settings::directChildren(const QString &parentGroup, Scope scope) const +QStringList Settings::directChildren(const QString &parentGroup, Scopes scopes) const { - QSettings * const settings = settingsForScope(scope); - settings->beginGroup(internalRepresentation(parentGroup)); - QStringList children = settings->childGroups(); - children << settings->childKeys(); - settings->endGroup(); + auto helper = [this, &parentGroup](const Scope scope) { + QSettings * const settings = settingsForScope(scope); + settings->beginGroup(internalRepresentation(parentGroup)); + QStringList children = settings->childGroups(); + children << settings->childKeys(); + settings->endGroup(); + return children; + }; + + QStringList children; + if (scopes & UserScope) + children += helper(UserScope); + if (scopes & SystemScope) + children += helper(SystemScope); fixupKeys(children); return children; } @@ -241,7 +246,7 @@ void Settings::fixupKeys(QStringList &keys) const QSettings *Settings::settingsForScope(Settings::Scope scope) const { - return scope == UserScope ? m_settings : m_systemSettings; + return scope == UserScope ? m_settings.get() : m_systemSettings.get(); } QSettings *Settings::targetForWriting() const diff --git a/src/lib/corelib/tools/settings.h b/src/lib/corelib/tools/settings.h index d4de08490..7c9c52c9e 100644 --- a/src/lib/corelib/tools/settings.h +++ b/src/lib/corelib/tools/settings.h @@ -43,11 +43,13 @@ #include "qbs_export.h" #include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> #include <QtCore/qvariant.h> +#include <memory> + QT_BEGIN_NAMESPACE class QSettings; -class QStringList; QT_END_NAMESPACE namespace qbs { @@ -68,7 +70,7 @@ public: QVariant value(const QString &key, Scopes scopes, const QVariant &defaultValue = QVariant()) const; QStringList allKeys(Scopes scopes) const; - QStringList directChildren(const QString &parentGroup, Scope scope) const; // Keys and groups. + QStringList directChildren(const QString &parentGroup, Scopes scopes) const; // Keys and groups. QStringList allKeysWithPrefix(const QString &group, Scopes scopes) const; void setValue(const QString &key, const QVariant &value); void remove(const QString &key); @@ -94,8 +96,8 @@ private: QSettings *targetForWriting() const; void checkForWriteError(); - QSettings * const m_settings; - QSettings * const m_systemSettings; + const std::unique_ptr<QSettings> m_settings; + const std::unique_ptr<QSettings> m_systemSettings; const QString m_baseDir; Scope m_scopeForWriting = UserScope; }; diff --git a/src/lib/corelib/tools/settingscreator.cpp b/src/lib/corelib/tools/settingscreator.cpp index b7c727cd0..2cef0e5ce 100644 --- a/src/lib/corelib/tools/settingscreator.cpp +++ b/src/lib/corelib/tools/settingscreator.cpp @@ -53,23 +53,35 @@ namespace qbs { namespace Internal { +namespace { +QString getBaseDir(QString baseDir) { + if (!baseDir.isEmpty()) + return baseDir; + + const char key[] = "QBS_SETTINGS_DIR"; + if (qEnvironmentVariableIsSet(key)) + return QLatin1String(qgetenv(key)); + + return {}; +} +} // namespace + static QSettings::Format format() { return HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat; } - SettingsCreator::SettingsCreator(QString baseDir) - : m_settingsBaseDir(std::move(baseDir)) + : m_settingsBaseDir(getBaseDir(std::move(baseDir))) , m_qbsVersion(Version::fromString(QLatin1String(QBS_VERSION))) { } -QSettings *SettingsCreator::getQSettings() +std::unique_ptr<QSettings> SettingsCreator::getQSettings() { createQSettings(); migrate(); - return m_settings.release(); + return std::move(m_settings); } void SettingsCreator::migrate() @@ -84,46 +96,15 @@ void SettingsCreator::migrate() QString oldSettingsDir = m_settingsBaseDir; if (thePredecessor.isValid()) oldSettingsDir.append(QLatin1String("/qbs/")).append(thePredecessor.toString()); - QString oldProfilesDir = oldSettingsDir; - if (!thePredecessor.isValid()) - oldProfilesDir += QLatin1String("/qbs"); - oldProfilesDir += QLatin1String("/profiles"); - const QString newProfilesDir = m_newSettingsDir + QLatin1String("/profiles"); - QString errorMessage; - if (QFileInfo(oldProfilesDir).exists() - && !copyFileRecursion(oldProfilesDir, newProfilesDir, false, true, &errorMessage)) { - qWarning() << "Error in settings migration: " << errorMessage; - } const QString oldSettingsFilePath = oldSettingsDir + QLatin1Char('/') + m_settingsFileName; - if (QFileInfo(oldSettingsFilePath).exists() + if (QFileInfo::exists(oldSettingsFilePath) && (!QDir::root().mkpath(m_newSettingsDir) || !QFile::copy(oldSettingsFilePath, m_newSettingsFilePath))) { qWarning() << "Error in settings migration: Could not copy" << oldSettingsFilePath << "to" << m_newSettingsFilePath; } - // Adapt all paths in settings that point to the old location. At the time of this writing, - // that's only preferences.qbsSearchPaths as written by libqtprofilesetup, but we don't want - // to hardcode that here. m_settings = std::make_unique<QSettings>(m_newSettingsFilePath, format()); - const auto allKeys = m_settings->allKeys(); - for (const QString &key : allKeys) { - QVariant v = m_settings->value(key); - if (v.type() == QVariant::String) { - QString s = v.toString(); - if (s.contains(oldProfilesDir)) - m_settings->setValue(key, s.replace(oldProfilesDir, newProfilesDir)); - } else if (v.type() == QVariant::StringList) { - const QStringList oldList = v.toStringList(); - QStringList newList; - for (const QString &oldString : oldList) { - QString newString = oldString; - newList << newString.replace(oldProfilesDir, newProfilesDir); - } - if (newList != oldList) - m_settings->setValue(key, newList); - } - } } void SettingsCreator::createQSettings() diff --git a/src/lib/corelib/tools/settingscreator.h b/src/lib/corelib/tools/settingscreator.h index 39da80a7f..ab491105c 100644 --- a/src/lib/corelib/tools/settingscreator.h +++ b/src/lib/corelib/tools/settingscreator.h @@ -58,7 +58,7 @@ class SettingsCreator public: SettingsCreator(QString baseDir); - QSettings *getQSettings(); + std::unique_ptr<QSettings> getQSettings(); private: void migrate(); diff --git a/src/lib/corelib/tools/settingsmodel.cpp b/src/lib/corelib/tools/settingsmodel.cpp index 7283e959c..4fa0e14da 100644 --- a/src/lib/corelib/tools/settingsmodel.cpp +++ b/src/lib/corelib/tools/settingsmodel.cpp @@ -42,6 +42,7 @@ #include <tools/profile.h> #include <tools/qttools.h> #include <tools/settings.h> +#include <tools/stlutils.h> #include <tools/stringconstants.h> #include <QtCore/qlist.h> @@ -77,7 +78,7 @@ QString Node::uniqueChildName() const bool unique; do { unique = true; - for (const Node *childNode : qAsConst(children)) { + for (const Node *childNode : std::as_const(children)) { if (childNode->name == newName) { unique = false; newName += QLatin1Char('_'); @@ -90,11 +91,9 @@ QString Node::uniqueChildName() const bool Node::hasDirectChildWithName(const QString &name) const { - for (const Node * const child : qAsConst(children)) { - if (child->name == name) - return true; - } - return false; + return Internal::any_of(children, [&name](const auto &child){ + return child->name == name; + }); } } // namespace Internal @@ -123,17 +122,15 @@ public: }; SettingsModel::SettingsModel(const QString &settingsDir, Settings::Scope scope, QObject *parent) - : QAbstractItemModel(parent), d(new SettingsModelPrivate) + : QAbstractItemModel(parent), + d(std::make_unique<SettingsModelPrivate>()) { d->settings = std::make_unique<qbs::Settings>(settingsDir); d->settings->setScopeForWriting(scope); d->readSettings(); } -SettingsModel::~SettingsModel() -{ - delete d; -} +SettingsModel::~SettingsModel() = default; void SettingsModel::reload() { @@ -284,10 +281,10 @@ bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int const QString valueString = value.toString(); QString *toChange = nullptr; if (index.column() == keyColumn() && !valueString.isEmpty() - && !node->parent->hasDirectChildWithName(valueString) - && !(node->parent->parent == &d->rootNode - && node->parent->name == Internal::StringConstants::profilesSettingsKey() - && valueString == Profile::fallbackName())) { + && !node->parent->hasDirectChildWithName(valueString) + && (node->parent->parent != &d->rootNode + || node->parent->name != Internal::StringConstants::profilesSettingsKey() + || valueString != Profile::fallbackName())) { toChange = &node->name; } else if (index.column() == valueColumn() && valueString != node->value) { toChange = &node->value; @@ -332,7 +329,7 @@ void SettingsModel::SettingsModelPrivate::readSettings() addNodeFromSettings(&rootNode, topLevelKey); for (QVariantMap::ConstIterator it = additionalProperties.constBegin(); it != additionalProperties.constEnd(); ++it) { - const QStringList nameAsList = it.key().split(QLatin1Char('.'), QString::SkipEmptyParts); + const QStringList nameAsList = it.key().split(QLatin1Char('.'), Qt::SkipEmptyParts); addNode(&rootNode, nameAsList.front(), nameAsList.mid(1), it.value()); } dirty = false; @@ -364,7 +361,7 @@ void SettingsModel::SettingsModelPrivate::addNode(qbs::Internal::Node *parentNod const QString ¤tNamePart, const QStringList &restOfName, const QVariant &value) { Node *currentNode = nullptr; - for (Node * const n : qAsConst(parentNode->children)) { + for (Node * const n : std::as_const(parentNode->children)) { if (n->name == currentNamePart) { currentNode = n; break; @@ -388,7 +385,7 @@ void SettingsModel::SettingsModelPrivate::doSave(const Node *node, const QString } const QString newPrefix = prefix + node->name + QLatin1Char('.'); - for (const Node * const child : qAsConst(node->children)) + for (const Node * const child : std::as_const(node->children)) doSave(child, newPrefix); } diff --git a/src/lib/corelib/tools/settingsmodel.h b/src/lib/corelib/tools/settingsmodel.h index 1bd59737c..63651d300 100644 --- a/src/lib/corelib/tools/settingsmodel.h +++ b/src/lib/corelib/tools/settingsmodel.h @@ -46,6 +46,8 @@ #include <QtCore/qabstractitemmodel.h> #include <QtCore/qvariant.h> +#include <memory> + namespace qbs { class QBS_EXPORT SettingsModel : public QAbstractItemModel @@ -81,7 +83,7 @@ public: private: class SettingsModelPrivate; - SettingsModelPrivate * const d; + const std::unique_ptr<SettingsModelPrivate> d; }; } // namespace qbs diff --git a/src/lib/corelib/tools/settingsrepresentation.cpp b/src/lib/corelib/tools/settingsrepresentation.cpp index 256c60c0e..6dfd81beb 100644 --- a/src/lib/corelib/tools/settingsrepresentation.cpp +++ b/src/lib/corelib/tools/settingsrepresentation.cpp @@ -41,8 +41,8 @@ #include "jsliterals.h" -#include <QtScript/qscriptengine.h> -#include <QtScript/qscriptvalue.h> +#include <language/scriptengine.h> +#include <logging/logger.h> namespace qbs { @@ -54,11 +54,19 @@ QString settingsValueToRepresentation(const QVariant &value) static QVariant variantFromString(const QString &str, bool &ok) { // ### use Qt5's JSON reader at some point. - QScriptEngine engine; - QScriptValue sv = engine.evaluate(QLatin1String("(function(){return ") - + str + QLatin1String(";})()")); - ok = !sv.isError(); - return sv.toVariant(); + class DummyLogSink : public ILogSink { + void doPrintMessage(LoggerLevel, const QString &, const QString &) override { } + } logSink; + Internal::Logger logger(&logSink); + + const auto engine = Internal::ScriptEngine::create(logger, {}); + Internal::ScopedJsValue sv( + engine->context(), + engine->evaluate(Internal::JsValueOwner::Caller, + QLatin1String("(function(){return ") + str + + QLatin1String(";})()"))); + ok = !engine->checkAndClearException({}); + return Internal::getJsVariant(engine->context(), sv); } QVariant representationToSettingsValue(const QString &representation) @@ -67,8 +75,8 @@ QVariant representationToSettingsValue(const QString &representation) QVariant variant = variantFromString(representation, ok); // We have no floating-point properties, so this is most likely intended to be a string. - if (static_cast<QMetaType::Type>(variant.type()) == QMetaType::Float - || static_cast<QMetaType::Type>(variant.type()) == QMetaType::Double) { + if (static_cast<QMetaType::Type>(variant.userType()) == QMetaType::Float + || static_cast<QMetaType::Type>(variant.userType()) == QMetaType::Double) { variant = variantFromString(QLatin1Char('"') + representation + QLatin1Char('"'), ok); } diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp index 996f6b273..e9212c165 100644 --- a/src/lib/corelib/tools/setupprojectparameters.cpp +++ b/src/lib/corelib/tools/setupprojectparameters.cpp @@ -38,6 +38,8 @@ ****************************************************************************/ #include "setupprojectparameters.h" +#include "buildoptions.h" + #include <logging/logger.h> #include <logging/translator.h> #include <tools/buildgraphlocker.h> @@ -45,8 +47,10 @@ #include <tools/jsonhelper.h> #include <tools/profile.h> #include <tools/qbsassert.h> +#include <tools/qttools.h> #include <tools/scripttools.h> #include <tools/settings.h> +#include <tools/stringconstants.h> #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> @@ -89,21 +93,24 @@ public: mutable QVariantMap buildConfigurationTree; mutable QVariantMap overriddenValuesTree; mutable QVariantMap finalBuildConfigTree; + int maxJobCount = 0; bool overrideBuildGraphData; bool dryRun; bool logElapsedTime; bool forceProbeExecution; bool waitLockBuildGraph; - bool fallbackProviderEnabled = true; SetupProjectParameters::RestoreBehavior restoreBehavior; ErrorHandlingMode propertyCheckingMode; ErrorHandlingMode productErrorMode; + DeprecationWarningMode deprecationWarningMode = defaultDeprecationWarningMode(); QProcessEnvironment environment; }; } // namespace Internal -SetupProjectParameters::SetupProjectParameters() : d(new Internal::SetupProjectParametersPrivate) +using namespace Internal; + +SetupProjectParameters::SetupProjectParameters() : d(new SetupProjectParametersPrivate) { } @@ -125,6 +132,11 @@ template<> ErrorHandlingMode fromJson(const QJsonValue &v) return ErrorHandlingMode::Strict; } +template<> DeprecationWarningMode fromJson(const QJsonValue &v) +{ + return deprecationWarningModeFromName(v.toString()); +} + template<> SetupProjectParameters::RestoreBehavior fromJson(const QJsonValue &v) { const QString value = v.toString(); @@ -145,15 +157,18 @@ SetupProjectParameters SetupProjectParameters::fromJson(const QJsonObject &data) setValueFromJson(params.d->projectFilePath, data, "project-file-path"); setValueFromJson(params.d->buildRoot, data, "build-root"); setValueFromJson(params.d->settingsBaseDir, data, "settings-directory"); + setValueFromJson(params.d->maxJobCount, data, "max-job-count"); + if (params.maxJobCount() <= 0) + params.setMaxJobCount(BuildOptions::defaultMaxJobCount()); setValueFromJson(params.d->overriddenValues, data, "overridden-properties"); setValueFromJson(params.d->dryRun, data, "dry-run"); setValueFromJson(params.d->logElapsedTime, data, "log-time"); setValueFromJson(params.d->forceProbeExecution, data, "force-probe-execution"); setValueFromJson(params.d->waitLockBuildGraph, data, "wait-lock-build-graph"); - setValueFromJson(params.d->fallbackProviderEnabled, data, "fallback-provider-enabled"); setValueFromJson(params.d->environment, data, "environment"); setValueFromJson(params.d->restoreBehavior, data, "restore-behavior"); setValueFromJson(params.d->propertyCheckingMode, data, "error-handling-mode"); + setValueFromJson(params.d->deprecationWarningMode, data, "deprecation-warning-mode"); params.d->productErrorMode = params.d->propertyCheckingMode; return params; } @@ -219,6 +234,44 @@ void SetupProjectParameters::setProjectFilePath(const QString &projectFilePath) d->projectFilePath = canonicalProjectFilePath; } +void SetupProjectParameters::finalizeProjectFilePath() +{ + QString filePath = projectFilePath(); + if (filePath.isEmpty()) + filePath = QDir::currentPath(); + const QFileInfo projectFileInfo(filePath); + if (!projectFileInfo.exists()) + throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(filePath)); + if (projectFileInfo.isRelative()) + filePath = projectFileInfo.absoluteFilePath(); + if (projectFileInfo.isFile()) { + setProjectFilePath(filePath); + return; + } + if (!projectFileInfo.isDir()) + throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(filePath)); + + const QStringList &actualFileNames + = QDir(filePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); + if (actualFileNames.empty()) { + QString error; + if (projectFilePath().isEmpty()) + error = Tr::tr("No project file given and none found in current directory.\n"); + else + error = Tr::tr("No project file found in directory '%1'.").arg(filePath); + throw ErrorInfo(error); + } + if (actualFileNames.size() > 1) { + throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") + .arg(filePath)); + } + filePath.append(QLatin1Char('/')).append(actualFileNames.front()); + + filePath = QDir::current().filePath(filePath); + filePath = QDir::cleanPath(filePath); + setProjectFilePath(filePath); +} + /*! * \brief Returns the base path of where to put the build artifacts and store the build graph. */ @@ -243,7 +296,7 @@ void SetupProjectParameters::setBuildRoot(const QString &buildRoot) // Calling mkpath() may be necessary to get the canonical build root, but if we do it, // it must be reverted immediately afterwards as not to create directories needlessly, // e.g in the case of a dry run build. - Internal::DirectoryManager dirManager(buildRoot, Internal::Logger()); + DirectoryManager dirManager(buildRoot, Logger()); // We don't do error checking here, as this is not a convenient place to report an error. // If creation of the build directory is not possible, we will get sensible error messages @@ -325,6 +378,27 @@ void SetupProjectParameters::setSettingsDirectory(const QString &settingsBaseDir } /*! + * \brief Returns the maximum number of threads to employ when resolving the project. + * If the value is not valid (i.e. <= 0), a sensible one will be derived from the number of + * available processor cores. + * The default is 0. + * \sa BuildOptions::defaultMaxJobCount + */ +int SetupProjectParameters::maxJobCount() const +{ + return d->maxJobCount; +} + +/*! + * \brief Controls how many threads to employ when resolving the project. + * A value <= 0 leaves the decision to qbs. + */ +void SetupProjectParameters::setMaxJobCount(int jobCount) +{ + d->maxJobCount = jobCount; +} + +/*! * Returns the overridden values of the build configuration. */ QVariantMap SetupProjectParameters::overriddenValues() const @@ -349,12 +423,12 @@ static void provideValuesTree(const QVariantMap &values, QVariantMap *valueTree) valueTree->clear(); for (QVariantMap::const_iterator it = values.constBegin(); it != values.constEnd(); ++it) { - const QString name = it.key(); + const QString &name = it.key(); int idx = name.lastIndexOf(QLatin1Char('.')); const QStringList nameElements = (idx == -1) ? QStringList() << name : QStringList() << name.left(idx) << name.mid(idx + 1); - Internal::setConfigProperty(*valueTree, nameElements, it.value()); + setConfigProperty(*valueTree, nameElements, it.value()); } } @@ -395,7 +469,7 @@ static QVariantMap expandedBuildConfigurationInternal(const Profile &profile, if (err.hasError()) throw err; if (profileKeys.empty()) - throw ErrorInfo(Internal::Tr::tr("Unknown or empty profile '%1'.").arg(profile.name())); + throw ErrorInfo(Tr::tr("Unknown or empty profile '%1'.").arg(profile.name())); for (const QString &profileKey : profileKeys) { buildConfig.insert(profileKey, profile.value(profileKey, QVariant(), &err)); if (err.hasError()) @@ -405,7 +479,7 @@ static QVariantMap expandedBuildConfigurationInternal(const Profile &profile, // (2) Build configuration name. if (configurationName.isEmpty()) - throw ErrorInfo(Internal::Tr::tr("No build configuration name set.")); + throw ErrorInfo(Tr::tr("No build configuration name set.")); buildConfig.insert(QStringLiteral("qbs.configurationName"), configurationName); return buildConfig; } @@ -442,7 +516,7 @@ ErrorInfo SetupProjectParameters::expandBuildConfiguration() QVariantMap expandedConfig = expandedBuildConfiguration(profile, configurationName(), &err); if (err.hasError()) return err; - if (d->buildConfiguration != expandedConfig) { + if (!qVariantMapsEqual(d->buildConfiguration, expandedConfig)) { d->buildConfigurationTree.clear(); d->buildConfiguration = expandedConfig; } @@ -547,22 +621,6 @@ void SetupProjectParameters::setWaitLockBuildGraph(bool wait) } /*! - * \brief Returns true if qbs should fall back to pkg-config if a dependency is not found. - */ -bool SetupProjectParameters::fallbackProviderEnabled() const -{ - return d->fallbackProviderEnabled; -} - -/*! - * Controls whether to fall back to pkg-config if a dependency is not found. - */ -void SetupProjectParameters::setFallbackProviderEnabled(bool enable) -{ - d->fallbackProviderEnabled = enable; -} - -/*! * \brief Gets the environment used while resolving the project. */ QProcessEnvironment SetupProjectParameters::environment() const @@ -688,4 +746,20 @@ void SetupProjectParameters::setProductErrorMode(ErrorHandlingMode mode) d->productErrorMode = mode; } +/*! + * \brief Indicates how deprecated constructs are handled. + */ +DeprecationWarningMode SetupProjectParameters::deprecationWarningMode() const +{ + return d->deprecationWarningMode; +} + +/*! + * \brief Specifies the behavior on encountering deprecated constructs. + */ +void SetupProjectParameters::setDeprecationWarningMode(DeprecationWarningMode mode) +{ + d->deprecationWarningMode = mode; +} + } // namespace qbs diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h index a4d090ec5..5cd9700a9 100644 --- a/src/lib/corelib/tools/setupprojectparameters.h +++ b/src/lib/corelib/tools/setupprojectparameters.h @@ -41,13 +41,14 @@ #include "qbs_export.h" +#include <tools/deprecationwarningmode.h> #include <tools/error.h> #include <QtCore/qshareddata.h> +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QProcessEnvironment; -class QStringList; using QVariantMap = QMap<QString, QVariant>; QT_END_NAMESPACE @@ -81,6 +82,7 @@ public: QString projectFilePath() const; void setProjectFilePath(const QString &projectFilePath); + void finalizeProjectFilePath(); QString buildRoot() const; void setBuildRoot(const QString &buildRoot); @@ -97,6 +99,9 @@ public: QString settingsDirectory() const; void setSettingsDirectory(const QString &settingsBaseDir); + int maxJobCount() const; + void setMaxJobCount(int jobCount); + QVariantMap overriddenValues() const; void setOverriddenValues(const QVariantMap &values); QVariantMap overriddenValuesTree() const; @@ -127,9 +132,6 @@ public: bool waitLockBuildGraph() const; void setWaitLockBuildGraph(bool wait); - bool fallbackProviderEnabled() const; - void setFallbackProviderEnabled(bool enable); - QProcessEnvironment environment() const; void setEnvironment(const QProcessEnvironment &env); QProcessEnvironment adjustedEnvironment() const; @@ -144,6 +146,9 @@ public: ErrorHandlingMode productErrorMode() const; void setProductErrorMode(ErrorHandlingMode mode); + DeprecationWarningMode deprecationWarningMode() const; + void setDeprecationWarningMode(DeprecationWarningMode mode); + private: QSharedDataPointer<Internal::SetupProjectParametersPrivate> d; }; diff --git a/src/lib/corelib/tools/shellutils.cpp b/src/lib/corelib/tools/shellutils.cpp index dae98f337..5fff254f6 100644 --- a/src/lib/corelib/tools/shellutils.cpp +++ b/src/lib/corelib/tools/shellutils.cpp @@ -39,9 +39,12 @@ ****************************************************************************/ #include "shellutils.h" + #include "pathutils.h" +#include "qttools.h" + #include <QtCore/qfile.h> -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qtextstream.h> namespace qbs { @@ -53,8 +56,8 @@ QString shellInterpreter(const QString &filePath) { QTextStream ts(&file); const QString shebang = ts.readLine(); if (shebang.startsWith(QLatin1String("#!"))) { - return (shebang.mid(2).split(QRegExp(QStringLiteral("\\s")), - QString::SkipEmptyParts) << QString()).front(); + return (shebang.mid(2).split(QRegularExpression(QStringLiteral("\\s")), + Qt::SkipEmptyParts) << QString()).front(); } } @@ -66,9 +69,7 @@ QString shellInterpreter(const QString &filePath) { inline static bool isSpecialChar(ushort c, const uchar (&iqm)[16]) { - if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) - return true; - return false; + return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) @@ -124,9 +125,9 @@ static QString shellQuoteWin(const QString &arg) // The process-level standard quoting allows escaping quotes with backslashes (note // that backslashes don't escape themselves, unless they are followed by a quote). // Consequently, quotes are escaped and their preceding backslashes are doubled. - ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\"")); + ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\"")); // Trailing backslashes must be doubled as well, as they are followed by a quote. - ret.replace(QRegExp(QLatin1String("(\\\\+)$")), QLatin1String("\\1\\1")); + ret.replace(QRegularExpression(QLatin1String("(\\\\+)$")), QLatin1String("\\1\\1")); // However, the shell also interprets the command, and no backslash-escaping exists // there - a quote always toggles the quoting state, but is nonetheless passed down // to the called process verbatim. In the unquoted state, the circumflex escapes diff --git a/src/lib/corelib/tools/shellutils.h b/src/lib/corelib/tools/shellutils.h index 6f1d82afb..f4ad35044 100644 --- a/src/lib/corelib/tools/shellutils.h +++ b/src/lib/corelib/tools/shellutils.h @@ -77,7 +77,7 @@ public: private: struct Argument { - Argument(const QString &value = QString()) : value(value) { } + Argument(QString value = QString()) : value(std::move(value)) { } QString value; bool isFilePath = false; bool shouldQuote = true; diff --git a/src/lib/corelib/tools/stlutils.h b/src/lib/corelib/tools/stlutils.h index ad00070cf..306f37157 100644 --- a/src/lib/corelib/tools/stlutils.h +++ b/src/lib/corelib/tools/stlutils.h @@ -46,19 +46,58 @@ namespace qbs { namespace Internal { -template <class C> -C sorted(const C &container) +template <typename C> +auto sorted(C &&container) { - C result = container; + using R = std::remove_cv_t<std::remove_reference_t<C>>; + R result(std::forward<C>(container)); std::sort(std::begin(result), std::end(result)); return result; } -template <class C> -bool contains(const C &container, const typename C::value_type &v) +template <typename C, typename Pred> +auto sorted(C &&container, Pred &&pred) { - const auto &end = container.cend(); - return std::find(container.cbegin(), end, v) != end; + using R = std::remove_cv_t<std::remove_reference_t<C>>; + R result(std::forward<C>(container)); + std::sort(std::begin(result), std::end(result), std::forward<Pred>(pred)); + return result; +} + +template <typename To, typename From, typename Op> +To transformed(const From &from, Op op) +{ + To to; + to.reserve(int(from.size())); + std::transform(std::cbegin(from), std::cend(from), std::back_inserter(to), std::move(op)); + return to; +} + +template <typename C, typename Op> +void transform(C &&container, Op op) +{ + std::transform(std::begin(container), std::end(container), std::begin(container), + std::move(op)); +} + +template <typename To, typename From, typename Op> +void transform(const From &from, To &&to, Op op) +{ + std::transform(std::cbegin(from), std::cend(from), std::back_inserter(to), std::move(op)); +} + +template <class C, class T> +bool contains(const C &container, const T &v) +{ + const auto &end = std::cend(container); + return std::find(std::cbegin(container), end, v) != end; +} + +template <class T, size_t N, class U> +bool contains(const T (&container)[N], const U &v) +{ + const auto &end = std::cend(container); + return std::find(std::cbegin(container), end, v) != end; } template <class C> @@ -68,6 +107,17 @@ bool containsKey(const C &container, const typename C::key_type &v) return container.find(v) != end; } +template <class C> +typename C::mapped_type mapValue( + const C &container, + const typename C::key_type &key, + const typename C::mapped_type &value = typename C::mapped_type()) +{ + const auto end = container.cend(); + const auto it = container.find(key); + return it != end ? it->second : value; +} + template <typename C> bool removeOne(C &container, const typename C::value_type &v) { @@ -86,6 +136,13 @@ void removeAll(C &container, const typename C::value_type &v) std::end(container)); } +template <typename C, typename Pred> +void removeIf(C &container, const Pred &pred) +{ + container.erase(std::remove_if(std::begin(container), std::end(container), pred), + std::end(container)); +} + template <class Container, class UnaryPredicate> bool any_of(const Container &container, const UnaryPredicate &predicate) { @@ -93,11 +150,32 @@ bool any_of(const Container &container, const UnaryPredicate &predicate) } template <class Container, class UnaryPredicate> +bool all_of(const Container &container, const UnaryPredicate &predicate) +{ + return std::all_of(std::begin(container), std::end(container), predicate); +} + +template <class Container, class UnaryPredicate> bool none_of(const Container &container, const UnaryPredicate &predicate) { return std::none_of(std::begin(container), std::end(container), predicate); } +template <class It, class T, class Compare> +It binaryFind(It begin, It end, const T &value, Compare comp) +{ + const auto it = std::lower_bound(begin, end, value, comp); + if (it == end || comp(value, *it)) + return end; + return it; +} + +template <class It, class T> +It binaryFind(It begin, It end, const T &value) +{ + return binaryFind(begin, end, value, std::less<T>()); +} + template <class C> C &operator<<(C &container, const typename C::value_type &v) { @@ -112,6 +190,79 @@ C &operator<<(C &container, const C &other) return container; } +// based on http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0814r0.pdf +template<typename T> +void hashCombineHelper(size_t &seed, const T &val) +{ + seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template<typename... Types> +size_t hashCombine(const Types &... args) +{ + size_t seed = 0; + (hashCombineHelper(seed, args), ...); // create hash value with seed over all args return seed; + return seed; +} + +template<typename It> +size_t hashRange(It first, It last) +{ + size_t seed = 0; + for (; first != last; ++first) + hashCombineHelper(seed, *first); + + return seed; +} + +template<typename R> +size_t hashRange(R &&range) +{ + return hashRange(std::begin(range), std::end(range)); +} + +// based on qcontainertools_impl.h +template <typename Iterator> +using IfIsForwardIterator_t = typename std::enable_if_t< + std::is_convertible_v< + typename std::iterator_traits<Iterator>::iterator_category, std::forward_iterator_tag>, + bool>; + +template <typename Iterator> +using IfIsNotForwardIterator = typename std::enable_if_t< + !std::is_convertible_v< + typename std::iterator_traits<Iterator>::iterator_category, std::forward_iterator_tag>, + bool>; + +template <typename Container, + typename InputIterator, + IfIsNotForwardIterator<InputIterator> = true> +void reserveIfForwardIterator(Container *, InputIterator, InputIterator) +{ +} + +template <typename Container, + typename ForwardIterator, + IfIsForwardIterator_t<ForwardIterator> = true> +void reserveIfForwardIterator(Container *c, ForwardIterator f, ForwardIterator l) +{ + c->reserve(static_cast<typename Container::size_type>(std::distance(f, l))); +} + +// similar to ranges::to proposal +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1206r1.pdf +template <class C, class R> +C rangeTo(R &&r) +{ + return C(std::begin(r), std::end(r)); +} + +template<class Enum> +constexpr std::underlying_type_t<Enum> toUnderlying(Enum e) noexcept +{ + return static_cast<std::underlying_type_t<Enum>>(e); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h index 79cbcd125..799a140d9 100644 --- a/src/lib/corelib/tools/stringconstants.h +++ b/src/lib/corelib/tools/stringconstants.h @@ -110,6 +110,7 @@ public: QBS_STRING_CONSTANT(installDirProperty, "installDir") QBS_STRING_CONSTANT(installSourceBaseProperty, "installSourceBase") QBS_STRING_CONSTANT(isEnabledKey, "is-enabled") + QBS_STRING_CONSTANT(isEagerProperty, "isEager") QBS_STRING_CONSTANT(jobCountProperty, "jobCount") QBS_STRING_CONSTANT(jobPoolProperty, "jobPool") QBS_STRING_CONSTANT(lengthProperty, "length") @@ -143,6 +144,7 @@ public: static const QString &profilesProperty() { return profiles(); } QBS_STRING_CONSTANT(productTypesProperty, "productTypes") QBS_STRING_CONSTANT(productsKey, "products") + QBS_STRING_CONSTANT(qbsModuleProviders, "qbsModuleProviders") QBS_STRING_CONSTANT(qbsSearchPathsProperty, "qbsSearchPaths") QBS_STRING_CONSTANT(referencesProperty, "references") QBS_STRING_CONSTANT(recursiveProperty, "recursive") @@ -153,6 +155,7 @@ public: QBS_STRING_CONSTANT(searchPathsProperty, "searchPaths") QBS_STRING_CONSTANT(setupBuildEnvironmentProperty, "setupBuildEnvironment") QBS_STRING_CONSTANT(setupRunEnvironmentProperty, "setupRunEnvironment") + QBS_STRING_CONSTANT(shadowProductPrefix, "__shadow__") QBS_STRING_CONSTANT(sourceCodeProperty, "sourceCode") QBS_STRING_CONSTANT(sourceDirectoryProperty, "sourceDirectory") QBS_STRING_CONSTANT(submodulesProperty, "submodules") @@ -166,6 +169,7 @@ public: QBS_STRING_CONSTANT(importScopeNamePropertyInternal, "_qbs_importScopeName") QBS_STRING_CONSTANT(modulePropertyInternal, "__module") + QBS_STRING_CONSTANT(dataPropertyInternal, "_qbs_data") QBS_STRING_CONSTANT(qbsSourceDirPropertyInternal, "_qbs_sourceDir") static const char *qbsProcEnvVarInternal() { return "_qbs_procenv"; } diff --git a/src/lib/corelib/tools/toolchains.cpp b/src/lib/corelib/tools/toolchains.cpp index 0d793f8aa..6263fb199 100644 --- a/src/lib/corelib/tools/toolchains.cpp +++ b/src/lib/corelib/tools/toolchains.cpp @@ -49,9 +49,11 @@ namespace qbs { namespace Internal { static const QString clangToolchain() { return QStringLiteral("clang"); } +static const QString clangClToolchain() { return QStringLiteral("clang-cl"); } static const QString gccToolchain() { return QStringLiteral("gcc"); } static const QString llvmToolchain() { return QStringLiteral("llvm"); } static const QString mingwToolchain() { return QStringLiteral("mingw"); } +static const QString msvcToolchain() { return QStringLiteral("msvc"); } } using namespace Internal; @@ -64,7 +66,8 @@ QStringList canonicalToolchain(const QStringList &toolchain) llvmToolchain(), mingwToolchain(), gccToolchain(), - QStringLiteral("msvc") + clangClToolchain(), + msvcToolchain() }; // Canonicalize each toolchain in the toolchain list, @@ -110,6 +113,8 @@ QStringList canonicalToolchain(const QString &name) else if (toolchainName == llvmToolchain() || toolchainName == mingwToolchain()) { toolchains << canonicalToolchain(QStringLiteral("gcc")); + } else if (toolchainName == clangClToolchain()) { + toolchains << canonicalToolchain(msvcToolchain()); } return toolchains; } diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri deleted file mode 100644 index 89d752671..000000000 --- a/src/lib/corelib/tools/tools.pri +++ /dev/null @@ -1,144 +0,0 @@ -include(../../../install_prefix.pri) - -INCLUDEPATH += $$PWD/../.. # for plugins - -QBS_SYSTEM_SETTINGS_DIR = $$(QBS_SYSTEM_SETTINGS_DIR) -!isEmpty(QBS_SYSTEM_SETTINGS_DIR) { - DEFINES += QBS_SYSTEM_SETTINGS_DIR=\\\"$$QBS_SYSTEM_SETTINGS_DIR\\\" -} - -HEADERS += \ - $$PWD/architectures.h \ - $$PWD/buildgraphlocker.h \ - $$PWD/codelocation.h \ - $$PWD/commandechomode.h \ - $$PWD/dynamictypecheck.h \ - $$PWD/error.h \ - $$PWD/executablefinder.h \ - $$PWD/fileinfo.h \ - $$PWD/filesaver.h \ - $$PWD/filetime.h \ - $$PWD/generateoptions.h \ - $$PWD/id.h \ - $$PWD/iosutils.h \ - $$PWD/joblimits.h \ - $$PWD/jsliterals.h \ - $$PWD/jsonhelper.h \ - $$PWD/launcherinterface.h \ - $$PWD/launcherpackets.h \ - $$PWD/launchersocket.h \ - $$PWD/msvcinfo.h \ - $$PWD/persistence.h \ - $$PWD/scannerpluginmanager.h \ - $$PWD/scripttools.h \ - $$PWD/set.h \ - $$PWD/settings.h \ - $$PWD/settingsmodel.h \ - $$PWD/settingsrepresentation.h \ - $$PWD/pathutils.h \ - $$PWD/preferences.h \ - $$PWD/profile.h \ - $$PWD/profiling.h \ - $$PWD/processresult.h \ - $$PWD/processresult_p.h \ - $$PWD/processutils.h \ - $$PWD/progressobserver.h \ - $$PWD/projectgeneratormanager.h \ - $$PWD/qbspluginmanager.h \ - $$PWD/qbsprocess.h \ - $$PWD/shellutils.h \ - $$PWD/stlutils.h \ - $$PWD/stringutils.h \ - $$PWD/toolchains.h \ - $$PWD/hostosinfo.h \ - $$PWD/buildoptions.h \ - $$PWD/installoptions.h \ - $$PWD/cleanoptions.h \ - $$PWD/setupprojectparameters.h \ - $$PWD/weakpointer.h \ - $$PWD/qbs_export.h \ - $$PWD/qbsassert.h \ - $$PWD/qttools.h \ - $$PWD/settingscreator.h \ - $$PWD/stringconstants.h \ - $$PWD/version.h \ - $$PWD/visualstudioversioninfo.h \ - $$PWD/vsenvironmentdetector.h - -SOURCES += \ - $$PWD/architectures.cpp \ - $$PWD/buildgraphlocker.cpp \ - $$PWD/codelocation.cpp \ - $$PWD/commandechomode.cpp \ - $$PWD/error.cpp \ - $$PWD/executablefinder.cpp \ - $$PWD/fileinfo.cpp \ - $$PWD/filesaver.cpp \ - $$PWD/filetime.cpp \ - $$PWD/generateoptions.cpp \ - $$PWD/id.cpp \ - $$PWD/joblimits.cpp \ - $$PWD/jsliterals.cpp \ - $$PWD/launcherinterface.cpp \ - $$PWD/launcherpackets.cpp \ - $$PWD/launchersocket.cpp \ - $$PWD/msvcinfo.cpp \ - $$PWD/persistence.cpp \ - $$PWD/scannerpluginmanager.cpp \ - $$PWD/scripttools.cpp \ - $$PWD/settings.cpp \ - $$PWD/settingsmodel.cpp \ - $$PWD/settingsrepresentation.cpp \ - $$PWD/preferences.cpp \ - $$PWD/processresult.cpp \ - $$PWD/processutils.cpp \ - $$PWD/profile.cpp \ - $$PWD/profiling.cpp \ - $$PWD/progressobserver.cpp \ - $$PWD/projectgeneratormanager.cpp \ - $$PWD/qbspluginmanager.cpp \ - $$PWD/qbsprocess.cpp \ - $$PWD/shellutils.cpp \ - $$PWD/buildoptions.cpp \ - $$PWD/installoptions.cpp \ - $$PWD/cleanoptions.cpp \ - $$PWD/setupprojectparameters.cpp \ - $$PWD/qbsassert.cpp \ - $$PWD/qttools.cpp \ - $$PWD/settingscreator.cpp \ - $$PWD/toolchains.cpp \ - $$PWD/version.cpp \ - $$PWD/visualstudioversioninfo.cpp \ - $$PWD/vsenvironmentdetector.cpp - -osx { - HEADERS += $$PWD/applecodesignutils.h - SOURCES += $$PWD/applecodesignutils.cpp - LIBS += -framework Security -} - -!qbs_no_dev_install { - tools_headers.files = \ - $$PWD/architectures.h \ - $$PWD/buildoptions.h \ - $$PWD/cleanoptions.h \ - $$PWD/codelocation.h \ - $$PWD/commandechomode.h \ - $$PWD/error.h \ - $$PWD/generateoptions.h \ - $$PWD/installoptions.h \ - $$PWD/joblimits.h \ - $$PWD/preferences.h \ - $$PWD/processresult.h \ - $$PWD/profile.h \ - $$PWD/projectgeneratormanager.h \ - $$PWD/qbs_export.h \ - $$PWD/settings.h \ - $$PWD/settingsmodel.h \ - $$PWD/settingsrepresentation.h \ - $$PWD/setupprojectparameters.h \ - $$PWD/toolchains.h \ - $$PWD/version.h - tools_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/tools - INSTALLS += tools_headers -} diff --git a/src/lib/corelib/tools/version.cpp b/src/lib/corelib/tools/version.cpp index f653256b3..719bc386f 100644 --- a/src/lib/corelib/tools/version.cpp +++ b/src/lib/corelib/tools/version.cpp @@ -39,7 +39,7 @@ #include "version.h" -#include <QtCore/qregexp.h> +#include <QtCore/qregularexpression.h> #include <QtCore/qstring.h> namespace qbs { @@ -51,13 +51,14 @@ Version Version::fromString(const QString &versionString, bool buildNumberAllowe pattern += QStringLiteral("(?:\\.(\\d+))?"); // Followed by a dot and a number up to two times. if (buildNumberAllowed) pattern += QStringLiteral("(?:[-.](\\d+))?"); // And possibly a dash or dot followed by the build number. - QRegExp rex(pattern); - if (!rex.exactMatch(versionString)) + const QRegularExpression rex(QRegularExpression::anchoredPattern(pattern)); + const QRegularExpressionMatch match = rex.match(versionString); + if (!match.hasMatch()) return Version{}; - const int majorNr = rex.cap(1).toInt(); - const int minorNr = rex.captureCount() >= 2 ? rex.cap(2).toInt() : 0; - const int patchNr = rex.captureCount() >= 3 ? rex.cap(3).toInt() : 0; - const int buildNr = rex.captureCount() >= 4 ? rex.cap(4).toInt() : 0; + const int majorNr = match.captured(1).toInt(); + const int minorNr = match.lastCapturedIndex() >= 2 ? match.captured(2).toInt() : 0; + const int patchNr = match.lastCapturedIndex() >= 3 ? match.captured(3).toInt() : 0; + const int buildNr = match.lastCapturedIndex() >= 4 ? match.captured(4).toInt() : 0; return Version{majorNr, minorNr, patchNr, buildNr}; } diff --git a/src/lib/corelib/tools/version.h b/src/lib/corelib/tools/version.h index 63ad3f88c..7b2d23ebb 100644 --- a/src/lib/corelib/tools/version.h +++ b/src/lib/corelib/tools/version.h @@ -51,7 +51,7 @@ QT_END_NAMESPACE namespace qbs { -class QBS_EXPORT Version +class Version { public: constexpr explicit Version(int majorVersion = 0, int minorVersion = 0, int patchLevel = 0, @@ -73,9 +73,9 @@ public: constexpr int buildNumber() const { return m_build; } constexpr void setBuildNumber(int nr) { m_build = nr; } - static Version fromString(const QString &versionString, bool buildNumberAllowed = false); - QString toString(const QChar &separator = QLatin1Char('.'), - const QChar &buildSeparator = QLatin1Char('-')) const; + static QBS_EXPORT Version fromString(const QString &versionString, bool buildNumberAllowed = false); + QString QBS_EXPORT toString(const QChar &separator = QLatin1Char('.'), + const QChar &buildSeparator = QLatin1Char('-')) const; private: int m_major; diff --git a/src/lib/corelib/tools/visualstudioversioninfo.cpp b/src/lib/corelib/tools/visualstudioversioninfo.cpp index 02e5ef495..9ea86aaed 100644 --- a/src/lib/corelib/tools/visualstudioversioninfo.cpp +++ b/src/lib/corelib/tools/visualstudioversioninfo.cpp @@ -58,8 +58,8 @@ VisualStudioVersionInfo::VisualStudioVersionInfo(const Version &version) std::set<VisualStudioVersionInfo> VisualStudioVersionInfo::knownVersions() { static const std::set<VisualStudioVersionInfo> known = { - Version(16), Version(15), Version(14), Version(12), Version(11), Version(10), Version(9), - Version(8), Version(7, 1), Version(7), Version(6) + Version(17), Version(16), Version(15), Version(14), Version(12), Version(11), + Version(10), Version(9), Version(8), Version(7, 1), Version(7), Version(6) }; return known; } @@ -125,6 +125,8 @@ int VisualStudioVersionInfo::marketingVersion() const return 2017; case 16: return 2019; + case 17: + return 2022; default: qWarning() << QStringLiteral("unrecognized Visual Studio version: ") << m_version.toString(); @@ -171,17 +173,18 @@ QString VisualStudioVersionInfo::toolsVersion() const QString VisualStudioVersionInfo::platformToolsetVersion() const { static std::pair<int, QString> table[] = { + {17, QStringLiteral("v143")}, // VS 2022 {16, QStringLiteral("v142")}, // VS 2019 {15, QStringLiteral("v141")} // VS 2017 }; - for (auto p : table) { + for (const auto &p : table) { if (p.first == m_version.majorVersion()) return p.second; } return QStringLiteral("v%1").arg(m_version.majorVersion() * 10); } -quint32 qHash(const VisualStudioVersionInfo &info) +QHashValueType qHash(const VisualStudioVersionInfo &info) { return qHash(info.version().toString()); } diff --git a/src/lib/corelib/tools/visualstudioversioninfo.h b/src/lib/corelib/tools/visualstudioversioninfo.h index d4b226623..92eecb388 100644 --- a/src/lib/corelib/tools/visualstudioversioninfo.h +++ b/src/lib/corelib/tools/visualstudioversioninfo.h @@ -43,6 +43,7 @@ #include "qbs_export.h" +#include <tools/porting.h> #include <tools/version.h> #include <QtCore/qstring.h> @@ -78,7 +79,7 @@ private: Version m_version; }; -quint32 qHash(const VisualStudioVersionInfo &info); +QHashValueType qHash(const VisualStudioVersionInfo &info); } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/tools/vsenvironmentdetector.cpp b/src/lib/corelib/tools/vsenvironmentdetector.cpp index f8f98e7b7..5bcbd93b6 100644 --- a/src/lib/corelib/tools/vsenvironmentdetector.cpp +++ b/src/lib/corelib/tools/vsenvironmentdetector.cpp @@ -64,7 +64,7 @@ static QString windowsSystem32Path() #ifdef Q_OS_WIN wchar_t str[UNICODE_STRING_MAX_CHARS]; if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, str))) - return QString::fromUtf16(reinterpret_cast<ushort*>(str)); + return QString::fromUtf16(reinterpret_cast<char16_t*>(str)); #endif return {}; } @@ -124,14 +124,12 @@ QString VsEnvironmentDetector::findVcVarsAllBat(const MSVC &msvc, QString fullPath = dir.absoluteFilePath(path); if (dir.exists(path)) return fullPath; - else - searchedPaths.push_back(fullPath); + searchedPaths.push_back(fullPath); path = QStringLiteral("Auxiliary/Build/") + vcvarsallbat; fullPath = dir.absoluteFilePath(path); if (dir.exists(path)) return fullPath; - else - searchedPaths.push_back(fullPath); + searchedPaths.push_back(fullPath); return {}; } @@ -193,13 +191,13 @@ bool VsEnvironmentDetector::startDetection(const std::vector<MSVC *> &compatible static void batClearVars(QTextStream &s, const QStringList &varnames) { for (const QString &varname : varnames) - s << "set " << varname << '=' << endl; + s << "set " << varname << '=' << Qt::endl; } static void batPrintVars(QTextStream &s, const QStringList &varnames) { for (const QString &varname : varnames) - s << "echo " << varname << "=%" << varname << '%' << endl; + s << "echo " << varname << "=%" << varname << '%' << Qt::endl; } static QString vcArchitecture(const MSVC *msvc) @@ -232,14 +230,22 @@ void VsEnvironmentDetector::writeBatchFile(QIODevice *device, const QString &vcv << QStringLiteral("INCLUDE") << QStringLiteral("LIB") << QStringLiteral("WindowsSdkDir") << QStringLiteral("WindowsSDKVersion") << QStringLiteral("VSINSTALLDIR"); QTextStream s(device); + using Qt::endl; s << "@echo off" << endl; + // Avoid execution of powershell (in vsdevcmd.bat), which is not in the cleared PATH + s << "set VSCMD_SKIP_SENDTELEMETRY=1" << endl; for (const MSVC *msvc : msvcs) { s << "echo --" << msvc->architecture << "--" << endl << "setlocal" << endl; batClearVars(s, varnames); s << "set PATH=" << m_windowsSystemDirPath << endl; // vcvarsall.bat needs tools from here - s << "call \"" << vcvarsallbat << "\" " << vcArchitecture(msvc) - << " || exit /b 1" << endl; + s << "call \"" << vcvarsallbat << "\" " << vcArchitecture(msvc); + if (!msvc->sdkVersion.isEmpty()) + s << " " << msvc->sdkVersion; + const auto vcVarsVer = MSVC::vcVariablesVersionFromBinPath(msvc->binPath); + if (!vcVarsVer.isEmpty()) + s << " -vcvars_ver=" << vcVarsVer; + s << " || exit /b 1" << endl; batPrintVars(s, varnames); s << "endlocal" << endl; } @@ -272,8 +278,6 @@ void VsEnvironmentDetector::parseBatOutput(const QByteArray &output, std::vector value.remove(m_windowsSystemDirPath); if (value.endsWith(QLatin1Char(';'))) value.chop(1); - if (value.endsWith(QLatin1Char('\\'))) - value.chop(1); targetEnv->insert(name, value); } } diff --git a/src/lib/corelib/tools/vsenvironmentdetector.h b/src/lib/corelib/tools/vsenvironmentdetector.h index 7fa152cb6..39bea07d6 100644 --- a/src/lib/corelib/tools/vsenvironmentdetector.h +++ b/src/lib/corelib/tools/vsenvironmentdetector.h @@ -57,7 +57,7 @@ class MSVC; class QBS_EXPORT VsEnvironmentDetector { public: - explicit VsEnvironmentDetector(QString vcvarsallPath = QString()); + explicit VsEnvironmentDetector(QString vcvarsallPath = {}); bool start(MSVC *msvc); bool start(std::vector<MSVC *> msvcs); @@ -71,6 +71,7 @@ private: const QString m_windowsSystemDirPath; const QString m_vcvarsallPath; + const QString m_vcVariablesVersion; QString m_errorString; }; diff --git a/src/lib/corelib/use_corelib.pri b/src/lib/corelib/use_corelib.pri deleted file mode 100644 index c674ee664..000000000 --- a/src/lib/corelib/use_corelib.pri +++ /dev/null @@ -1,47 +0,0 @@ -include(../../../qbs_version.pri) -include(../../library_dirname.pri) - -isEmpty(QBSLIBDIR) { - QBSLIBDIR = $$OUT_PWD/../../../$${QBS_LIBRARY_DIRNAME} -} - -unix { - LIBS += -L$$QBSLIBDIR -lqbscore -} - -isEmpty(QBS_RPATH): QBS_RPATH = ../$$QBS_LIBRARY_DIRNAME -!qbs_disable_rpath { - linux-*: QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,\$\$ORIGIN/$${QBS_RPATH}\' - macx: QMAKE_LFLAGS += -Wl,-rpath,@loader_path/$${QBS_RPATH} -} - -!CONFIG(static, static|shared) { - QBSCORELIBSUFFIX = $$QBS_VERSION_MAJ -} - -win32 { - CONFIG(debug, debug|release) { - QBSCORELIB = qbscored$$QBSCORELIBSUFFIX - } - CONFIG(release, debug|release) { - QBSCORELIB = qbscore$$QBSCORELIBSUFFIX - } - msvc { - LIBS += /LIBPATH:$$QBSLIBDIR - QBSCORELIB = $${QBSCORELIB}.lib - LIBS += Shell32.lib - } else { - LIBS += -L$${QBSLIBDIR} - QBSCORELIB = lib$${QBSCORELIB} - } - LIBS += $$QBSCORELIB -} - -INCLUDEPATH += \ - $$PWD - -CONFIG(static, static|shared) { - DEFINES += QBS_STATIC_LIB -} -qbs_enable_project_file_updates:DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES -qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS diff --git a/src/lib/corelib/use_installed_corelib.pri b/src/lib/corelib/use_installed_corelib.pri deleted file mode 100644 index 4ff72414d..000000000 --- a/src/lib/corelib/use_installed_corelib.pri +++ /dev/null @@ -1,38 +0,0 @@ -include(qbs_version.pri) - -QBSLIBDIR=$${PWD}/../../lib -unix { - LIBS += -L$$QBSLIBDIR -lqbscore -} - -!qbs_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 - } - msvc { - LIBS += /LIBPATH:$$QBSLIBDIR - QBSCORELIB = $${QBSCORELIB}.lib - LIBS += Shell32.lib - } else { - LIBS += -L$${QBSLIBDIR} - QBSCORELIB = lib$${QBSCORELIB} - } - LIBS += $$QBSCORELIB -} - -INCLUDEPATH += $${PWD} - -CONFIG(static, static|shared) { - DEFINES += QBS_STATIC_LIB -} -qbs_enable_project_file_updates:DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES -qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS |